This repository is a small, didactic example of Spec-Driven Development (SDD) applied to hardware-facing Python code. It uses a Linux pseudo-terminal (PTY) as a hardware double for a GPS receiver, so the telemetry client can be specified, implemented, and tested without physical UART hardware.
It is an educational prototype, not flight-qualified software or a substitute for hardware integration and certification.
The project turns an Interface Control Document (ICD) into progressively more concrete artifacts:
- Governing engineering principles in
.specify/memory/constitution.md. - Functional requirements and acceptance criteria in
specs/001-gps-telemetry/spec.md. - Architecture, contracts, data model, research decisions, and a quickstart guide in
specs/001-gps-telemetry/. - Dependency-ordered, test-first implementation tasks in
specs/001-gps-telemetry/tasks.md. - A Python client, a PTY hardware double, and automated unit/integration tests.
- A convergence pass that identifies and closes remaining gaps after implementation.
The authoritative source requirement document is GPS System Requirements and ICD.md.
flowchart LR
Double[GPS PTY hardware double] -->|checksummed GPGGA at 1 Hz| Client[GPS telemetry client]
Double -->|SCIENCE_START control heartbeat| Client
Client -->|SU_R_SDP and SU_R_HK recovery frames| Double
Client -->|accepted telemetry records| Csv[telemetry.csv]
Client -->|OBC_SU_HK recovery event| Recovery[local recovery event]
gps_double.py creates a PTY master/slave pair and prints the slave device path. The client opens
that slave path with the same pyserial configuration it would use for a hardware UART. This makes
the operating-system serial path part of the test, rather than replacing it with an in-memory mock.
- The simulator emits a hardcoded, checksummed
$GPGGAsentence once per second. - The client accepts only
$GPGGAinput with non-empty UTC, latitude, and longitude fields and a valid two-digit NMEA XOR checksum. - Valid records are appended to
telemetry.csvonly while the client is inSCIENCE. - The CSV columns are
utc_timestamp,latitude,longitude, andpersisted_at.
| State | Purpose | Main transition |
|---|---|---|
INIT |
Create and configure the serial connection. | Successful initialization moves to STANDBY. |
STANDBY |
Connected, but does not write telemetry. | SCIENCE_START (0x01) moves to SCIENCE. |
SCIENCE |
Validate and log GPS telemetry. | A timeout or incomplete packet moves to ERROR. |
ERROR |
Execute fail-safe recovery. | Recovery records an event and attempts shutdown actions. |
The simulator emits the SCIENCE_START control frame every 250 ms. This acts as a control
heartbeat so the active serial session remains synchronized with the mandatory 500 ms packet
deadline while GPS data remains at 1 Hz.
Commands use this binary frame:
0x7E | CMD_ID | LEN | DATA[LEN] | XOR
XOR is calculated over CMD_ID, LEN, and every data byte.
| Command | Value | Purpose |
|---|---|---|
SCIENCE_START |
0x01 |
Transition from STANDBY to SCIENCE. |
SU_R_SDP |
0x02 |
Recovery action requesting science data. |
SU_R_HK |
0x03 |
Recovery action requesting housekeeping data. |
Unknown commands and frames with an invalid start byte, length, or XOR are rejected without a state change.
A missing or incomplete packet triggers SyncError after a configured 500 ms serial timeout. In
the automated timing test, the observed timeout is required to be between 500 ms and 550 ms.
When recovery starts, the client records these actions in order:
ABORT- Send
SU_R_SDP(0x02) - Send
SU_R_HK(0x03) - Emit the local
OBC_SU_HKrecovery event TURN_OFF
The recovery event is created even when the disconnected serial device cannot accept the two outbound recovery frames.
- Linux
- Python 3.11 or later
pyserial3.5
Create and activate a virtual environment if desired:
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txtFrom the repository root:
python3 -W error::ResourceWarning -m unittest discover -p 'test_*.py'The suite covers:
- NMEA
$GPGGAfield and checksum validation. - Command-frame encoding, XOR validation, and unknown-command rejection.
- Little-endian preservation of multi-byte command payload data.
- UART 9600 8N1 configuration.
- State transitions, state-gated CSV writing, timeout behavior, and recovery ordering.
- PTY master/slave communication, recovery-frame observation, 1 Hz NMEA intervals, and PTY loss.
The generated telemetry.csv file is ignored by Git and is removed by the test workflow.
Start the simulator in one terminal:
python3 gps_double.pyIt prints a path such as /dev/pts/3. In a second terminal, use that path:
python3 gps_client.py /dev/pts/3The simulator automatically sends control heartbeats and valid NMEA records. The client enters
SCIENCE after a SCIENCE_START frame and writes accepted records to telemetry.csv in the
current directory. Stop the simulator with Ctrl+C; the client detects the disconnection and
enters its error-recovery path.
This repository was initialized for GitHub Copilot with Spec Kit. The relevant generated assets are intentionally committed:
.github/skills/: Copilot commands such as/speckit.specifyand/speckit.implement..specify/: Spec Kit templates, scripts, workflow metadata, and project constitution.specs/001-gps-telemetry/: The feature-specific SDD artifacts.
The completed flow was:
/speckit.constitution
/speckit.specify
/speckit.clarify
/speckit.plan
/speckit.tasks
/speckit.analyze
/speckit.implement
/speckit.converge
/speckit.implement
For a new change, begin by describing the desired behavior with /speckit.specify, then repeat the
specification, clarification, planning, tasking, implementation, and convergence cycle. Keep the
ICD updated when interface requirements change.
.
├── GPS System Requirements and ICD.md # Source requirements and interface contract
├── README.md # This guide
├── requirements.txt # Python runtime dependency
├── gps_client.py # Serial client, state machine, parser, and CSV writer
├── gps_double.py # Linux PTY hardware double
├── test_parser.py # Parser and command-frame unit tests
├── test_client.py # Client, state, recovery, and CSV tests
├── test_integration.py # Real PTY integration and disconnection tests
├── .github/skills/ # Spec Kit command definitions for Copilot
├── .specify/ # Spec Kit project configuration and constitution
└── specs/001-gps-telemetry/ # Specification, plan, tasks, contract, and guides
The implementation plan maps the ICD requirements to their planned verification in specs/001-gps-telemetry/plan.md. The completed task list provides additional requirement-level traceability in specs/001-gps-telemetry/tasks.md.
The PTY double validates the client against a real Linux serial-device interface, but it does not prove behavior on a physical UART. In particular:
pyserialconfigures 9600 8N1 but cannot control or measure on-wire LSB-first bit order. Verify that behavior with the selected UART hardware and integration evidence.- The two independent 10 MB mass-memory units and the ten connector mate/de-mate cycle limit are physical deployment responsibilities; the client and simulator do not emulate them.
- The NMEA source data, command IDs, and recovery event are deliberately minimal so the project remains a clear teaching example.