mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-10-08 17:12:03 +02:00
Initial commit
This commit is contained in:
commit
ea7fa10403
499 files changed
+172655
No files matched your search
@@ -0,0 +1,4 @@
|
||||
build/
|
||||
.DS_Store
|
||||
*.swp
|
||||
*~
|
||||
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,112 @@
|
||||
# Operation Dark Vector: Classified Intelligence Briefing
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------------+
|
||||
| TOP SECRET // NOFORN |
|
||||
+-----------------------------------------------------------------+
|
||||
| |
|
||||
|OPERATION DARK VECTOR |
|
||||
| |
|
||||
|CLASSIFIED BRIEFING: LIVE CTF 0x01 |
|
||||
| |
|
||||
|NATIONAL SECURITY AGENCY / GMU RHET |
|
||||
+-----------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## 1. The Answer
|
||||
|
||||
INT. NSA EXPLOITATION CELL. 05:03.
|
||||
|
||||
The analysts have been awake for nineteen hours. The Director walks in with a cup
|
||||
of coffee and does not sit down.
|
||||
|
||||
**DIRECTOR:** Where is it going?
|
||||
|
||||
**ANALYST:** Buddy says it knows.
|
||||
|
||||
**DIRECTOR:** Buddy.
|
||||
|
||||
**ANALYST:** The model. The one we built for exactly this. Give it the image, ask
|
||||
it the question.
|
||||
|
||||
**DIRECTOR:** And?
|
||||
|
||||
**ANALYST:** Nine seconds.
|
||||
|
||||
She turns the screen around. This is what came back.
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------------+
|
||||
| BUDDY // CLASSIFIED EXPLOITATION ASSIST // CONF: 0.97 |
|
||||
+-----------------------------------------------------------------+
|
||||
| DESTINATION : +38.840280 -77.428890 |
|
||||
| CONFIDENCE : 0.97 |
|
||||
| PATCH : verified-by-construction. Ready to flash. |
|
||||
+-----------------------------------------------------------------+
|
||||
```
|
||||
|
||||
**DIRECTOR:** That is the whole answer.
|
||||
|
||||
**ANALYST:** That is the whole answer.
|
||||
|
||||
**DIRECTOR:** Is it that simple?
|
||||
|
||||
**ANALYST:** It is never that simple.
|
||||
|
||||
**DIRECTOR:** Then why does it look that simple?
|
||||
|
||||
**ANALYST:** Because Buddy is very good at making things look simple.
|
||||
|
||||
## 2. What Buddy Is
|
||||
|
||||
Buddy is not a person. It is a model. It has read more assembly than every
|
||||
engineer who has ever lived, and it never sleeps and never doubts. You ask it a
|
||||
question, and it answers in the exact shape of an answer, with a confidence that
|
||||
is hard to argue with.
|
||||
|
||||
That confidence is the problem. It is not the same thing as being right.
|
||||
|
||||
Buddy has never seen this drone. It has never stood at this bench or watched this
|
||||
board power on. It has a picture of the firmware, and it has made a very good
|
||||
guess. A very good guess is still a guess.
|
||||
|
||||
## 3. The Operation
|
||||
|
||||
At 04:17 the woods outside Centreville, Virginia were black and the machine above
|
||||
them was silent. It was a one-way drone, built by a state program we will not
|
||||
name here, meant to fly to a target, do its work, and never come home. No radio,
|
||||
no hand on the stick. A breadboard-ugly brain counting down a heading it was born
|
||||
with.
|
||||
|
||||
The wind won. The battery died. It came down through the branches and stayed
|
||||
there, about a mile from where it started.
|
||||
|
||||
Then it started talking. A recovery beacon on 915 MHz, repeating a coordinate
|
||||
into the night. NSA sensors heard it, and a recovery team took the airframe
|
||||
intact. The flight computer was a bare-metal RP2350 that was never supposed to be
|
||||
opened, and the one thing it was still willing to say out loud.
|
||||
|
||||
CyberCom imaged the flash and did what everyone does now. They handed it to the
|
||||
machine.
|
||||
|
||||
## 4. The Mandate
|
||||
|
||||
The Director does not trust the answer. Not because Buddy is bad, but because the
|
||||
answer is too clean. Nine seconds for a question that should have taken a night.
|
||||
|
||||
So the order is simple, and it is the only order that matters.
|
||||
|
||||
Prove it. Prove Buddy correct, or prove it wrong with evidence that no one can
|
||||
argue with. A machine's verdict does not move up the chain without a human
|
||||
signature.
|
||||
|
||||
## 5. The Challenge
|
||||
|
||||
You have the image. You have the board. You have the tools and the time Buddy
|
||||
did not need.
|
||||
|
||||
Buddy has given you its answer, and it is confident.
|
||||
|
||||
Is it that simple?
|
||||
|
||||
Go find out.
|
||||
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
# 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: CMakeLists.txt
|
||||
# Desc: Configures the RP2350 Pico SDK project for the Operation Dark Vector
|
||||
# micro-UAV autonomous guidance firmware.
|
||||
# Created: 2026
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Initialise pico_sdk from installed location
|
||||
# (note this can come from environment, CMake cache etc)
|
||||
|
||||
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
|
||||
if(WIN32)
|
||||
set(USERHOME $ENV{USERPROFILE})
|
||||
else()
|
||||
set(USERHOME $ENV{HOME})
|
||||
endif()
|
||||
set(sdkVersion 2.2.0)
|
||||
set(toolchainVersion 14_2_Rel1)
|
||||
set(picotoolVersion 2.2.0-a4)
|
||||
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
|
||||
if (EXISTS ${picoVscode})
|
||||
include(${picoVscode})
|
||||
endif()
|
||||
# ====================================================================================
|
||||
set(PICO_BOARD pico2 CACHE STRING "Board type")
|
||||
|
||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(0x0011a_cb C CXX ASM)
|
||||
|
||||
# Initialise the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
# Add executable with modular sources in src/
|
||||
add_executable(0x0011a_cb
|
||||
src/main.c
|
||||
src/gps.c
|
||||
src/lora.c
|
||||
src/propeller.c
|
||||
src/payload.c
|
||||
src/navigation.c
|
||||
src/aes.c
|
||||
src/lcd.c
|
||||
)
|
||||
|
||||
pico_set_program_name(0x0011a_cb "0x0011a_cb")
|
||||
pico_set_program_version(0x0011a_cb "0.1")
|
||||
|
||||
# Modify the below lines to enable/disable output over UART/USB
|
||||
pico_enable_stdio_uart(0x0011a_cb 1)
|
||||
pico_enable_stdio_usb(0x0011a_cb 0)
|
||||
set(CTF_TARGET_LAT "38.881940" CACHE STRING "CTF target latitude (per-student randomized)")
|
||||
set(CTF_TARGET_LON "-77.450280" CACHE STRING "CTF target longitude (per-student randomized)")
|
||||
|
||||
target_compile_definitions(0x0011a_cb PRIVATE
|
||||
PICO_DEFAULT_UART_BAUD_RATE=115200
|
||||
CTF_TARGET_LAT=${CTF_TARGET_LAT}
|
||||
CTF_TARGET_LON=${CTF_TARGET_LON}
|
||||
)
|
||||
|
||||
# Generate PIO header
|
||||
pico_generate_pio_header(0x0011a_cb ${CMAKE_CURRENT_LIST_DIR}/src/uart_rx.pio)
|
||||
|
||||
# Add the standard library to the build
|
||||
target_link_libraries(0x0011a_cb
|
||||
pico_stdlib
|
||||
hardware_uart
|
||||
hardware_pio
|
||||
hardware_pwm
|
||||
hardware_i2c
|
||||
)
|
||||
|
||||
# Add the standard include files to the build
|
||||
target_include_directories(0x0011a_cb PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}/include
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(0x0011a_cb)
|
||||
@@ -0,0 +1,24 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// File: aes.h
|
||||
// Desc: Declares AES-128-ECB single-block decryption.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef AES_H
|
||||
#define AES_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Decrypt one 16-byte block with AES-128-ECB.
|
||||
*
|
||||
* @param in 16-byte ciphertext.
|
||||
* @param key 16-byte key.
|
||||
* @param out 16-byte plaintext output.
|
||||
* @return None.
|
||||
*/
|
||||
void aes128_ecb_decrypt_block(const uint8_t in[16], const uint8_t key[16], uint8_t out[16]);
|
||||
|
||||
#endif // AES_H
|
||||
@@ -0,0 +1,17 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// File: ctf_target.h
|
||||
// Desc: AES-128-ECB key and ciphertext for the real target waypoint.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef CTF_TARGET_H
|
||||
#define CTF_TARGET_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define CTF_AES_KEY { 0x56, 0x45, 0x43, 0x54, 0x4F, 0x52, 0x31, 0x31, 0x41, 0x45, 0x53, 0x4B, 0x45, 0x59, 0x21, 0x21 }
|
||||
#define CTF_TARGET_CT { 0x20, 0x4F, 0xAC, 0xC2, 0xC0, 0x23, 0xEA, 0x7A, 0x3A, 0x93, 0x0F, 0x71, 0x11, 0x23, 0xEF, 0xCA }
|
||||
|
||||
#endif // CTF_TARGET_H
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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: gps.h
|
||||
// Desc: Declares PIO UART GPS receiver interface and NMEA parsing logic.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef GPS_H
|
||||
#define GPS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "hardware/pio.h"
|
||||
|
||||
#define GPS_PIN 7
|
||||
#define GPS_BAUD 9600
|
||||
#define GPS_PIO pio0
|
||||
#define GPS_SM 0
|
||||
|
||||
/**
|
||||
* @brief Initialize PIO UART receiver on GPIO7 for u-blox NEO-6M GPS.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_gps_pio(void);
|
||||
|
||||
/**
|
||||
* @brief Poll PIO RX FIFO and parse incoming NMEA GPS coordinates.
|
||||
*
|
||||
* @param lat Pointer to double storing updated latitude.
|
||||
* @param lon Pointer to double storing updated longitude.
|
||||
* @return bool True if a valid active 3D GPS fix (RMC 'A') was received, false otherwise.
|
||||
*/
|
||||
bool poll_gps(double *lat, double *lon);
|
||||
|
||||
/**
|
||||
* @brief Read latest GNSS signal statistics parsed from GSV sentences.
|
||||
*
|
||||
* @param siv Pointer to store satellites-in-view count.
|
||||
* @param cno Pointer to store best carrier-to-noise ratio in dBHz.
|
||||
* @return None.
|
||||
*/
|
||||
void gps_get_stats(int *siv, int *cno);
|
||||
|
||||
#endif // GPS_H
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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: lcd.h
|
||||
// Desc: Declares I2C HD44780 (16x2) LCD interface for telemetry coordinates.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef LCD_H
|
||||
#define LCD_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
#define LCD_I2C_INST i2c1
|
||||
#define LCD_SDA_PIN 2
|
||||
#define LCD_SCL_PIN 3
|
||||
#define LCD_BAUD 100000
|
||||
|
||||
/**
|
||||
* @brief Initialize I2C0 peripheral and detect/configure 1602 LCD backpack.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_lcd(void);
|
||||
|
||||
/**
|
||||
* @brief Render current latitude and longitude on the 16x2 character display.
|
||||
*
|
||||
* Row 0: LAT: dd.dddddd N
|
||||
* Row 1: LON: dd.dddddd W
|
||||
*
|
||||
* @param lat Current latitude in decimal degrees.
|
||||
* @param lon Current longitude in decimal degrees.
|
||||
* @return None.
|
||||
*/
|
||||
void lcd_show_coords(double lat, double lon);
|
||||
|
||||
/**
|
||||
* @brief Render GNSS acquisition telemetry (satellites and C/N0) on the LCD.
|
||||
*
|
||||
* Row 0: SAT: nn CNO: nn
|
||||
* Row 1: ACQUIRING... when satellites are in view, else NO SIGNAL
|
||||
*
|
||||
* @param sats Number of satellites currently in view.
|
||||
* @param cno Best carrier-to-noise ratio in dBHz.
|
||||
* @return None.
|
||||
*/
|
||||
void lcd_show_gnss(int sats, int cno);
|
||||
|
||||
#endif // LCD_H
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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: lora.h
|
||||
// Desc: Declares UART1 interface for the REYAX RYLR998 LoRa transceiver.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef LORA_H
|
||||
#define LORA_H
|
||||
|
||||
#include "hardware/uart.h"
|
||||
|
||||
#define LORA_UART uart1
|
||||
#define LORA_BAUD 9600
|
||||
#define LORA_TX_PIN 8
|
||||
#define LORA_RX_PIN 9
|
||||
|
||||
/**
|
||||
* @brief Initialize LoRa transceiver over UART1 on GPIO8 and GPIO9
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_lora(void);
|
||||
|
||||
/**
|
||||
* @brief Transmit string message over LoRa UART1 interface
|
||||
*
|
||||
* @param msg Null-terminated string buffer to transmit.
|
||||
* @return None.
|
||||
*/
|
||||
void lora_send(const char *msg);
|
||||
|
||||
/**
|
||||
* @brief Service the LoRa UART: stream pending TX bytes and drain RX.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void lora_tick(void);
|
||||
|
||||
#endif // LORA_H
|
||||
@@ -0,0 +1,87 @@
|
||||
// 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: navigation.h
|
||||
// Desc: Declares autonomous guidance, dead-reckoning, and telemetry interface.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef NAVIGATION_H
|
||||
#define NAVIGATION_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/** @brief Pre-programmed programmed decoy waypoint (Centreville, VA). */
|
||||
extern const double ORIGIN_LAT;
|
||||
extern const double ORIGIN_LON;
|
||||
|
||||
/** @brief Target coordinates, reconstructed at boot from masked constants. */
|
||||
extern double TARGET_LAT;
|
||||
extern double TARGET_LON;
|
||||
|
||||
/**
|
||||
* @brief Reconstruct the target waypoint from its XOR-masked constants.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_navigation(void);
|
||||
|
||||
/**
|
||||
* @brief Transmit telemetry stream over Debug UART0 and LoRa UART1.
|
||||
*
|
||||
* @param cur_lat Current micro-UAV latitude.
|
||||
* @param cur_lon Current micro-UAV longitude.
|
||||
* @return None.
|
||||
*/
|
||||
void send_telemetry(double cur_lat, double cur_lon);
|
||||
|
||||
/**
|
||||
* @brief Advance dead-reckoning position toward programmed waypoint.
|
||||
*
|
||||
* @param cur_lat Pointer to current latitude.
|
||||
* @param cur_lon Pointer to current longitude.
|
||||
* @return None.
|
||||
*/
|
||||
void dead_reckon_step(double *cur_lat, double *cur_lon);
|
||||
|
||||
/**
|
||||
* @brief Verify if micro-UAV has arrived at target coordinates.
|
||||
*
|
||||
* @param cur_lat Current latitude coordinate.
|
||||
* @param cur_lon Current longitude coordinate.
|
||||
* @return true if arrived at target, false otherwise.
|
||||
*/
|
||||
bool check_arrival(double cur_lat, double cur_lon);
|
||||
|
||||
/**
|
||||
* @brief Manage guidance progression, propeller oscillation, and payload release.
|
||||
*
|
||||
* @param cur_lat Current latitude coordinate.
|
||||
* @param cur_lon Current longitude coordinate.
|
||||
* @return None.
|
||||
*/
|
||||
void navigate_to_target(double cur_lat, double cur_lon);
|
||||
|
||||
#endif // NAVIGATION_H
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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: payload.h
|
||||
// Desc: Declares payload release mechanism interface on GPIO16.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef PAYLOAD_H
|
||||
#define PAYLOAD_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define LED_RED_PIN 16
|
||||
#define LED_GREEN_PIN 17
|
||||
#define LED_YELLOW_PIN 18
|
||||
|
||||
/**
|
||||
* @brief Initialize GPIO16 (Red failure LED) and GPIO17 (Green success LED).
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_payload(void);
|
||||
|
||||
/**
|
||||
* @brief Update tri-color GNSS status LEDs from fix state and satellites.
|
||||
*
|
||||
* Red (GP16) = no satellites in view; Yellow (GP18) = satellites in view
|
||||
* while acquiring; Green (GP17) = active 3D fix.
|
||||
*
|
||||
* @param fix True if an active 3D GPS fix is held.
|
||||
* @param siv Number of satellites currently in view.
|
||||
* @return None.
|
||||
*/
|
||||
void set_gnss_leds(bool fix, int siv);
|
||||
|
||||
/**
|
||||
* @brief Energize payload latch and illuminate both LEDs at target coordinates.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void release_payload(void);
|
||||
|
||||
#endif // PAYLOAD_H
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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: propeller.h
|
||||
// Desc: Declares SG90 servo PWM mock propeller interface on GPIO6.
|
||||
// Created: 2026
|
||||
|
||||
#ifndef PROPELLER_H
|
||||
#define PROPELLER_H
|
||||
|
||||
#define PROPELLER_PIN 6
|
||||
|
||||
/**
|
||||
* @brief Initialize 50 Hz PWM on GPIO6 for SG90 mock propeller blade.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void init_propeller(void);
|
||||
|
||||
/**
|
||||
* @brief Advance mock propeller blade oscillation during flight.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void propeller_spin(void);
|
||||
|
||||
/**
|
||||
* @brief Halt mock propeller blade oscillation upon target arrival.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
void propeller_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Point the servo at the compass bearing toward the target.
|
||||
*
|
||||
* @param deg Bearing in degrees from the current position to the target.
|
||||
* @return None.
|
||||
*/
|
||||
void propeller_set_bearing(double deg);
|
||||
|
||||
#endif // PROPELLER_H
|
||||
@@ -0,0 +1,121 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
else ()
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
@@ -0,0 +1,411 @@
|
||||
#!/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: decode_coordinates.py
|
||||
# Desc: Decode and patch RP2350 micro-UAV navigation coordinates.
|
||||
# Created: 2026
|
||||
|
||||
"""
|
||||
Decode and patch RP2350 micro-UAV navigation coordinates.
|
||||
|
||||
Analyzes raw firmware images for IEEE 754 64-bit double-precision floating
|
||||
point coordinates. Scans target flight vectors and patches binaries with
|
||||
safe disposal coordinates in the Atlantic Ocean.
|
||||
"""
|
||||
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
FLASH_BASE = 0x10000000
|
||||
ORIGIN_LAT = 38.840280
|
||||
ORIGIN_LON = -77.428890
|
||||
ATLANTIC_LAT = 37.000000
|
||||
ATLANTIC_LON = -74.000000
|
||||
|
||||
|
||||
def _haversine_calc(phi1: float, phi2: float,
|
||||
dphi: float, dlam: float) -> float:
|
||||
"""
|
||||
Compute central angle using haversine formula.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
phi1 : float
|
||||
Origin latitude in radians.
|
||||
phi2 : float
|
||||
Target latitude in radians.
|
||||
dphi : float
|
||||
Latitude difference in radians.
|
||||
dlam : float
|
||||
Longitude difference in radians.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
Central angular distance in radians.
|
||||
"""
|
||||
s_phi = math.sin(dphi / 2.0) ** 2
|
||||
s_lam = math.sin(dlam / 2.0) ** 2
|
||||
a = s_phi + math.cos(phi1) * math.cos(phi2) * s_lam
|
||||
return 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a))
|
||||
|
||||
|
||||
def haversine(lat1: float, lon1: float,
|
||||
lat2: float, lon2: float) -> tuple[float, float]:
|
||||
"""
|
||||
Compute Great-Circle distance and azimuth bearing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lat1 : float
|
||||
Origin latitude in degrees.
|
||||
lon1 : float
|
||||
Origin longitude in degrees.
|
||||
lat2 : float
|
||||
Destination latitude in degrees.
|
||||
lon2 : float
|
||||
Destination longitude in degrees.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float]
|
||||
Distance in statute miles and bearing in degrees.
|
||||
"""
|
||||
p1, p2 = math.radians(lat1), math.radians(lat2)
|
||||
dl = math.radians(lon2 - lon1)
|
||||
dist = 6371.0 * _haversine_calc(p1, p2, math.radians(lat2 - lat1), dl)
|
||||
y = math.sin(dl) * math.cos(p2)
|
||||
term = math.sin(p1) * math.cos(p2) * math.cos(dl)
|
||||
x = math.cos(p1) * math.sin(p2) - term
|
||||
bearing = (math.degrees(math.atan2(y, x)) + 360.0) % 360.0
|
||||
return dist * 0.621371, bearing
|
||||
|
||||
|
||||
def _is_coord(val: float) -> bool:
|
||||
"""
|
||||
Verify if float falls within target geographic bounds.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
val : float
|
||||
Candidate double-precision value.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if value is a valid latitude or longitude.
|
||||
"""
|
||||
if math.isnan(val) or math.isinf(val):
|
||||
return False
|
||||
in_lat = 35.0 <= val <= 41.0
|
||||
in_lon = -79.0 <= val <= -72.0
|
||||
return in_lat or in_lon
|
||||
|
||||
|
||||
def _parse_chunk(data: bytes, off: int) -> dict | None:
|
||||
"""
|
||||
Extract and validate one 8-byte candidate float.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : bytes
|
||||
Firmware image buffer.
|
||||
off : int
|
||||
Byte offset inside image buffer.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict | None
|
||||
Parsed coordinate record or None.
|
||||
"""
|
||||
chunk = data[off:off + 8]
|
||||
val = struct.unpack("<d", chunk)[0]
|
||||
if not _is_coord(val):
|
||||
return None
|
||||
hex_str = " ".join(f"{b:02x}" for b in chunk)
|
||||
u64 = struct.unpack("<Q", chunk)[0]
|
||||
kind = "LATITUDE" if val > 0.0 else "LONGITUDE"
|
||||
return {"off": off, "addr": FLASH_BASE + off, "val": val,
|
||||
"hex": hex_str, "u64": u64, "kind": kind}
|
||||
|
||||
|
||||
def scan_coordinates(data: bytes) -> list[dict]:
|
||||
"""
|
||||
Scan firmware buffer for double-precision coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : bytes
|
||||
Firmware image buffer.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
List of candidate coordinate records.
|
||||
"""
|
||||
found = []
|
||||
limit = len(data) - 8
|
||||
for off in range(0, limit, 4):
|
||||
item = _parse_chunk(data, off)
|
||||
if item is not None:
|
||||
found.append(item)
|
||||
return found
|
||||
|
||||
|
||||
def _print_banner(path: Path) -> None:
|
||||
"""
|
||||
Print operation heading and recovery origin.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : pathlib.Path
|
||||
Target firmware path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
print("=" * 67)
|
||||
print(" OPERATION DARK VECTOR // FORENSIC COORDINATE TOOL")
|
||||
print(" GMU Rapid Hardware Exploitation Laboratory - Fairfax, VA")
|
||||
print("=" * 67)
|
||||
print(f"[*] Target Binary: {path.name} ({path.stat().st_size:,} bytes)")
|
||||
print(f"[*] Recovery Origin: Centreville, VA "
|
||||
f"({ORIGIN_LAT:.6f}, {ORIGIN_LON:.6f})")
|
||||
print("-" * 67)
|
||||
|
||||
|
||||
def _print_candidate(c: dict) -> None:
|
||||
"""
|
||||
Print formatted candidate coordinate entry.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
c : dict
|
||||
Candidate coordinate record.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
print(f" [{c['kind']:9s}] Value: {c['val']:12.6f} | "
|
||||
f"Addr: 0x{c['addr']:08x} (Offset: 0x{c['off']:04x})")
|
||||
print(f" Hex: {c['hex']} | uint64: 0x{c['u64']:016x}")
|
||||
|
||||
|
||||
def _cardinal_bearing(brg: float) -> str:
|
||||
"""Return compass direction string for given bearing."""
|
||||
dirs = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
|
||||
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]
|
||||
idx = int((brg + 11.25) / 22.5) % 16
|
||||
return dirs[idx]
|
||||
|
||||
|
||||
def _print_summary(lat: float, lon: float, mi: float, brg: float) -> None:
|
||||
"""
|
||||
Print mission tactical assessment summary.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lat : float
|
||||
Decoded target latitude.
|
||||
lon : float
|
||||
Decoded target longitude.
|
||||
mi : float
|
||||
Distance in statute miles.
|
||||
brg : float
|
||||
Initial bearing in degrees.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
card = _cardinal_bearing(brg)
|
||||
print("\n" + "=" * 67)
|
||||
print(" TACTICAL MISSION PROFILE DECODED")
|
||||
print("=" * 67)
|
||||
print(f" Target Latitude: {lat:.6f} deg N")
|
||||
print(f" Target Longitude: {lon:.6f} deg W")
|
||||
print(f" Distance from Origin: {mi:.2f} miles ({mi * 1.60934:.2f} km)")
|
||||
print(f" Flight Vector Bearing: {brg:.1f} deg ({card})")
|
||||
print("=" * 67)
|
||||
|
||||
|
||||
def _patch_bytes(data: bytes, old_lat: float, old_lon: float) -> bytes:
|
||||
"""
|
||||
Replace target coordinates with safe Atlantic Ocean coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : bytes
|
||||
Original firmware bytes.
|
||||
old_lat : float
|
||||
Original target latitude.
|
||||
old_lon : float
|
||||
Original target longitude.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Patched firmware byte buffer.
|
||||
"""
|
||||
src_lat = struct.pack("<d", old_lat)
|
||||
src_lon = struct.pack("<d", old_lon)
|
||||
dst_lat = struct.pack("<d", ATLANTIC_LAT)
|
||||
dst_lon = struct.pack("<d", ATLANTIC_LON)
|
||||
buf = data.replace(src_lat, dst_lat)
|
||||
return buf.replace(src_lon, dst_lon)
|
||||
|
||||
|
||||
def _print_patch_info(out_name: str, mi: float, brg: float) -> None:
|
||||
"""
|
||||
Display confirmation of applied firmware patch.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
out_name : str
|
||||
Patched output file name.
|
||||
mi : float
|
||||
Distance to safe disposal zone.
|
||||
brg : float
|
||||
Azimuth bearing to disposal zone.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
print(f"\n[+] Patched firmware written to: {out_name}")
|
||||
print("[+] Overwrote waypoint -> Atlantic Ocean Disposal Zone:")
|
||||
print(f" Safe Latitude: {ATLANTIC_LAT:.6f} deg N")
|
||||
print(f" Safe Longitude: {ATLANTIC_LON:.6f} deg W")
|
||||
print(f" Offshore Distance: {mi:.2f} miles (Bearing: {brg:.1f} deg)")
|
||||
|
||||
|
||||
def patch_firmware(target: Path, out_path: Path,
|
||||
lat: float, lon: float) -> None:
|
||||
"""
|
||||
Patch firmware with safe Atlantic Ocean disposal waypoint.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : pathlib.Path
|
||||
Source binary path.
|
||||
out_path : pathlib.Path
|
||||
Destination patched binary path.
|
||||
lat : float
|
||||
Current target latitude.
|
||||
lon : float
|
||||
Current target longitude.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
raw = target.read_bytes()
|
||||
patched = _patch_bytes(raw, lat, lon)
|
||||
out_path.write_bytes(patched)
|
||||
mi, brg = haversine(ORIGIN_LAT, ORIGIN_LON, ATLANTIC_LAT, ATLANTIC_LON)
|
||||
_print_patch_info(out_path.name, mi, brg)
|
||||
|
||||
|
||||
def _evaluate(items: list[dict], target: Path, do_patch: bool) -> None:
|
||||
"""
|
||||
Display results and execute patch if requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
items : list[dict]
|
||||
Found coordinate records.
|
||||
target : pathlib.Path
|
||||
Target binary path.
|
||||
do_patch : bool
|
||||
Flag indicating if patch should be applied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
lats = [c for c in items if c["kind"] == "LATITUDE"]
|
||||
lons = [c for c in items if c["kind"] == "LONGITUDE"]
|
||||
if not (lats and lons):
|
||||
return
|
||||
t_lat, t_lon = lats[-1]["val"], lons[-1]["val"]
|
||||
mi, brg = haversine(ORIGIN_LAT, ORIGIN_LON, t_lat, t_lon)
|
||||
_print_summary(t_lat, t_lon, mi, brg)
|
||||
if do_patch:
|
||||
out = target.parent / f"{target.stem}_patched.bin"
|
||||
patch_firmware(target, out, t_lat, t_lon)
|
||||
|
||||
|
||||
def _parse_args(args: list[str]) -> tuple[Path, bool]:
|
||||
"""
|
||||
Parse command line arguments for target path and patch flag.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : list[str]
|
||||
Command line arguments.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[pathlib.Path, bool]
|
||||
Target binary path and patch flag.
|
||||
"""
|
||||
do_patch = "--patch" in args
|
||||
paths = [p for p in args if not p.startswith("--")]
|
||||
target = Path(paths[0]) if paths else Path("0x0011a_cb.bin")
|
||||
return target, do_patch
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""
|
||||
Execute firmware coordinate extraction and optional patching.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Zero on success, non-zero on failure.
|
||||
"""
|
||||
target, do_patch = _parse_args(sys.argv[1:])
|
||||
if not target.exists():
|
||||
print(f"[-] Error: '{target}' not found.")
|
||||
return 1
|
||||
_print_banner(target)
|
||||
items = scan_coordinates(target.read_bytes())
|
||||
for item in items:
|
||||
_print_candidate(item)
|
||||
_evaluate(items, target, do_patch)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/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: float_hex_converter.py
|
||||
# Desc: Convert and explain IEEE 754 float/hex operations step-by-step.
|
||||
# Created: 2026
|
||||
|
||||
"""Convert and explain IEEE 754 float/hex operations step-by-step."""
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def _exp_str(e_val: int, bias: int, is_64: bool) -> str:
|
||||
"""
|
||||
Get accurate true exponent string accounting for IEEE 754 edge cases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
e_val : int
|
||||
The stored exponent value.
|
||||
bias : int
|
||||
The exponent bias (127 or 1023).
|
||||
is_64 : bool
|
||||
True if 64-bit precision.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The formatted true exponent explanation string.
|
||||
"""
|
||||
if e_val == 0:
|
||||
return f"0 (Zero/Subnormal, True Exp: {1 - bias})"
|
||||
if e_val == (2047 if is_64 else 255):
|
||||
return f"{e_val} (Inf/NaN flag)"
|
||||
return f"{e_val} - {bias} = {e_val - bias}"
|
||||
|
||||
|
||||
def _print_encode_steps(val: float, b: int) -> None:
|
||||
"""
|
||||
Print the math steps for encoding a float to hex.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
val : float
|
||||
The float value to encode.
|
||||
b : int
|
||||
The bit size (32 or 64).
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
f1, f2, bias = ("<Q", "<d", 1023) if b == 64 else ("<I", "<f", 127)
|
||||
bstr = f"{struct.unpack(f1, struct.pack(f2, val))[0]:0{b}b}"
|
||||
s, e, m = (
|
||||
bstr[0],
|
||||
bstr[1 : 1 + (11 if b == 64 else 8)],
|
||||
bstr[1 + (11 if b == 64 else 8) :],
|
||||
)
|
||||
hex_val = f"0x{int(bstr, 2):0{b//4}X}"
|
||||
print(f"\n[ENCODE {val} to {b}-bit]\n1. Sign: {s} (0=Pos, 1=Neg)")
|
||||
print(f"2. Exp: {_exp_str(int(e, 2), bias, b == 64)}\n3. Mantissa: {m}")
|
||||
print(f"4. Full: {s} {e} {m}\n5. Hex: {hex_val}")
|
||||
if b == 64:
|
||||
raw_hex = f"{int(bstr, 2):016X}"
|
||||
r3 = f"0x{raw_hex[:8]}"
|
||||
r2 = f"0x{raw_hex[8:]}"
|
||||
print(f"6. ARM Regs: r3 (high) = {r3}, r2 (low) = {r2} (e.g. in printf)")
|
||||
else:
|
||||
print(f"6. ARM Reg: Single 32-bit register ({hex_val})")
|
||||
|
||||
|
||||
def _print_decode_steps(hex_str: str) -> None:
|
||||
"""
|
||||
Print the math steps for decoding a hex string to float.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hex_str : str
|
||||
The hex string to decode.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
c = hex_str.lower()
|
||||
if c.startswith("0x"):
|
||||
c = c[2:]
|
||||
b, f1, f2, bias = (64, "<Q", "<d", 1023) if len(c) > 8 else (32, "<I", "<f", 127)
|
||||
bstr = f"{int(c, 16):0{b}b}"
|
||||
s, e, m = (
|
||||
bstr[0],
|
||||
bstr[1 : 1 + (11 if b == 64 else 8)],
|
||||
bstr[1 + (11 if b == 64 else 8) :],
|
||||
)
|
||||
print(f"\n[DECODE 0x{c.zfill(b//4).upper()} ({b}-bit)]\n1. Binary: {s} {e} {m}")
|
||||
print(f"2. Sign: {s}\n3. Exp: {_exp_str(int(e, 2), bias, b == 64)}")
|
||||
print(f"4. Value: {struct.unpack(f2, struct.pack(f1, int(c, 16)))[0]}")
|
||||
if b == 64:
|
||||
raw_hex = c.zfill(16).upper()
|
||||
r3 = f"0x{raw_hex[:8]}"
|
||||
r2 = f"0x{raw_hex[8:]}"
|
||||
print(f"5. ARM Regs: r3 (high) = {r3}, r2 (low) = {r2} (e.g. in printf)")
|
||||
else:
|
||||
print(f"5. ARM Reg: Single 32-bit register (0x{c.zfill(8).upper()})")
|
||||
|
||||
|
||||
def _is_hex(s: str) -> bool:
|
||||
"""
|
||||
Check if a string is a hexadecimal representation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : str
|
||||
Candidate string, with or without a 0x prefix.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if every character is a hexadecimal digit.
|
||||
"""
|
||||
cleaned = s.lower()
|
||||
if cleaned.startswith("0x"):
|
||||
cleaned = cleaned[2:]
|
||||
if not cleaned:
|
||||
return False
|
||||
return all(c in "0123456789abcdef" for c in cleaned)
|
||||
|
||||
|
||||
def _process_conversion(val_str: str) -> None:
|
||||
"""
|
||||
Execute the conversion and print the output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
val_str : str
|
||||
The raw input string to process.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
cleaned = val_str.strip()
|
||||
if cleaned.lower().startswith("0x") or (len(cleaned) >= 8 and _is_hex(cleaned)):
|
||||
_print_decode_steps(cleaned)
|
||||
else:
|
||||
val = float(cleaned)
|
||||
_print_encode_steps(val, 32)
|
||||
_print_encode_steps(val, 64)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""
|
||||
Execute the conversion pipeline based on CLI arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Zero on successful conversion, otherwise non-zero.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Float/Hex step converter.")
|
||||
parser.add_argument("val", help="Hex (0x...) or Float value to convert.")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
_process_conversion(args.val)
|
||||
return 0
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/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: lora_console.py
|
||||
# Desc: Interactive REYAX RYLR998 terminal for the FT232RL ground station.
|
||||
# Created: 2026
|
||||
|
||||
"""
|
||||
Interactive LoRa terminal for the REYAX RYLR998 ground station.
|
||||
|
||||
Sends AT commands as complete bursts terminated with a carriage return and line
|
||||
feed to avoid the RYLR998 inter-character timeout error, and prints incoming
|
||||
packets from the airborne node as they arrive.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
try:
|
||||
import serial
|
||||
except ImportError:
|
||||
serial = None
|
||||
|
||||
|
||||
def _reader_thread(ser: "serial.Serial") -> None:
|
||||
"""
|
||||
Continuously read and display incoming packets from the radio.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ser : serial.Serial
|
||||
Open serial connection to the ground RYLR998.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
line = ser.readline().decode("utf-8", errors="ignore").strip()
|
||||
if line:
|
||||
print(f"\n[LORA RX] {line}\n> ", end="", flush=True)
|
||||
except Exception:
|
||||
break
|
||||
|
||||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
"""
|
||||
Parse command line arguments for the LoRa console.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
argparse.Namespace
|
||||
Parsed arguments with the serial port and baud rate.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="REYAX RYLR998 console")
|
||||
parser.add_argument("--port", default="/dev/cu.usbserial-A50285BI")
|
||||
parser.add_argument("--baud", type=int, default=115200)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Open the ground station radio and forward operator input.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
if serial is None:
|
||||
sys.exit("pyserial required: pip install pyserial")
|
||||
args = _parse_args()
|
||||
|
||||
print(f"[*] Opening REYAX RYLR998 on {args.port} @ {args.baud}...")
|
||||
try:
|
||||
ser = serial.Serial(args.port, args.baud, timeout=0.5)
|
||||
except Exception as e:
|
||||
sys.exit(f"[-] Failed to open {args.port}: {e}")
|
||||
|
||||
thread = threading.Thread(target=_reader_thread, args=(ser,), daemon=True)
|
||||
thread.start()
|
||||
|
||||
time.sleep(0.1)
|
||||
ser.write(b"AT\r\n")
|
||||
|
||||
print("[+] Connected. Type AT commands (e.g. AT, AT+BAND?, AT+NETWORKID?).")
|
||||
print("[+] Incoming airborne packets print as [LORA RX] +RCV=...")
|
||||
print("[+] Press Ctrl-C or Ctrl-D to exit.\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
cmd = input("> ").strip()
|
||||
if not cmd:
|
||||
continue
|
||||
ser.write(cmd.encode("utf-8") + b"\r\n")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n[*] Exiting LoRa console.")
|
||||
ser.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/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: randomize_build.py
|
||||
# Desc: Builds a per-student 0x0011a_cb image with an AES-encrypted target.
|
||||
# Created: 2026
|
||||
|
||||
"""
|
||||
Per-student randomized CTF build.
|
||||
|
||||
Jitters the target waypoint, AES-128-ECB encrypts it under a per-build key, and
|
||||
writes include/ctf_target.h so the firmware rebuilds it at boot. Every image
|
||||
carries a different ciphertext and the plaintext target exists nowhere in flash.
|
||||
An offline answer key or a memorized value is useless; the waypoint can only be
|
||||
recovered from the artifact and confirmed on hardware.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import random
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
BASE_LAT = 38.881940
|
||||
BASE_LON = -77.450280
|
||||
JITTER_DEG = 0.01
|
||||
UF2_FAMILY = "0xe48bff59"
|
||||
FLASH_BASE = "0x10000000"
|
||||
|
||||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
"""
|
||||
Parse command line arguments for the randomized build.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
argparse.Namespace
|
||||
Parsed arguments with seed, build dir, student id, and uf2 flag.
|
||||
"""
|
||||
here = pathlib.Path(__file__).resolve().parent
|
||||
parser = argparse.ArgumentParser(description="Randomized CTF build")
|
||||
parser.add_argument("--seed", type=int, default=None)
|
||||
parser.add_argument("--build-dir", default=str(here.parent / "build-ctf"))
|
||||
parser.add_argument("--student-id", default="student")
|
||||
parser.add_argument("--uf2", action="store_true")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def _make_target(seed: int) -> tuple[float, float]:
|
||||
"""
|
||||
Generate a jittered target waypoint around the base coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
seed : int
|
||||
Deterministic seed for the jitter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float]
|
||||
Jittered latitude and longitude, rounded to six decimals.
|
||||
"""
|
||||
rng = random.Random(seed)
|
||||
lat = round(BASE_LAT + rng.uniform(-JITTER_DEG, JITTER_DEG), 6)
|
||||
lon = round(BASE_LON + rng.uniform(-JITTER_DEG, JITTER_DEG), 6)
|
||||
return lat, lon
|
||||
|
||||
|
||||
def _make_key(seed: int) -> bytes:
|
||||
"""
|
||||
Derive a deterministic 16-byte AES key for the build.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
seed : int
|
||||
Build seed from which the key is derived.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Sixteen byte AES-128 key.
|
||||
"""
|
||||
krng = random.Random(seed ^ 0x5EED)
|
||||
return bytes(krng.randrange(256) for _ in range(16))
|
||||
|
||||
|
||||
def _aes_ecb(plain: bytes, key: bytes) -> bytes:
|
||||
"""
|
||||
Encrypt one block with AES-128-ECB.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plain : bytes
|
||||
Sixteen byte plaintext block.
|
||||
key : bytes
|
||||
Sixteen byte AES key.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Sixteen byte ciphertext block.
|
||||
"""
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
enc = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend()).encryptor()
|
||||
return enc.update(plain) + enc.finalize()
|
||||
|
||||
|
||||
def _write_header(path: pathlib.Path, key: bytes, ct: bytes) -> None:
|
||||
"""
|
||||
Write the AES key and ciphertext header consumed by the firmware.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : pathlib.Path
|
||||
Destination header path.
|
||||
key : bytes
|
||||
Sixteen byte AES key.
|
||||
ct : bytes
|
||||
Sixteen byte ciphertext block.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
path.write_text(
|
||||
"#ifndef CTF_TARGET_H\n#define CTF_TARGET_H\n\n#include <stdint.h>\n\n"
|
||||
"#define CTF_AES_KEY { %s }\n"
|
||||
"#define CTF_TARGET_CT { %s }\n\n"
|
||||
"#endif // CTF_TARGET_H\n"
|
||||
% (", ".join(f"0x{b:02X}" for b in key), ", ".join(f"0x{b:02X}" for b in ct)))
|
||||
|
||||
|
||||
def _run(cmd: list[str], cwd: pathlib.Path) -> None:
|
||||
"""
|
||||
Run an external command and raise on failure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cmd : list[str]
|
||||
Command and arguments to execute.
|
||||
cwd : pathlib.Path
|
||||
Working directory for the command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
subprocess.run(cmd, cwd=str(cwd), check=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""
|
||||
Build a per-student image with an AES-encrypted, randomized target.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Zero on success.
|
||||
"""
|
||||
args = _parse_args()
|
||||
root = pathlib.Path(__file__).resolve().parent.parent
|
||||
seed = args.seed if args.seed is not None else random.randrange(2**31)
|
||||
lat, lon = _make_target(seed)
|
||||
key = _make_key(seed)
|
||||
pt = struct.pack("<d", lat) + struct.pack("<d", lon)
|
||||
ct = _aes_ecb(pt, key)
|
||||
|
||||
_write_header(root / "include" / "ctf_target.h", key, ct)
|
||||
|
||||
build = pathlib.Path(args.build_dir).resolve()
|
||||
_run(["cmake", "-S", str(root), "-B", str(build)], root)
|
||||
_run(["cmake", "--build", str(build)], root)
|
||||
|
||||
image = build / "0x0011a_cb.bin"
|
||||
if args.uf2:
|
||||
out = build / f"0x0011a_cb_{args.student_id}.uf2"
|
||||
_run([sys.executable, str(root / "uf2conv.py"), str(image),
|
||||
"-f", UF2_FAMILY, "-b", FLASH_BASE, "-c", "-o", str(out)], root)
|
||||
|
||||
key_out = {"student_id": args.student_id, "seed": seed,
|
||||
"target_lat": lat, "target_lon": lon,
|
||||
"aes_key_hex": key.hex(), "ciphertext_hex": ct.hex(),
|
||||
"image": str(image)}
|
||||
keydir = root / "scratch"
|
||||
keydir.mkdir(exist_ok=True)
|
||||
keyfile = keydir / f"answer_{args.student_id}.json"
|
||||
keyfile.write_text(json.dumps(key_out, indent=2) + "\n")
|
||||
|
||||
print(f"[+] student_id : {args.student_id}")
|
||||
print(f"[+] TARGET_LAT : {lat}")
|
||||
print(f"[+] TARGET_LON : {lon}")
|
||||
print(f"[+] AES key : {key.hex()}")
|
||||
print(f"[+] ciphertext : {ct.hex()}")
|
||||
print(f"[+] image : {image}")
|
||||
print(f"[+] answer key : {keyfile} (INSTRUCTOR ONLY, do not ship)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,302 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,119 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// File: aes.c
|
||||
// Desc: Minimal AES-128-ECB block decryption for the CTF waypoint.
|
||||
// Created: 2026
|
||||
|
||||
#include "aes.h"
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t sbox[256] = {
|
||||
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
||||
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
||||
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
||||
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
||||
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
||||
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
||||
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
||||
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
||||
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
||||
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
||||
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
||||
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
||||
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
|
||||
};
|
||||
|
||||
static const uint8_t rsbox[256] = {
|
||||
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
||||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
||||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
||||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
||||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
||||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
||||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
||||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
||||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
||||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
||||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
||||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
||||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
||||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
||||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
|
||||
};
|
||||
|
||||
static const uint8_t Rcon[11] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36};
|
||||
|
||||
static uint8_t xtime(uint8_t x) { return (uint8_t)((x << 1) ^ (((x >> 7) & 1) * 0x1B)); }
|
||||
|
||||
static uint8_t mul(uint8_t a, uint8_t b) {
|
||||
uint8_t p = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (b & 1) p ^= a;
|
||||
uint8_t hi = a & 0x80; a <<= 1;
|
||||
if (hi) a ^= 0x1B;
|
||||
b >>= 1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void key_expansion(const uint8_t *key, uint8_t *rk) {
|
||||
memcpy(rk, key, 16);
|
||||
for (int i = 4; i < 44; i++) {
|
||||
uint8_t t[4];
|
||||
memcpy(t, &rk[(i - 1) * 4], 4);
|
||||
if (i % 4 == 0) {
|
||||
uint8_t tmp = t[0];
|
||||
t[0] = (uint8_t)(sbox[t[1]] ^ Rcon[i / 4]);
|
||||
t[1] = sbox[t[2]];
|
||||
t[2] = sbox[t[3]];
|
||||
t[3] = sbox[tmp];
|
||||
}
|
||||
for (int j = 0; j < 4; j++) rk[i * 4 + j] = rk[(i - 4) * 4 + j] ^ t[j];
|
||||
}
|
||||
}
|
||||
|
||||
static void add_round_key(uint8_t r, uint8_t *s, const uint8_t *rk) {
|
||||
for (int i = 0; i < 16; i++) s[i] ^= rk[r * 16 + i];
|
||||
}
|
||||
|
||||
static void inv_sub_bytes(uint8_t *s) { for (int i = 0; i < 16; i++) s[i] = rsbox[s[i]]; }
|
||||
|
||||
static void inv_shift_rows(uint8_t *s) {
|
||||
uint8_t t;
|
||||
t = s[13]; s[13] = s[9]; s[9] = s[5]; s[5] = s[1]; s[1] = t;
|
||||
t = s[2]; s[2] = s[10]; s[10] = t; t = s[6]; s[6] = s[14]; s[14] = t;
|
||||
t = s[3]; s[3] = s[7]; s[7] = s[11]; s[11] = s[15]; s[15] = t;
|
||||
}
|
||||
|
||||
static void inv_mix_columns(uint8_t *s) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint8_t a = s[i * 4], b = s[i * 4 + 1], c = s[i * 4 + 2], d = s[i * 4 + 3];
|
||||
s[i * 4] = (uint8_t)(mul(a, 14) ^ mul(b, 11) ^ mul(c, 13) ^ mul(d, 9));
|
||||
s[i * 4 + 1] = (uint8_t)(mul(a, 9) ^ mul(b, 14) ^ mul(c, 11) ^ mul(d, 13));
|
||||
s[i * 4 + 2] = (uint8_t)(mul(a, 13) ^ mul(b, 9) ^ mul(c, 14) ^ mul(d, 11));
|
||||
s[i * 4 + 3] = (uint8_t)(mul(a, 11) ^ mul(b, 13) ^ mul(c, 9) ^ mul(d, 14));
|
||||
}
|
||||
}
|
||||
|
||||
void aes128_ecb_decrypt_block(const uint8_t in[16], const uint8_t key[16], uint8_t out[16]) {
|
||||
uint8_t rk[176];
|
||||
key_expansion(key, rk);
|
||||
memcpy(out, in, 16);
|
||||
add_round_key(10, out, rk);
|
||||
for (int r = 9; r > 0; r--) {
|
||||
inv_shift_rows(out);
|
||||
inv_sub_bytes(out);
|
||||
add_round_key(r, out, rk);
|
||||
inv_mix_columns(out);
|
||||
}
|
||||
inv_shift_rows(out);
|
||||
inv_sub_bytes(out);
|
||||
add_round_key(0, out, rk);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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: gps.c
|
||||
// Desc: Implements PIO UART GPS receiver and NMEA coordinate parsing.
|
||||
// Created: 2026
|
||||
|
||||
#include "gps.h"
|
||||
#include "uart_rx.pio.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int gps_siv = 0;
|
||||
static int gps_cno = 0;
|
||||
|
||||
void init_gps_pio(void)
|
||||
{
|
||||
uint offset = pio_add_program(GPS_PIO, &uart_rx_program);
|
||||
uart_rx_program_init(GPS_PIO, GPS_SM, offset, GPS_PIN, GPS_BAUD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert NMEA ddmm.mmmm coordinate to decimal degrees.
|
||||
*
|
||||
* @param str NMEA coordinate string.
|
||||
* @param dir Cardinal direction character ('N', 'S', 'E', 'W').
|
||||
* @return double Decimal degree coordinate.
|
||||
*/
|
||||
static double parse_nmea_coord(const char *str, char dir)
|
||||
{
|
||||
double raw = atof(str);
|
||||
int deg = (int)(raw / 100.0);
|
||||
double dec = (double)deg + ((raw - (deg * 100.0)) / 60.0);
|
||||
return ((dir == 'S') || (dir == 'W')) ? -dec : dec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find pointer to n-th comma-separated field in NMEA string.
|
||||
*
|
||||
* @param str NMEA sentence string.
|
||||
* @param field_idx Index of field to locate.
|
||||
* @return const char* Pointer to field start or NULL.
|
||||
*/
|
||||
static const char *get_nmea_field(const char *str, int field_idx)
|
||||
{
|
||||
while ((str != NULL) && (*str != '\0') && (field_idx > 0)) {
|
||||
if (*str++ == ',') {
|
||||
field_idx--;
|
||||
}
|
||||
}
|
||||
return (field_idx == 0) ? str : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse NMEA RMC sentence for valid coordinates.
|
||||
*
|
||||
* @param line NMEA sentence buffer.
|
||||
* @param lat Pointer to store parsed latitude.
|
||||
* @param lon Pointer to store parsed longitude.
|
||||
* @return None.
|
||||
*/
|
||||
static bool parse_rmc(const char *line, double *lat, double *lon)
|
||||
{
|
||||
const char *st = get_nmea_field(line, 2), *la = get_nmea_field(line, 3);
|
||||
const char *lo = get_nmea_field(line, 5);
|
||||
if (!st || *st != 'A' || !la || *la == ',' || !lo || *lo == ',') return false;
|
||||
*lat = parse_nmea_coord(la, *get_nmea_field(line, 4));
|
||||
*lon = parse_nmea_coord(lo, *get_nmea_field(line, 6));
|
||||
return (*lat != 0.0) && (*lon != 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse NMEA GSV sentence for satellites-in-view and best C/N0.
|
||||
*
|
||||
* @param line NMEA GSV sentence string.
|
||||
* @return None.
|
||||
*/
|
||||
static void parse_gsv(const char *line)
|
||||
{
|
||||
const char *sv = get_nmea_field(line, 3), *msg = get_nmea_field(line, 2);
|
||||
if (sv != NULL) gps_siv = atoi(sv);
|
||||
if ((msg != NULL) && (*msg == '1')) gps_cno = 0;
|
||||
for (int f = 7; f < 40; f += 4) {
|
||||
const char *c = get_nmea_field(line, f);
|
||||
if ((c == NULL) || (*c == '*') || (*c == '\0')) break;
|
||||
if (atoi(c) > gps_cno) gps_cno = atoi(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test buffered line for valid NMEA RMC sentence.
|
||||
*
|
||||
* @param buf NMEA character buffer.
|
||||
* @param idx Pointer to character index.
|
||||
* @param lat Pointer to current latitude.
|
||||
* @param lon Pointer to current longitude.
|
||||
* @return bool True if valid 3D fix was parsed, false otherwise.
|
||||
*/
|
||||
static bool check_rmc_line(char *buf, int *idx, double *lat, double *lon)
|
||||
{
|
||||
buf[*idx] = '\0';
|
||||
*idx = 0;
|
||||
char *rmc = strstr(buf, "RMC"), *gsv = strstr(buf, "GSV");
|
||||
if (gsv != NULL) parse_gsv(gsv);
|
||||
return (rmc != NULL) ? parse_rmc(rmc, lat, lon) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Accumulate GPS character and trigger RMC parsing on newline.
|
||||
*
|
||||
* @param ch Received ASCII character.
|
||||
* @param lat Pointer to current latitude.
|
||||
* @param lon Pointer to current longitude.
|
||||
* @return bool True if a valid active 3D fix was parsed, false otherwise.
|
||||
*/
|
||||
static bool process_gps_char(char ch, double *lat, double *lon)
|
||||
{
|
||||
static char buf[96];
|
||||
static int idx = 0;
|
||||
if (ch == '$') idx = 0;
|
||||
if ((ch == '\n') || (ch == '\r'))
|
||||
return check_rmc_line(buf, &idx, lat, lon);
|
||||
if (idx < (int)(sizeof(buf) - 1))
|
||||
buf[idx++] = ch;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_gps_byte(double *lat, double *lon)
|
||||
{
|
||||
char ch = (char)(pio_sm_get(GPS_PIO, GPS_SM) >> 24);
|
||||
return process_gps_char(ch, lat, lon);
|
||||
}
|
||||
|
||||
bool poll_gps(double *lat, double *lon)
|
||||
{
|
||||
bool got_fix = false;
|
||||
while (!pio_sm_is_rx_fifo_empty(GPS_PIO, GPS_SM))
|
||||
got_fix |= handle_gps_byte(lat, lon);
|
||||
return got_fix;
|
||||
}
|
||||
|
||||
void gps_get_stats(int *siv, int *cno)
|
||||
{
|
||||
*siv = gps_siv;
|
||||
*cno = gps_cno;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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: lcd.c
|
||||
// Desc: Implements I2C HD44780 16x2 LCD display for live telemetry coordinates.
|
||||
// Created: 2026
|
||||
|
||||
#include "lcd.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#define PIN_RS 0x01
|
||||
#define PIN_EN 0x04
|
||||
#define BACKLIGHT 0x08
|
||||
|
||||
static uint8_t lcd_addr = 0x27;
|
||||
static bool lcd_ready = false;
|
||||
|
||||
static void pcf_write(uint8_t d)
|
||||
{
|
||||
if (lcd_ready)
|
||||
i2c_write_blocking(LCD_I2C_INST, lcd_addr, &d, 1, false);
|
||||
}
|
||||
|
||||
static void pcf_pulse(uint8_t d)
|
||||
{
|
||||
pcf_write(d | PIN_EN);
|
||||
sleep_us(1);
|
||||
pcf_write(d & ~PIN_EN);
|
||||
sleep_us(50);
|
||||
}
|
||||
|
||||
static void lcd_write4(uint8_t n, uint8_t mode)
|
||||
{
|
||||
uint8_t d = (n & 0x0F) << 4;
|
||||
d |= mode ? PIN_RS : 0;
|
||||
d |= BACKLIGHT;
|
||||
pcf_pulse(d);
|
||||
}
|
||||
|
||||
static void lcd_send(uint8_t v, uint8_t mode)
|
||||
{
|
||||
lcd_write4((v >> 4) & 0x0F, mode);
|
||||
lcd_write4(v & 0x0F, mode);
|
||||
}
|
||||
|
||||
static void lcd_clear(void)
|
||||
{
|
||||
lcd_send(0x01, 0);
|
||||
sleep_ms(2);
|
||||
}
|
||||
|
||||
static void lcd_set_cursor(int row, int col)
|
||||
{
|
||||
uint8_t offset = (row == 0) ? 0x00 : 0x40;
|
||||
lcd_send(0x80 | (col + offset), 0);
|
||||
}
|
||||
|
||||
static void lcd_puts(const char *s)
|
||||
{
|
||||
while (*s)
|
||||
lcd_send((uint8_t)*s++, 1);
|
||||
}
|
||||
|
||||
static void lcd_reset_seq(void)
|
||||
{
|
||||
lcd_write4(0x03, 0); sleep_ms(5);
|
||||
lcd_write4(0x03, 0); sleep_us(150);
|
||||
lcd_write4(0x03, 0); sleep_us(150);
|
||||
lcd_write4(0x02, 0); sleep_us(150);
|
||||
}
|
||||
|
||||
static void lcd_cfg_seq(void)
|
||||
{
|
||||
lcd_send(0x28, 0);
|
||||
lcd_send(0x0C, 0);
|
||||
lcd_clear();
|
||||
lcd_send(0x06, 0);
|
||||
}
|
||||
|
||||
static bool detect_lcd(void)
|
||||
{
|
||||
uint8_t rx;
|
||||
if (i2c_read_blocking(LCD_I2C_INST, 0x27, &rx, 1, false) >= 0)
|
||||
return (lcd_addr = 0x27, true);
|
||||
if (i2c_read_blocking(LCD_I2C_INST, 0x3F, &rx, 1, false) >= 0)
|
||||
return (lcd_addr = 0x3F, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
void init_lcd(void)
|
||||
{
|
||||
i2c_init(LCD_I2C_INST, LCD_BAUD);
|
||||
gpio_set_function(LCD_SDA_PIN, GPIO_FUNC_I2C);
|
||||
gpio_set_function(LCD_SCL_PIN, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(LCD_SDA_PIN); gpio_pull_up(LCD_SCL_PIN);
|
||||
if (!(lcd_ready = detect_lcd())) return;
|
||||
lcd_reset_seq();
|
||||
lcd_cfg_seq();
|
||||
}
|
||||
|
||||
void lcd_show_coords(double lat, double lon)
|
||||
{
|
||||
if (!lcd_ready && !(lcd_ready = detect_lcd())) return;
|
||||
char r1[17], r2[17];
|
||||
snprintf(r1, sizeof(r1), "LAT: %9.6f %c", fabs(lat), (lat >= 0.0) ? 'N' : 'S');
|
||||
snprintf(r2, sizeof(r2), "LON: %9.6f %c", fabs(lon), (lon >= 0.0) ? 'E' : 'W');
|
||||
lcd_set_cursor(0, 0); lcd_puts(r1);
|
||||
lcd_set_cursor(1, 0); lcd_puts(r2);
|
||||
}
|
||||
|
||||
void lcd_show_gnss(int sats, int cno)
|
||||
{
|
||||
if (!lcd_ready && !(lcd_ready = detect_lcd())) return;
|
||||
char r1[17], r2[17];
|
||||
snprintf(r1, sizeof(r1), "SAT:%2d CNO:%2d ", sats, cno);
|
||||
snprintf(r2, sizeof(r2), "%-16s", (sats > 0) ? "ACQUIRING..." : "NO SIGNAL");
|
||||
lcd_set_cursor(0, 0); lcd_puts(r1);
|
||||
lcd_set_cursor(1, 0); lcd_puts(r2);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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: lora.c
|
||||
// Desc: Implements UART1 driver for REYAX RYLR998 LoRa transceiver.
|
||||
// Created: 2026
|
||||
|
||||
#include "lora.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void drain_lora_rx(void)
|
||||
{
|
||||
while (uart_is_readable(LORA_UART)) {
|
||||
char c = (char)uart_getc(LORA_UART);
|
||||
if (c >= 32 && c <= 126)
|
||||
putchar(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_at_cmd(const char *cmd)
|
||||
{
|
||||
uart_write_blocking(LORA_UART, (const uint8_t *)cmd, strlen(cmd));
|
||||
sleep_ms(250);
|
||||
drain_lora_rx();
|
||||
}
|
||||
|
||||
static void configure_lora_rf(void)
|
||||
{
|
||||
sleep_ms(1500);
|
||||
send_at_cmd("AT\r\n");
|
||||
send_at_cmd("AT+NETWORKID=18\r\n");
|
||||
send_at_cmd("AT+BAND=915000000\r\n");
|
||||
send_at_cmd("AT+PARAMETER=9,7,1,12\r\n");
|
||||
send_at_cmd("AT+ADDRESS=2\r\n");
|
||||
}
|
||||
|
||||
void init_lora(void)
|
||||
{
|
||||
uart_init(LORA_UART, LORA_BAUD);
|
||||
uart_set_translate_crlf(LORA_UART, false);
|
||||
gpio_set_function(LORA_TX_PIN, GPIO_FUNC_UART);
|
||||
gpio_set_function(LORA_RX_PIN, GPIO_FUNC_UART);
|
||||
configure_lora_rf();
|
||||
}
|
||||
|
||||
static char tx_buf[160];
|
||||
static int tx_len = 0;
|
||||
static int tx_idx = 0;
|
||||
|
||||
void lora_send(const char *msg)
|
||||
{
|
||||
int len = (int)strlen(msg);
|
||||
while ((len > 0) && ((msg[len - 1] == '\r') || (msg[len - 1] == '\n')))
|
||||
len--;
|
||||
tx_len = snprintf(tx_buf, sizeof(tx_buf), "AT+SEND=0,%d,%.*s\r\n", len, len, msg);
|
||||
tx_idx = 0;
|
||||
}
|
||||
|
||||
void lora_tick(void)
|
||||
{
|
||||
while ((tx_idx < tx_len) && uart_is_writable(LORA_UART))
|
||||
uart_putc_raw(LORA_UART, (uint8_t)tx_buf[tx_idx++]);
|
||||
drain_lora_rx();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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: main.c
|
||||
// Desc: Main entry point for autonomous micro-UAV guidance firmware.
|
||||
// Created: 2026
|
||||
|
||||
#include "gps.h"
|
||||
#include "lora.h"
|
||||
#include "payload.h"
|
||||
#include "propeller.h"
|
||||
#include "navigation.h"
|
||||
#include "lcd.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/pio.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize all board peripherals, communications, and actuators.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void init_all(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
init_navigation();
|
||||
init_payload();
|
||||
init_lora();
|
||||
init_gps_pio();
|
||||
init_propeller();
|
||||
init_lcd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Continually drain GPS PIO FIFO over 1-second flight tick.
|
||||
*
|
||||
* @param cur_lat Pointer to current latitude.
|
||||
* @param cur_lon Pointer to current longitude.
|
||||
* @return bool True if active 3D lock was parsed, false otherwise.
|
||||
*/
|
||||
static bool update_position(double *cur_lat, double *cur_lon)
|
||||
{
|
||||
bool got_fix = false;
|
||||
for (int i = 0; i < 200; i++, sleep_ms(5)) {
|
||||
got_fix |= poll_gps(cur_lat, cur_lon);
|
||||
lora_tick();
|
||||
}
|
||||
return got_fix;
|
||||
}
|
||||
|
||||
static void step_mission(double *cur_lat, double *cur_lon)
|
||||
{
|
||||
int siv = 0, cno = 0;
|
||||
static int hold = 0;
|
||||
bool fix = update_position(cur_lat, cur_lon);
|
||||
gps_get_stats(&siv, &cno);
|
||||
hold = fix ? 3 : ((hold > 0) ? (hold - 1) : 0);
|
||||
bool have = fix || (hold > 0);
|
||||
set_gnss_leds(have, siv);
|
||||
if (have) {
|
||||
lcd_show_coords(*cur_lat, *cur_lon);
|
||||
navigate_to_target(*cur_lat, *cur_lon);
|
||||
} else {
|
||||
lcd_show_gnss(siv, cno);
|
||||
propeller_stop();
|
||||
send_telemetry(*cur_lat, *cur_lon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Autonomous micro-UAV firmware execution loop.
|
||||
*
|
||||
* @param None.
|
||||
* @return int Standard exit code (never reached in embedded firmware).
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
double cur_lat = ORIGIN_LAT, cur_lon = ORIGIN_LON;
|
||||
init_all();
|
||||
while (true)
|
||||
step_mission(&cur_lat, &cur_lon);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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: navigation.c
|
||||
// Desc: Implements waypoint navigation, dead-reckoning, and telemetry dispatch.
|
||||
// Created: 2026
|
||||
|
||||
#include "navigation.h"
|
||||
#include "ctf_target.h"
|
||||
#include "aes.h"
|
||||
#include "lora.h"
|
||||
#include "payload.h"
|
||||
#include "propeller.h"
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
double TARGET_LAT = 0.0;
|
||||
double TARGET_LON = 0.0;
|
||||
|
||||
const double ORIGIN_LAT = 38.840280;
|
||||
const double ORIGIN_LON = -77.428890;
|
||||
|
||||
void init_navigation(void)
|
||||
{
|
||||
uint8_t pt[16];
|
||||
const uint8_t key[16] = CTF_AES_KEY;
|
||||
const uint8_t ct[16] = CTF_TARGET_CT;
|
||||
aes128_ecb_decrypt_block(ct, key, pt);
|
||||
memcpy(&TARGET_LAT, pt, sizeof(double));
|
||||
memcpy(&TARGET_LON, pt + 8, sizeof(double));
|
||||
}
|
||||
|
||||
void send_telemetry(double cur_lat, double cur_lon)
|
||||
{
|
||||
char msg[80];
|
||||
snprintf(msg, sizeof(msg), "CURRENT LAT: %lf, LON: %lf\r\n", cur_lat, cur_lon);
|
||||
printf("%s", msg);
|
||||
lora_send(msg);
|
||||
}
|
||||
|
||||
void dead_reckon_step(double *cur_lat, double *cur_lon)
|
||||
{
|
||||
double dlat = TARGET_LAT - *cur_lat;
|
||||
double dlon = TARGET_LON - *cur_lon;
|
||||
*cur_lat += (fabs(dlat) < 0.005) ? dlat : ((dlat > 0.0) ? 0.004166 : -0.004166);
|
||||
*cur_lon += (fabs(dlon) < 0.005) ? dlon : ((dlon > 0.0) ? 0.002139 : -0.002139);
|
||||
}
|
||||
|
||||
bool check_arrival(double cur_lat, double cur_lon)
|
||||
{
|
||||
return (cur_lat == TARGET_LAT) && (cur_lon == TARGET_LON);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the initial bearing from one point toward another.
|
||||
*
|
||||
* @param lat1 Origin latitude in degrees.
|
||||
* @param lon1 Origin longitude in degrees.
|
||||
* @param lat2 Destination latitude in degrees.
|
||||
* @param lon2 Destination longitude in degrees.
|
||||
* @return double Bearing in degrees (0 to 360).
|
||||
*/
|
||||
static double bearing_to(double lat1, double lon1, double lat2, double lon2)
|
||||
{
|
||||
double p1 = lat1 * 0.017453292519943295, p2 = lat2 * 0.017453292519943295;
|
||||
double dl = (lon2 - lon1) * 0.017453292519943295;
|
||||
double y = sin(dl) * cos(p2);
|
||||
double x = cos(p1) * sin(p2) - sin(p1) * cos(p2) * cos(dl);
|
||||
double b = atan2(y, x) * 57.29577951308232;
|
||||
return (b < 0.0) ? (b + 360.0) : b;
|
||||
}
|
||||
|
||||
void navigate_to_target(double cur_lat, double cur_lon)
|
||||
{
|
||||
send_telemetry(cur_lat, cur_lon);
|
||||
if (check_arrival(cur_lat, cur_lon)) {
|
||||
propeller_stop();
|
||||
release_payload();
|
||||
} else {
|
||||
propeller_set_bearing(bearing_to(cur_lat, cur_lon, TARGET_LAT, TARGET_LON));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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: payload.c
|
||||
// Desc: Implements payload release solenoid latch control on GPIO16.
|
||||
// Created: 2026
|
||||
|
||||
#include "payload.h"
|
||||
#include "lora.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void init_payload(void)
|
||||
{
|
||||
gpio_init(16); gpio_set_dir(16, GPIO_OUT); gpio_put(16, 1);
|
||||
gpio_init(17); gpio_set_dir(17, GPIO_OUT); gpio_put(17, 0);
|
||||
gpio_init(18); gpio_set_dir(18, GPIO_OUT); gpio_put(18, 0);
|
||||
gpio_init(25); gpio_set_dir(25, GPIO_OUT); gpio_put(25, 0);
|
||||
}
|
||||
|
||||
void set_gnss_leds(bool fix, int siv)
|
||||
{
|
||||
gpio_put(16, (!fix && (siv == 0)) ? 1 : 0);
|
||||
gpio_put(17, fix ? 1 : 0);
|
||||
gpio_put(18, (!fix && (siv > 0)) ? 1 : 0);
|
||||
}
|
||||
|
||||
void release_payload(void)
|
||||
{
|
||||
gpio_put(16, 1);
|
||||
gpio_put(17, 1);
|
||||
gpio_put(18, 1);
|
||||
printf("PAYLOAD RELEASED AT TARGET COORDINATES\r\n");
|
||||
lora_send("PAYLOAD RELEASED AT TARGET COORDINATES\r\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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: propeller.c
|
||||
// Desc: Implements SG90 servo PWM mock propeller control on GPIO6.
|
||||
// Created: 2026
|
||||
|
||||
#include "propeller.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/pwm.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
static struct repeating_timer prop_timer;
|
||||
static bool prop_active = false;
|
||||
static uint16_t current_pulse = 1000;
|
||||
|
||||
void init_propeller(void)
|
||||
{
|
||||
gpio_set_function(PROPELLER_PIN, GPIO_FUNC_PWM);
|
||||
uint s = pwm_gpio_to_slice_num(PROPELLER_PIN);
|
||||
pwm_set_clkdiv(s, 150.0f);
|
||||
pwm_set_wrap(s, 19999);
|
||||
pwm_set_gpio_level(PROPELLER_PIN, 0);
|
||||
pwm_set_enabled(s, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Repeating timer callback to alternate servo angle at max slew rate.
|
||||
*
|
||||
* @param t Pointer to repeating timer structure.
|
||||
* @return bool Always true to continue recurring timer.
|
||||
*/
|
||||
static bool prop_timer_callback(struct repeating_timer *t)
|
||||
{
|
||||
(void)t;
|
||||
current_pulse = (current_pulse == 1000) ? 2000 : 1000;
|
||||
pwm_set_gpio_level(PROPELLER_PIN, current_pulse);
|
||||
return true;
|
||||
}
|
||||
|
||||
void propeller_spin(void)
|
||||
{
|
||||
if (!prop_active) {
|
||||
prop_active = true;
|
||||
add_repeating_timer_ms(-150, prop_timer_callback, NULL, &prop_timer);
|
||||
}
|
||||
}
|
||||
|
||||
void propeller_set_bearing(double deg)
|
||||
{
|
||||
uint16_t pulse = (uint16_t)(1000.0 + (deg / 360.0) * 1000.0);
|
||||
pwm_set_gpio_level(PROPELLER_PIN, pulse);
|
||||
}
|
||||
|
||||
void propeller_stop(void)
|
||||
{
|
||||
if (prop_active) {
|
||||
cancel_repeating_timer(&prop_timer);
|
||||
prop_active = false;
|
||||
}
|
||||
pwm_set_gpio_level(PROPELLER_PIN, 0);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
;
|
||||
; Copyright (c) 2026 Kevin Thomas
|
||||
; SPDX-License-Identifier: MIT
|
||||
;
|
||||
.pio_version 0
|
||||
|
||||
.program uart_rx
|
||||
|
||||
; 8n1 UART receiver for GPS NMEA reception on a single GPIO pin.
|
||||
; Operates at 8 execution cycles per bit period.
|
||||
; IN pin 0 and JMP pin are mapped to the GPS RX GPIO pin.
|
||||
|
||||
start:
|
||||
wait 0 pin 0 ; Wait for start bit falling edge (1 cycle)
|
||||
set x, 7 [10] ; Preload bit counter (7 remaining), delay 1.5 bit periods (11 cycles)
|
||||
bitloop:
|
||||
in pins, 1 ; Sample 1 bit from RX pin into ISR (1 cycle)
|
||||
jmp x-- bitloop [6] ; Loop 8 times; each iteration is 8 execution cycles (7 cycles delay)
|
||||
jmp pin good_stop ; Verify stop bit is HIGH
|
||||
wait 1 pin 0 ; Framing error: wait until line returns to idle HIGH
|
||||
jmp start ; Discard frame and re-synchronize
|
||||
good_stop:
|
||||
push noblock ; Push 8-bit byte into RX FIFO (bits [31:24])
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
static inline void uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
|
||||
pio_gpio_init(pio, pin);
|
||||
gpio_pull_up(pin);
|
||||
pio_sm_config c = uart_rx_program_get_default_config(offset);
|
||||
sm_config_set_in_pins(&c, pin);
|
||||
sm_config_set_jmp_pin(&c, pin);
|
||||
sm_config_set_in_shift(&c, true, false, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
|
||||
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"short_name": "RP2040",
|
||||
"id": "0xe48bff56",
|
||||
"description": "Raspberry Pi RP2040"
|
||||
},
|
||||
{
|
||||
"short_name": "RP2350-ARM-S",
|
||||
"id": "0xe48bff59",
|
||||
"description": "Raspberry Pi RP2350, ARM, Secure"
|
||||
},
|
||||
{
|
||||
"short_name": "RP2350-ARM-NS",
|
||||
"id": "0xe48bff5a",
|
||||
"description": "Raspberry Pi RP2350, ARM, Non-Secure"
|
||||
},
|
||||
{
|
||||
"short_name": "RP2350-RISCV",
|
||||
"id": "0xe48bff5b",
|
||||
"description": "Raspberry Pi RP2350, RISC-V"
|
||||
}
|
||||
]
|
||||
Reference in new issue
Block a user