Ground Station Software &
Object-Oriented Programming
Bridging the gap between Web Scripting and Mission-Critical Systems in Space Engineering.
Video Lecture: Ground Station Fundamentals
Watch this pre-recorded lecture for an overview of the concepts we will cover today.
Scripting vs Software Engineering
In the Space Apps bootcamp, you learned web scripting (HTML, CSS, JavaScript, Leaflet maps, and Firebase databases) to create interactive visual interfaces. Developing ground station middleware introduces structural shifts:
| Aspect | Web Scripting | Mission Software |
|---|---|---|
| Goal | UX and reactive interfaces. | Reliability and data parsing. |
| Failure | Reload the page. | Loss of Mission. |
| Data | Strings, JSON. | Raw bytes, binary frames. |
| Hardware | Browser sandbox, APIs. | Serial ports, radio links. |
Why Mission-Critical Systems Need Structure
As you transition into building a satellite ground station, ad hoc scripting files quickly become unmanageable. Ground station software must handle multiple concurrent tasks:
- Concurrency: Reading serial data streams from a radio transceiver while simultaneously updating real-time tracking graphs, logging entries to a local server, and waiting for user command inputs.
- Determinism & Robustness: A ground station cannot afford unexpected crashes, memory leaks, or unhandled exceptions when tracking a satellite overhead.
- State Consistency: Your software needs to track the precise status of the satellite pass, including ground track coordinates, active serial port, radio link quality, and tracking angles.
Systems Thinking: The Software Architect Mindset
In professional aerospace and mission software engineering, individual code lines are rarely written from scratch. The true engineering innovation lies in the organization of code and the orchestration of workflows.
- Component Orchestration: Linking physical sensors, ESP32 microcontrollers, XBee radio transceivers, local Flask servers, and interactive web dashboards.
- Workflow Design: Composing data telemetry (downlink) and control instructions (uplink) into a secure, predictable loop.
- Systems Level Debugging: Troubleshooting an error by tracing it through hardware buffers, radio signals, serial parses, database saves, and browser rendering.
Data Types: JavaScript vs C/C++
In JavaScript, you never think about data types. A variable is just var x = 5 or let name = "ISU". In C/C++ and C#, every variable must declare its exact type and size because the compiler needs to know how many bytes to allocate in memory.
var temperature = 25.46; // number var sensorName = "BME280"; // string var isActive = true; // boolean var count = 42; // number // JS doesn't care if 42 is an // integer or float. It's all // just "number" (64-bit float).
float temperature = 25.46f; // 4 bytes char sensorName[] = "BME280"; // 7 bytes bool isActive = true; // 1 byte int count = 42; // 4 bytes uint8_t rawByte = 0xFF; // 1 byte double precise = 3.14159265; // 8 bytes // Every byte matters in telemetry.
| C/C++ Type | Size | Range / Purpose | Ground Station Example |
|---|---|---|---|
uint8_t / byte |
1 byte | 0 to 255 | Frame header byte: 0xA1 |
int |
4 bytes | -2.1B to 2.1B | Sensor ID: 0x00000002 |
float |
4 bytes | ~7 decimal digits | Temperature: 25.46 |
double |
8 bytes | ~15 decimal digits | GPS coordinates: 48.5734053 |
char |
1 byte | Single ASCII character | Command flag: 'T' |
Anatomy of a C Program
A C/C++ program has a rigid structure that differs significantly from a JavaScript file. Understanding this structure is essential before reading the ESP32 satellite firmware.
// Just start writing code! let temp = 25.46; console.log("Temperature:", temp); // Functions anywhere function readSensor() { return Math.random() * 40; } // No compilation needed // Browser runs it directly
// 1. INCLUDES (import libraries) #include <Wire.h> #include "sensors.h" // 2. GLOBAL VARIABLES (typed!) float temperature = 0.0; int sensorID = 0x02; // 3. SETUP (runs once at boot) void setup() { Serial.begin(115200); Wire.begin(13, 15); } // 4. LOOP (runs forever) void loop() { temperature = readBME(); Serial.write((uint8_t*)&temperature, sizeof(float)); delay(1000); }
import. Pulls in library code at compile time.setInterval. The main execution cycle.C Syntax Essentials: The Rules of the Compiler
Because C++ is compiled and strictly typed, the syntax is highly structured. Code blocks, terminations, and comments have rigid rules that the compiler enforces strictly before any code runs.
| Syntax Element | C/C++ Rule | Ground Station Example |
|---|---|---|
| Commenting Code | Single-line uses //.Multi-line blocks use /* ... */. |
// Reads temperature sensor/* Configure hardware pins */ |
| Semicolons | Required to terminate every single instruction statement. | float temp = 25.4f; (Compiler fails if missing) |
Curly Braces {} |
Enclose bodies of functions, loops, and conditional statements. | void loop() { readSensor(); } |
| Case Sensitivity | Identifiers are case-sensitive (capitalization matters!). | sensorTemp and sensortemp are completely different. |
& Operator)
In JavaScript, numbers and booleans are passed by value. In C/C++, you can pass a variable's memory reference using &. This allows a function to modify variables directly in the calling function!
// Function signature in sensors.h bool readBME(float &temp, float &pres); // Calling the function in loop() float t, p; bool success = readBME(t, p); // 't' and 'p' are updated directly in memory!
What is Object-Oriented Programming?
OOP organizes software around data (attributes) and behavior (methods) rather than sequential scripts. We model physical hardware as distinct, logical entities.
// Satellite attributes string name = "ISU-SAT1"; float batteryLevel = 87.3; double frequency = 437.500; int orbitAltKm = 550; bool isTransmitting = true; uint8_t satelliteID = 0x13;
// Satellite methods void transmitTelemetry(); void takePicture(); void setRefreshInterval(int ms); float getBatteryLevel(); bool executeCommand(uint8_t cmd); void deploySolarPanels();
Class vs Object: A Classic Example
The most common confusion for beginners: what is the difference between a Class and an Object? Think of it like this:
class Car { // Attributes (no values yet) string make; string color; int horsepower; float fuel_level; // Methods (what it can do) start_engine(); accelerate(speed); refuel(liters); }
// Two objects from the same class myCar = new Car() myCar.make = "Toyota" myCar.color = "Red" myCar.horsepower = 150 myCar.fuel_level = 45.2 yourCar = new Car() yourCar.make = "BMW" yourCar.color = "Black" yourCar.horsepower = 300 yourCar.fuel_level = 60.0
myCar.fuel_level does not affect yourCar.fuel_level.
Now Apply It: The Satellite Analogy
The same pattern applies directly to ground station development:
- Class (The Blueprint): Defines what attributes and methods all satellites share. No hardware or specific orbit exists yet.
- Object (The Instance): A specific satellite built from the blueprint, orbiting in its own track, with independent battery and telemetry values.
Satellite is the class, then ESTubeSat-1 and ESTubeSat-2 are two distinct objects, each running separate telemetry at different frequencies.
You Already Used OOP: Google Earth Engine
Here is the secret: you have been using OOP the entire time in Google Earth Engine without knowing it. Every line of GEE code is built on classes and methods:
// Class: ee.ImageCollection // Object: the Sentinel-2 collection var collection = ee.ImageCollection( 'COPERNICUS/S2' ) // Methods called on the object: .filterDate('2024-01-01', '2024-12-31') .filterBounds(roi) .map(maskClouds) .median();
| GEE Syntax | OOP Concept |
|---|---|
ee.ImageCollection |
Class (the blueprint) |
collection |
Object (specific instance) |
.filterDate() |
Method (behavior) |
.select('B4') |
Method (accessing attributes) |
ee.Image() |
Another class |
ee.Image encapsulates bands, metadata, and projection. The ground station code you will build follows the exact same pattern, just with hardware instead of satellite imagery.
OOP Concept 1: Encapsulation
Encapsulation is the practice of bundling data attributes and the methods that manipulate them into a single class, while restricting direct access to the inner workings.
Why in Space Engineering? You want to protect the internal hardware state. A software module should not be able to manually overwrite the raw register values of your gyroscope sensor or solar panel voltage directly.
- Private Members: Attributes like `_batteryVoltage` or `_calibrationMatrix` are kept hidden from the rest of the application.
- Public Interface: We expose safe, controlled public methods like `getBatteryLevel()` or `applyCalibration()` to interact with the object safely.
class Satellite { // Private - hidden from outside private float _battery = 100.0; // Public - accessible safely public float getBatteryLevel() { return _battery; } }
OOP Concept 2: Inheritance
Inheritance allows a new class (subclass or child class) to inherit attributes and methods from an existing class (superclass or parent class), preventing redundant code.
Instead of re-writing basic telemetry, command queues, and hardware links for every satellite variation, we create a core base class and extend it.
- `transmitTelemetry()`
- `readOBCStatus()`
- `logData()`
- `captureEarthImage()`
- `controlCameraSensor()`
- `fireThrusters()`
- `trackAltitude()`
OOP Concept 3: Polymorphism
Polymorphism (Greek for "many forms") is the ability of different classes to respond to the same method call in their own custom way.
Imagine your ground station software needs to talk to two types of transceivers: an XBee radio link (running over standard Serial UART) and a LoRa radio link (running over SPI).
- We define a standardized base interface `Transceiver` with a method signature `sendData(byte[] frame)`.
- XBeeTransceiver overrides this to output bytes using C++ `Serial.write()`.
- LoraTransceiver overrides this to write byte strings to specific SPI registers.
- The core Ground Station software simply calls `transceiver.sendData()`, without needing to know which physical hardware is attached.
Check Your Understanding: OOP
// The ground station dashboard code: Transceiver radio = getActiveTransceiver(); radio.transmit(telemetryPacket); // works for ANY radio type // Behind the scenes, 'radio' could be: // - SerialTransceiver (talks over COM port) // - TCPTransceiver (talks over network) // - SPITransceiver (talks over SPI bus) // The dashboard code doesn't need to know which one!
The Tools: Visual Studio vs VS Code
A major source of confusion for junior developers is the difference between Visual Studio and Visual Studio Code. Although developed by Microsoft, these tools are built for entirely different workflows. In this course, you will use both.
| Feature | Visual Studio (Full IDE) | Visual Studio Code (Code Editor) |
|---|---|---|
| Core Category | Heavyweight Integrated Development Environment (IDE). | Lightweight, fast Source Code Editor. |
| Primary Usage | Large-scale C#, C++, .NET enterprise, and Windows desktop application suites. | Web programming (HTML/JS), Python, scripting, and embedded firmware (via PlatformIO). |
| GUI Builder | Built-in drag and drop visual UI designers for desktop forms. | No visual GUI builder; relies on web pages or custom UI libraries. |
| Resource Footprint | Heavy (10-40 GB disk space, high RAM requirements). | Lightweight (200-500 MB, launches instantly). |
| Course Context | You will use this to build the C# .NET Windows desktop Ground Station app. | You will use this for Arduino/ESP32 C++ firmware and Web dashboard frontend. |
| AI Copilot | GitHub Copilot integrates natively. IntelliSense + Copilot suggestions inline while coding C#. | GitHub Copilot extension available. Works across all languages (JS, Python, C++). |
Compiled vs Interpreted Languages
In the bootcamp, your JavaScript ran directly in the browser with no extra steps. C, C++, and C# work completely differently: the source code must be compiled into machine code before it can run.
script.js↓ Browser reads line by line
↓ Executes immediately
↓ Errors appear at runtime
✓ No build step needed
✗ Slower execution
✗ Bugs found only when that line runs
main.cpp↓ Compiler analyzes ALL code
↓ Type-checks every variable
↓ Produces
program.exe↓ CPU runs the binary directly
✓ Fast execution (native speed)
✓ Errors caught BEFORE running
.exe file. If there is a single type mismatch (e.g., passing a string where an int is expected), the build fails and nothing runs. This strictness prevents bugs from reaching the satellite.
Open Source vs Closed Source Software
Once code is compiled into a binary (.exe), the original source code is no longer visible. This is the foundation of the open source vs closed source distinction.
Examples:
- Linux, Arduino IDE, Python
- VS Code (open source core)
- Your Space Apps web projects on GitHub
- Most scientific software
Licenses: MIT, GPL, Apache 2.0
Examples:
- Windows OS, Adobe Photoshop
- Visual Studio (full IDE)
- SDR# (radio receiver software)
- Many commercial ground station suites
Protection: Trade secrets, licensing fees
.exe without revealing your source code.
The .NET Framework Ecosystem
When you build the ground station desktop application in Visual Studio, you are building on top of Microsoft's .NET Framework. It provides a massive library of pre-built code so you do not have to write everything from scratch.
SerialPort.Open(), .Read(), .Write() to talk to the TNC and radio hardware.document.getElementById(), fetch(), and the DOM. .NET gives you SerialPort, File.ReadAllBytes(), and Windows UI components.
Getting Started with Visual Studio
Here is the step-by-step workflow you will follow to create your ground station desktop application in Visual Studio:
ISU_GroundStation) → Click Create.Main() function). Double-click Form1 to open the visual designer.115200, DataBits = 8, Parity = None, StopBits = One. This connects your app to the TNC hardware..exe application. The Output window shows build status and any compiler errors.AI Coding Assistants: Power & Limits
Modern software engineering in 2026 relies heavily on AI-assisted coding tools (like Gemini, GitHub Copilot, or Cursor). These tools are incredibly powerful junior coding partners, but they require a critical mental guardrail.
- Synthesizing syntax structures (e.g., regex, struct formats).
- Explaining errors and recommending optimizations.
- Finding typos or unhandled cases in code.
- AI frequently makes logical assumptions and silent hallucinations.
- Blindly copying AI code without understanding it leads to fragile systems.
Setting Up AI Assistants in Your IDEs
Extensions panel → Search "GitHub Copilot" → Install → Sign in with GitHub (free for students).
Gemini Code Assist:
Extensions panel → Search "Gemini Code Assist" → Install → Sign in with Google account.
Both provide inline completions, chat sidebar, and code explanations for JS, Python, C++, and Arduino sketches.
Extensions → Manage Extensions → Search "GitHub Copilot" → Install → Restart VS. Native IntelliSense integration for C#/.NET.
Gemini Code Assist:
Extensions → Search "Google Cloud Code" → Install → Enable Gemini. Provides inline suggestions and chat for .NET projects.
In Visual Studio, Copilot integrates directly with the debugger and solution explorer for deeper C# support.
Check Your Understanding: AI in Coding
Intermission: 15-Minute Break
Take a quick stretch, grab some coffee, and process the structural concepts of OOP before we deep-dive into binary telemetry frames and serialization!
Hardware Telemetry: ESP32 & XBee Links
In your upcoming lab, your satellite is driven by an ESP32 microcontroller. It communicates telemetry and receives commands over an XBee radio module using a Serial UART connection configured at 115200 baud.
- ESP32 Satellite Hub: Coordinates onboard systems, reads sensors (BME280 for temp/humidity, MPU6050 for acceleration/rotation), and drives an OV2640 camera payload.
- XBee Transceiver: Standardizes radio frequency (RF) packets, exposing them to the ESP32 as simple RX/TX serial pins (UART2).
- Baud Rate (115200): The clock speed of the serial interface, representing the transmission of 115,200 raw bits per second.
Why Binary Frames? JSON vs Hex
Let's compare transmitting a single telemetry coordinate (temperature = 25.46) over a radio link:
{"temp":25.46}
- Size: 15 characters = 15 bytes.
- Parser needed: A text parsing engine must run on the receiving microcomputer.
0x41CBAD70
- Size: Exactly 4 bytes.
- Parser needed: Zero string parsing. Raw bytes map directly into memory.
Anatomy of a Telemetry Frame
Dr. Ramson's satellite firmware packages sensor values into a rigid binary packet containing specific landmarks:
- Header (0xA1B2C3D4): A static 4-byte marker. The ground station monitors incoming serial data searching for this exact sequence to know a telemetry packet has arrived.
- Payload: Sensor IDs followed by three 4-byte floating point variables representing active sensor outputs (e.g., Temp, Hum, Press).
- Footer (0x1E2D3C4B): A static 4-byte trailing marker, confirming the packet is complete and was not corrupted during transmission.
How the Backend Parses the Bytes
On the ground station server, your Python/C# backend opens a connection to the serial port. It continuously reads bytes, looking for the header. Here is a simplified visual representation of the byte-unpacking pipeline:
[0xA1, 0xB2, 0xC3, 0xD4, 0x02, ...]
Validate 0xA1B2C3D4 Marker
Read sensor structs (floats)
Validate 0x1E2D3C4B
In Python, we unpack raw binary lists into standard variables using the struct library:
import struct # Unpack 4-byte header, 1-byte sensor ID, and three 4-byte floats header, sensor_id, val1, val2, val3 = struct.unpack('<I B f f f', raw_bytes[0:17])
Code Review: Header vs Source Separation
In your satellite firmware lab, you will see code split into .h (headers) and .cpp (sources). This separation of concerns represents the physical boundaries of your software components.
sensors.h)// Header guards prevent multiple inclusions #ifndef SENSORS_H #define SENSORS_H #include <Arduino.h> // Declare public functions (The Interface Contract) bool init_sensors(); void transmit_data(float &temperature, float &pressure, float &humidity, float &gx, float &gy, float &gz, float &ax, float &ay, float &az); #endif
sensors.cpp)#include "sensors.h" #include <Adafruit_BME280.h> Adafruit_BME280 bme; // Private hardware object bool init_sensors() { // Configure pins SDA=13, SCL=15 and start I2C sensor Wire.begin(13, 15); return bme.begin(0x76, &Wire); }
Code Review: sensors.cpp transmit_data()
Let's review the critical code that packages sensor data into a raw binary frame and streams it over the XBee radio interface.
// 1. Pack telemetry into an aligned memory structure struct TelemetryFrame { uint32_t header = 0xA1B2C3D4; // 4 bytes uint32_t sensor_id; // 4 bytes float data[3]; // 12 bytes uint32_t footer = 0x1E2D3C4B; // 4 bytes }; void transmit_data(float &temperature, ...) { TelemetryFrame frame; frame.sensor_id = 0x00000002; // BME280 ID frame.data[0] = temperature; frame.data[1] = pressure; frame.data[2] = humidity; // 2. Cast struct to a byte array and stream it over Serial2 Serial2.write((uint8_t *)&frame, sizeof(frame)); }
&frame gets the starting address of the struct in RAM. (uint8_t *) tells the microcontroller: "treat this block of memory as an array of individual bytes." sizeof(frame) tells it how many bytes to write.
Command Uplink: The 4-Byte Control Protocol
Communication with satellites is bidirectional. While the satellite downlinks telemetry frames, the ground station must uplink commands. In your lab, you will construct and transmit strict 4-byte command packets.
| Byte Index | Field Name | Hex Value | Description |
|---|---|---|---|
| Byte 0 | Satellite ID | 0x13 |
Target spacecraft identifier. |
| Byte 1 | OBC Header | 0xAA |
Onboard Computer target flag. |
| Byte 2 | Command Code | 0xB0 / 0x15 / ... |
The physical action to trigger. |
| Byte 3 | GS Source | 0x01 |
Originates from Ground Station. |
esp32_program.ino)// Reading the serial link in loop() if (Serial2.available() >= 4) { uint8_t packet[4]; Serial2.readBytes(packet, 4); // Verify headers if (packet[0] == 0x13 && packet[1] == 0xAA) { uint8_t cmd = packet[2]; if (cmd == 0xB0) capture_jpeg(); else if (cmd == 0x15) set_rate(15); } }
Variable Payload: The Camera JPEG Framing Protocol
Fixed-size telemetry frames (like our 56-byte sensor struct) are predictable because variable positions in memory never change. However, when the satellite transmits an **image capture**, the payload size changes with every picture. We must use a variable-length framing protocol.
| Field | Size | Value | Pedagogical Purpose |
|---|---|---|---|
| Header | 4 bytes | 0xDEADBEEF |
Identifies the start of an image stream instead of a telemetry frame. |
| Size | 4 bytes | image_size |
Tells the ground station exactly how many payload bytes to read. |
| Payload | Variable | Raw JPEG bytes | The physical image buffer (starts 0xFFD8, ends 0xFFD9). |
| Footer | 4 bytes | 0xFEEDFACE |
Confirms the full image completed transmission without data corruption. |
Because the payload is variable, the ground station cannot read a static struct size. It must execute a **two-phase read**:
- Read 8 bytes to verify
0xDEADBEEFand extract the 32-bit size integer (N). - Execute a blocked read for exactly
Nbytes to fetch the JPEG data, followed by 4 bytes to check the0xFEEDFACEfooter.
// Read image size from stream (4 bytes) byte[] sizeBuffer = readBytes(4); int imgSize = convertToInt(sizeBuffer); // Read the exact JPEG data byte[] jpegData = readBytes(imgSize);
End-to-End: The Telemetry Lifecycle
How does a physical physical phenomenon (like temperature) on a satellite in space travel all the way to a text character on your ground station browser dashboard? Let's trace the physical and logical lifecycle:
Full Stack Ground Station Architecture
A comprehensive ground station architecture is a multi-layered system, bridging raw hardware streams up to web-based dashboards.
- Physical/Hardware Layer: The satellite orbital transceivers and antennas communicating RF signals.
- Middleware/TNC Layer: A Terminal Node Controller (TNC) like the Kantronics 9612XE, running in KISS mode to package raw radio packets into clean serial structures.
- Backend/Server Layer: Opens the serial ports, validates frames, archives telemetry to database, and schedules commands.
- Frontend Dashboard Layer: Updates real-time dials, track map, and visualizes status changes.
Hardware You Will Work With
Runs the satellite firmware (C++). Reads sensors, captures images, transmits binary telemetry via UART.
Radio link between satellite and ground station. Communicates via serial UART at 115200 baud.
The Finished Ground Station Dashboard
In your upcoming lab, your C# or Python full stack backend will drive an integrated Web Dashboard UI representing your primary control terminal.
- Satellite Track Map: An interactive map plotting predicted orbital passes and live positions.
- Command Terminal: Interactive buttons to construct and send hex command frames (like capturing an image or switching radio channels).
- Live Telemetry Dashboard: Responsive gauges and charts graphing temperatures, battery, and gyroscopic orientation dynamically.
- Camera JPEG Viewer: Displays image telemetry rebuilt byte-by-byte from satellite transmissions.
Interactive Demo: Parsing Telemetry
This is what your ground station software does every second. Click Parse Next Field to step through a real telemetry frame byte by byte.
Tools of the Trade: Your Lab Environment
To construct, test, and debug this full stack ground system, you will use several key utility tools throughout your laboratory weeks:
Check Your Understanding: Telemetry
Glossary & Summary of Big Ideas
Here are the core concepts and vocabulary from today's lecture. Review these as you launch into your laboratory experiments:
Launch Your Ground Station Lab!
Congratulations! You have completed the foundational lecture on Object-Oriented Programming and Space Full-Stack middleware. You are ready to start building!