├── .gitignore ├── README.md ├── RPI ├── install_rpi.py └── rpi_dependancies.py ├── boards.png ├── eye.png ├── eyepurple.svg ├── firmware ├── esp32c3-wifi-rid-node-mode.bin ├── esp32c3-wifi-rid.bin ├── esp32s3-dual-rid-apple-maps.bin ├── esp32s3-dual-rid-node-mode.bin └── esp32s3-dual-rid.bin ├── mapper_test └── mapper_test.py ├── mesh-mapper.py ├── node-mode-dualcore ├── .gitignore ├── firmware.bin ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── main.cpp │ ├── odid_wifi.h │ ├── opendroneid.c │ ├── opendroneid.h │ └── wifi.c └── test │ └── README ├── remoteid-mesh-dualcore ├── .gitignore ├── firmware.bin ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── main.cpp │ ├── odid_wifi.h │ ├── opendroneid.c │ ├── opendroneid.h │ └── wifi.c └── test │ └── README ├── remoteid-mesh ├── .gitignore ├── firmware.bin ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── main.cpp │ ├── odid_wifi.h │ ├── opendroneid.c │ ├── opendroneid.h │ └── wifi.c └── test │ └── README └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.DS_Store 3 | 4 | *.pio/build/ 5 | 6 | *.vscode/ 7 | 8 | node-mode-dualcore/.pio/build/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #
**Drone Remote ID Mapper**
2 | 3 |
4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | [![Python](https://img.shields.io/badge/Python-3.7+-blue.svg)](https://www.python.org/) 7 | [![ESP32](https://img.shields.io/badge/ESP32-Compatible-green.svg)](https://www.espressif.com/) 8 | [![Flask](https://img.shields.io/badge/Flask-2.0+-red.svg)](https://flask.palletsprojects.com/) 9 | 10 | **Real-time drone detection, mapping, and Remote ID compliance monitoring** 11 | 12 | [🚀 Quick Start](#-quick-start) • [📋 Features](#-features) • [🛠️ API Reference](#-api-reference) • [🔧 Hardware](#-hardware-setup) 13 | 14 | Drone Detection Eye 15 | 16 |
17 | 18 | --- 19 | 20 | ## 🛠️ **Hardware Options** 21 | 22 | ### **🎯 Ready-to-Use Solution** 23 | Pre-built detection hardware designed specifically for this project: 24 | 25 | 26 | I sell on Tindie 27 | 28 | 29 | **✅ Complete kits with all components included** 30 | **✅ Pre-flashed firmware ready to use** 31 | 32 | 33 | **🔋 Completely Standalone Operation** 34 | - **No Raspberry Pi Required**: Boards operate independently for mesh detection 35 | - **No Computer Needed**: Self-contained drone detection and mesh communication 36 | - **Instant Setup**: Just power on and start detecting 37 | 38 | **📊 Optional Mapper Integration** 39 | - **Standalone mesh detection** works great on its own 40 | - **Add the mapper software** for enhanced visualization and logging 41 | - **Best of both worlds**: Mesh detection + centralized monitoring 42 | 43 | ### **🔧 DIY Build Option** 44 | 45 | Build your own detection system using readily available components: 46 | 47 | **Required Components:** 48 | - **Xiao ESP32-S3** (dual-core with WiFi + Bluetooth) 49 | - **Heltec WiFi LoRa 32 V3** (for mesh networking) 50 | - Basic wiring connections 51 | 52 | **Perfect for:** 53 | - Learning and experimentation 54 | - Custom modifications 55 | - Budget-conscious builds 56 | - Educational projects 57 | 58 | --- 59 | 60 | ## 🎯 **Overview** 61 | 62 | Advanced drone detection system that captures and maps Remote ID broadcasts from drones using ESP32 hardware. Features real-time web interface, persistent tracking across sessions, and comprehensive data export capabilities. 63 | 64 | --- 65 | 66 | ## ⚡ **Quick Start** 67 | 68 | ### 🔧 **Automated Setup** (Recommended) 69 | 70 | Download and install everything automatically using the official RPI setup scripts: 71 | 72 | ```bash 73 | # Download the RPI setup script 74 | wget https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/main/RPI/install_rpi.py 75 | 76 | # Install from main branch (stable) 77 | python3 install_rpi.py --branch main 78 | 79 | # Or install from Dev branch (latest features) 80 | python3 install_rpi.py --branch Dev 81 | ``` 82 | 83 | **Advanced Setup Options:** 84 | ```bash 85 | # Custom installation directory 86 | python3 install_rpi.py --branch main --install-dir /opt/mesh-mapper 87 | 88 | # Skip auto-start cron job 89 | python3 install_rpi.py --branch main --no-cron 90 | 91 | # Force overwrite existing installation 92 | python3 install_rpi.py --branch Dev --force 93 | ``` 94 | 95 | ### 📦 **Dependency Installation** 96 | 97 | Install all required dependencies automatically: 98 | 99 | ```bash 100 | # Download and run the universal dependency installer 101 | wget https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/main/RPI/rpi_dependancies.py 102 | python3 rpi_dependancies.py 103 | ``` 104 | 105 | This installer handles: 106 | - ✅ **System Detection**: Automatically detects Linux, macOS, Windows 107 | - ✅ **Package Manager Support**: apt, yum, dnf, pacman, brew, pkg 108 | - ✅ **Python & pip**: Ensures compatible Python 3.7+ and pip installation 109 | - ✅ **Core Dependencies**: Flask, Flask-SocketIO, pyserial, requests, urllib3 110 | - ✅ **Optional Packages**: Performance and development tools 111 | 112 | ### 📖 **Manual Setup** 113 | 114 | 1. **Download mapper** 115 | ```bash 116 | wget https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/main/mesh-mapper.py 117 | ``` 118 | 119 | 2. **Install dependencies** 120 | ```bash 121 | pip3 install Flask Flask-SocketIO pyserial requests urllib3 python-socketio eventlet 122 | ``` 123 | 124 | 3. **Flash ESP32 firmware** 125 | - Choose appropriate firmware from `firmware/` directory 126 | - Use Arduino IDE, PlatformIO, or esptool.py 127 | - Configure WiFi channel and mesh settings 128 | 129 | 4. **Run Mapper** 130 | ```bash 131 | python3 mesh-mapper.py 132 | ``` 133 | 134 | --- 135 | 136 | ## 📋 **Core Features** 137 | 138 | ### 🗺️ **Real-time Mapping** 139 | - **Live Detection Display**: Interactive map showing drone positions as they're detected 140 | - **Flight Path Tracking**: Visual trails showing drone and pilot movement over time 141 | - **Persistent Sessions**: Drones remain visible across application restarts 142 | - **Multi-device Support**: Handle multiple ESP32 receivers simultaneously 143 | 144 | ### 📊 **Data Management** 145 | - **Detection History**: Complete log of all drone encounters with timestamps 146 | - **Device Aliases**: Assign friendly names to frequently seen drones 147 | - **Export Formats**: Download data as CSV, KML (Google Earth), or GeoJSON 148 | - **Cumulative Logging**: Long-term historical data storage 149 | 150 | ### 🔧 **ESP32 Integration** 151 | - **Auto-detection**: Automatically finds and connects to ESP32 devices 152 | - **Port Management**: Save and restore USB port configurations 153 | - **Status Monitoring**: Real-time connection health and data flow indicators 154 | - **Command Interface**: Send diagnostic commands to connected hardware 155 | 156 | ### 🌐 **Web Interface** 157 | - **Real-time Updates**: WebSocket-powered live data streaming 158 | - **Mobile Responsive**: Works on desktop, tablet, and mobile devices 159 | - **Multiple Views**: Map, detection list, and device status panels 160 | - **Data Export**: Download detections directly from web interface 161 | 162 | ### ⚙️ **Configuration & Monitoring** 163 | - **Headless Operation**: Run without web interface for dedicated deployments 164 | - **Debug Logging**: Detailed logging for troubleshooting and development 165 | - **FAA Integration**: Automatic Remote ID registration lookups 166 | - **Webhook Support**: External system integration via HTTP callbacks 167 | 168 | --- 169 | 170 | ## 🚀 **Usage** 171 | 172 | ### **Command Line Options** 173 | 174 | ```bash 175 | python3 mesh-mapper.py [OPTIONS] 176 | ``` 177 | 178 | | Option | Description | Default | 179 | |--------|-------------|---------| 180 | | `--headless` | Run without web interface | false | 181 | | `--debug` | Enable debug logging | false | 182 | | `--web-port PORT` | Web interface port | 5000 | 183 | | `--port-interval SECONDS` | Port monitoring interval | 10 | 184 | | `--no-auto-start` | Disable automatic port connection | false | 185 | 186 | ### **Examples** 187 | 188 | ```bash 189 | # Standard operation with web interface 190 | python3 mesh-mapper.py 191 | 192 | # Headless operation for dedicated server 193 | python3 mesh-mapper.py --headless --debug 194 | 195 | # Custom web port with verbose logging 196 | python3 mesh-mapper.py --web-port 8080 --debug 197 | 198 | # Disable auto-connection to saved ports 199 | python3 mesh-mapper.py --no-auto-start 200 | ``` 201 | 202 | --- 203 | 204 | 205 | 206 | ## 🛠️ **API Reference** 207 | 208 | ### **Core Endpoints** 209 | 210 | | Method | Endpoint | Description | 211 | |--------|----------|-------------| 212 | | `GET` | `/` | Main web interface | 213 | | `GET` | `/api/detections` | Current active drone detections | 214 | | `POST` | `/api/detections` | Submit new detection data | 215 | | `GET` | `/api/detections_history` | Historical detection data (GeoJSON) | 216 | | `GET` | `/api/paths` | Flight path data for visualization | 217 | | `POST` | `/api/reactivate/` | Reactivate inactive drone detection | 218 | 219 | ### **Device Management** 220 | 221 | | Method | Endpoint | Description | 222 | |--------|----------|-------------| 223 | | `GET` | `/api/aliases` | Get device aliases | 224 | | `POST` | `/api/set_alias` | Set friendly name for device | 225 | | `POST` | `/api/clear_alias/` | Remove device alias | 226 | | `GET` | `/api/ports` | Available serial ports | 227 | | `GET` | `/api/serial_status` | ESP32 connection status | 228 | | `GET` | `/api/selected_ports` | Currently configured ports | 229 | 230 | ### **FAA & External Integration** 231 | 232 | | Method | Endpoint | Description | 233 | |--------|----------|-------------| 234 | | `GET` | `/api/faa/` | FAA registration lookup | 235 | | `POST` | `/api/query_faa` | Manual FAA query | 236 | | `POST` | `/api/set_webhook_url` | Configure webhook endpoint | 237 | | `GET` | `/api/get_webhook_url` | Get current webhook URL | 238 | | `POST` | `/api/webhook_popup` | Webhook notification handler | 239 | 240 | ### **Data Export** 241 | 242 | | Method | Endpoint | Description | 243 | |--------|----------|-------------| 244 | | `GET` | `/download/csv` | Download current detections (CSV) | 245 | | `GET` | `/download/kml` | Download current detections (KML) | 246 | | `GET` | `/download/aliases` | Download device aliases | 247 | | `GET` | `/download/cumulative_detections.csv` | Download full history (CSV) | 248 | | `GET` | `/download/cumulative.kml` | Download full history (KML) | 249 | 250 | ### **System Management** 251 | 252 | | Method | Endpoint | Description | 253 | |--------|----------|-------------| 254 | | `GET` | `/api/diagnostics` | System health and performance | 255 | | `POST` | `/api/debug_mode` | Toggle debug logging | 256 | | `POST` | `/api/send_command` | Send command to ESP32 devices | 257 | | `GET` | `/select_ports` | Port selection interface | 258 | | `POST` | `/select_ports` | Update port configuration | 259 | 260 | ### **WebSocket Events** 261 | 262 | Real-time events pushed to connected clients: 263 | 264 | - `detections` - Updated drone detection data 265 | - `paths` - Updated flight path data 266 | - `serial_status` - ESP32 connection status changes 267 | - `aliases` - Device alias updates 268 | - `cumulative_log` - Historical data updates 269 | - `faa_cache` - FAA lookup results 270 | 271 | --- 272 | 273 | ## 🔧 **Hardware Setup** 274 | 275 | ### **Supported ESP32 Boards** 276 | - ✅ **Xiao ESP32-C3** (Single core, WiFi only) 277 | - ✅ **Xiao ESP32-S3** (Dual core, WiFi + Bluetooth) 278 | - ✅ **ESP32-DevKit** (Development and testing) 279 | - ✅ **Custom PCBs** (See Tindie store link below) 280 | 281 | ### **Wiring for Mesh Integration** 282 | ``` 283 | ESP32 Pin | Mesh Radio Pin 284 | ----------|--------------- 285 | TX1 (d4) | RX 19 286 | RX1 (d5) | TX 20 287 | 3.3V | VCC 288 | GND | GND 289 | ``` 290 | 291 | --- 292 | 293 | ## 📊 **Performance** 294 | 295 | | Metric | Performance | 296 | |--------|-------------| 297 | | **Detection Latency** | < 500ms average | 298 | | **Concurrent Drones** | 50+ simultaneous | 299 | | **Memory Usage** | < 100MB typical | 300 | | **Storage Efficiency** | ~1KB per detection | 301 | | **Network Throughput** | 1000+ detections/min | 302 | 303 | --- 304 | 305 | ## 🐛 **Troubleshooting** 306 | 307 | ### **Common Issues** 308 | 309 | **ESP32 Not Detected** 310 | ```bash 311 | # Check USB connection 312 | ls -la /dev/tty* | grep USB 313 | 314 | # Verify driver installation 315 | dmesg | grep tty 316 | ``` 317 | 318 | **Web Interface Not Loading** 319 | ```bash 320 | # Check if service is running 321 | netstat -tlnp | grep :5000 322 | 323 | # Review logs 324 | tail -f mesh-mapper.log 325 | ``` 326 | 327 | **No Drone Detections** 328 | - Verify ESP32 firmware is properly flashed 329 | - Check WiFi channel configuration (default: channel 6) 330 | - Ensure drones are transmitting Remote ID (required in many jurisdictions) 331 | 332 | --- 333 | 334 | ## 📄 **License** 335 | 336 | This project is licensed under the MIT License 337 | 338 | --- 339 | 340 | ## 🙏 **Acknowledgments** 341 | 342 | - **Cemaxacutor** 343 | - **Luke Switzer** 344 | - **OpenDroneID Community** - Standards and specifications 345 | - Thank you PCBway for the awesome boards! The combination of their top tier quality, competitive pricing, fast turnaround times, and stellar customer service makes PCBWay the go-to choice for professional PCB fabrication, whether you're prototyping innovative mesh detection systems or scaling up for full production runs. 346 | https://www.pcbway.com/ 347 |
boards 348 | 349 | 350 | --- 351 | 352 | ## 🛒 **Hardware Store** 353 | 354 | Get professional PCBs and complete kits: 355 | 356 | 357 | I sell on Tindie 358 | 359 | 360 | --- 361 | 362 |
363 | 364 | **⭐ If this project helped you, please give it a star! ⭐** 365 | 366 | Made with ❤️ by the Drone Detection Community 367 | 368 |
369 | -------------------------------------------------------------------------------- /RPI/install_rpi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Mesh-Mapper Setup Script 4 | Downloads mesh-mapper.py from colonelpanichacks/drone-mesh-mapper and sets up auto-start cron job. 5 | 6 | Usage: 7 | python3 setup_mesh_mapper.py --branch main 8 | python3 setup_mesh_mapper.py --branch dev 9 | python3 setup_mesh_mapper.py --branch Dev --install-dir /custom/path 10 | """ 11 | 12 | import os 13 | import sys 14 | import argparse 15 | import subprocess 16 | import requests 17 | import getpass 18 | from pathlib import Path 19 | from urllib.parse import urlparse 20 | 21 | # GitHub repository configuration 22 | GITHUB_REPO = "colonelpanichacks/drone-mesh-mapper" 23 | GITHUB_BASE_URL = f"https://raw.githubusercontent.com/{GITHUB_REPO}" 24 | TARGET_FILE = "mesh-mapper.py" 25 | 26 | def get_current_user(): 27 | """Get the current username""" 28 | return getpass.getuser() 29 | 30 | def get_user_home(): 31 | """Get the current user's home directory""" 32 | return str(Path.home()) 33 | 34 | def construct_download_url(branch): 35 | """Construct the raw GitHub URL for downloading the file""" 36 | return f"{GITHUB_BASE_URL}/{branch}/{TARGET_FILE}" 37 | 38 | def download_file(url, destination): 39 | """Download a file from URL to destination""" 40 | print(f"📥 Downloading from: {url}") 41 | print(f"📁 Saving to: {destination}") 42 | 43 | try: 44 | response = requests.get(url, stream=True, timeout=30) 45 | response.raise_for_status() 46 | 47 | # Create directory if it doesn't exist 48 | os.makedirs(os.path.dirname(destination), exist_ok=True) 49 | 50 | with open(destination, 'wb') as f: 51 | for chunk in response.iter_content(chunk_size=8192): 52 | if chunk: 53 | f.write(chunk) 54 | 55 | # Make the file executable 56 | os.chmod(destination, 0o755) 57 | 58 | print(f"✅ Downloaded successfully: {destination}") 59 | return True 60 | 61 | except requests.exceptions.RequestException as e: 62 | print(f"❌ Error downloading file: {e}") 63 | return False 64 | except Exception as e: 65 | print(f"❌ Error saving file: {e}") 66 | return False 67 | 68 | def check_file_exists(url): 69 | """Check if file exists at the URL""" 70 | try: 71 | response = requests.head(url, timeout=10) 72 | return response.status_code == 200 73 | except: 74 | return False 75 | 76 | def get_current_crontab(): 77 | """Get current user's crontab""" 78 | try: 79 | result = subprocess.run(['crontab', '-l'], 80 | capture_output=True, text=True, check=False) 81 | if result.returncode == 0: 82 | return result.stdout.strip() 83 | else: 84 | return "" # No crontab exists 85 | except Exception as e: 86 | print(f"⚠️ Error reading crontab: {e}") 87 | return "" 88 | 89 | def install_cron_job(install_dir): 90 | """Install the cron job for auto-start""" 91 | current_user = get_current_user() 92 | user_home = get_user_home() 93 | 94 | # Construct the cron job command 95 | cron_command = f"@reboot sleep 5 && cd {install_dir} && /usr/bin/python3 {TARGET_FILE} --debug" 96 | 97 | print(f"🔧 Setting up cron job for user: {current_user}") 98 | print(f"📍 Install directory: {install_dir}") 99 | print(f"⚙️ Cron command: {cron_command}") 100 | 101 | # Get current crontab 102 | current_crontab = get_current_crontab() 103 | 104 | # Check if our cron job already exists 105 | if f"cd {install_dir} && /usr/bin/python3 {TARGET_FILE}" in current_crontab: 106 | print("ℹ️ Cron job already exists, updating...") 107 | # Remove old entries 108 | lines = current_crontab.split('\n') 109 | filtered_lines = [line for line in lines 110 | if not (f"cd {install_dir}" in line and TARGET_FILE in line)] 111 | current_crontab = '\n'.join(filtered_lines).strip() 112 | 113 | # Add our cron job 114 | if current_crontab: 115 | new_crontab = current_crontab + '\n' + cron_command 116 | else: 117 | new_crontab = cron_command 118 | 119 | try: 120 | # Install the new crontab 121 | process = subprocess.Popen(['crontab', '-'], 122 | stdin=subprocess.PIPE, 123 | stdout=subprocess.PIPE, 124 | stderr=subprocess.PIPE, 125 | text=True) 126 | stdout, stderr = process.communicate(new_crontab) 127 | 128 | if process.returncode == 0: 129 | print("✅ Cron job installed successfully!") 130 | print("🔄 The mesh-mapper will auto-start on system reboot") 131 | return True 132 | else: 133 | print(f"❌ Error installing cron job: {stderr}") 134 | return False 135 | 136 | except Exception as e: 137 | print(f"❌ Error setting up cron job: {e}") 138 | return False 139 | 140 | def verify_installation(install_path, cron_expected=True): 141 | """Verify the installation""" 142 | print("\n🔍 Verifying installation...") 143 | 144 | # Check if file exists and is executable 145 | if not os.path.exists(install_path): 146 | print(f"❌ File not found: {install_path}") 147 | return False 148 | 149 | if not os.access(install_path, os.X_OK): 150 | print(f"⚠️ File is not executable: {install_path}") 151 | return False 152 | 153 | # Check file size (should be substantial) 154 | file_size = os.path.getsize(install_path) 155 | if file_size < 1000: # Less than 1KB is suspicious 156 | print(f"⚠️ File seems too small ({file_size} bytes): {install_path}") 157 | return False 158 | 159 | print(f"✅ Installation verified: {install_path} ({file_size} bytes)") 160 | 161 | # Verify cron job only if expected 162 | if cron_expected: 163 | current_crontab = get_current_crontab() 164 | if TARGET_FILE in current_crontab and "@reboot" in current_crontab: 165 | print("✅ Cron job verified") 166 | return True 167 | else: 168 | print("⚠️ Cron job not found in crontab") 169 | return False 170 | else: 171 | print("ℹ️ Cron job verification skipped (--no-cron used)") 172 | return True 173 | 174 | def main(): 175 | parser = argparse.ArgumentParser( 176 | description="Download and setup mesh-mapper.py from GitHub", 177 | formatter_class=argparse.RawDescriptionHelpFormatter, 178 | epilog=""" 179 | Examples: 180 | python3 setup_mesh_mapper.py --branch main 181 | python3 setup_mesh_mapper.py --branch Dev 182 | python3 setup_mesh_mapper.py --branch main --install-dir /opt/mesh-mapper 183 | python3 setup_mesh_mapper.py --branch Dev --no-cron 184 | """) 185 | 186 | parser.add_argument('--branch', 187 | choices=['main', 'Dev', 'dev'], 188 | default='main', 189 | help='GitHub branch to download from (default: main)') 190 | 191 | parser.add_argument('--install-dir', 192 | default=None, 193 | help='Installation directory (default: ~/mesh-mapper)') 194 | 195 | parser.add_argument('--no-cron', 196 | action='store_true', 197 | help='Skip installing cron job') 198 | 199 | parser.add_argument('--force', 200 | action='store_true', 201 | help='Force overwrite existing files') 202 | 203 | args = parser.parse_args() 204 | 205 | # Normalize branch name (GitHub is case-sensitive) 206 | if args.branch.lower() == 'dev': 207 | branch = 'Dev' # GitHub uses capital D 208 | else: 209 | branch = args.branch 210 | 211 | # Determine installation directory 212 | if args.install_dir: 213 | install_dir = os.path.abspath(args.install_dir) 214 | else: 215 | install_dir = os.path.join(get_user_home(), 'mesh-mapper') 216 | 217 | install_path = os.path.join(install_dir, TARGET_FILE) 218 | 219 | print("=" * 60) 220 | print("🚁 MESH-MAPPER GITHUB SETUP SCRIPT") 221 | print("=" * 60) 222 | print(f"📦 Repository: https://github.com/{GITHUB_REPO}") 223 | print(f"🌿 Branch: {branch}") 224 | print(f"👤 User: {get_current_user()}") 225 | print(f"🏠 Home: {get_user_home()}") 226 | print(f"📁 Install Dir: {install_dir}") 227 | print(f"📄 Target File: {TARGET_FILE}") 228 | print() 229 | 230 | # Check if file already exists 231 | if os.path.exists(install_path) and not args.force: 232 | response = input(f"File already exists: {install_path}\nOverwrite? (y/N): ") 233 | if response.lower() != 'y': 234 | print("❌ Installation cancelled") 235 | return 1 236 | 237 | # Construct download URL 238 | download_url = construct_download_url(branch) 239 | 240 | # Check if file exists on GitHub 241 | print("🔍 Checking if file exists on GitHub...") 242 | if not check_file_exists(download_url): 243 | print(f"❌ File not found at: {download_url}") 244 | print(f" Please check if the branch '{branch}' exists and contains {TARGET_FILE}") 245 | return 1 246 | 247 | print(f"✅ File found on GitHub: {branch}/{TARGET_FILE}") 248 | 249 | # Download the file 250 | if not download_file(download_url, install_path): 251 | print("❌ Download failed") 252 | return 1 253 | 254 | # Install cron job (if requested) 255 | if not args.no_cron: 256 | print("\n🕒 Setting up auto-start cron job...") 257 | if not install_cron_job(install_dir): 258 | print("⚠️ Cron job installation failed, but file was downloaded successfully") 259 | else: 260 | print("⏭️ Skipping cron job installation (--no-cron specified)") 261 | 262 | # Verify installation 263 | if verify_installation(install_path, not args.no_cron): 264 | print("\n🎉 Installation completed successfully!") 265 | print(f"📍 Mesh-mapper installed at: {install_path}") 266 | 267 | if not args.no_cron: 268 | print("🔄 Auto-start enabled: Will run on system reboot") 269 | print("💡 To test manually, run:") 270 | print(f" cd {install_dir} && python3 {TARGET_FILE} --debug") 271 | 272 | print("\n📋 Next steps:") 273 | print(" 1. Connect your ESP32 device") 274 | print(" 2. Run the mesh-mapper manually to test") 275 | print(" 3. Reboot to test auto-start (if cron enabled)") 276 | print(f" 4. Access web interface at: http://localhost:5000") 277 | 278 | return 0 279 | else: 280 | print("❌ Installation verification failed") 281 | return 1 282 | 283 | if __name__ == "__main__": 284 | try: 285 | exit_code = main() 286 | sys.exit(exit_code) 287 | except KeyboardInterrupt: 288 | print("\n⏹️ Installation cancelled by user") 289 | sys.exit(1) 290 | except Exception as e: 291 | print(f"\n💥 Unexpected error: {e}") 292 | sys.exit(1) 293 | -------------------------------------------------------------------------------- /RPI/rpi_dependancies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Universal system-wide dependency installer for mapper.py 4 | Works on different systems (Linux, macOS, Windows) 5 | Enhanced version with comprehensive dependency detection and installation 6 | """ 7 | 8 | import subprocess 9 | import sys 10 | import os 11 | import platform 12 | 13 | def print_banner(): 14 | """Print a nice banner""" 15 | print("🚀" + "="*60 + "🚀") 16 | print(" UNIVERSAL MAPPER.PY DEPENDENCY INSTALLER") 17 | print("🚀" + "="*60 + "🚀") 18 | 19 | def detect_system(): 20 | """Detect the operating system and package manager""" 21 | system = platform.system().lower() 22 | print(f"🔍 Detected system: {platform.system()} {platform.release()}") 23 | 24 | # Detect package manager 25 | package_managers = { 26 | 'apt': 'sudo apt update && sudo apt install -y python3-pip', 27 | 'yum': 'sudo yum install -y python3-pip', 28 | 'dnf': 'sudo dnf install -y python3-pip', 29 | 'pacman': 'sudo pacman -S python-pip', 30 | 'brew': 'brew install python3', 31 | 'pkg': 'sudo pkg install py39-pip', # FreeBSD 32 | } 33 | 34 | available_pm = None 35 | for pm in package_managers.keys(): 36 | if run_command_silent(f"which {pm}")[0]: 37 | available_pm = pm 38 | print(f"✅ Found package manager: {pm}") 39 | break 40 | 41 | return system, available_pm 42 | 43 | def run_command_silent(cmd): 44 | """Run a command silently and return success status""" 45 | try: 46 | result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True) 47 | return True, result.stdout 48 | except subprocess.CalledProcessError as e: 49 | return False, e.stderr 50 | 51 | def run_command(cmd): 52 | """Run a command and return success status with output""" 53 | try: 54 | result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True) 55 | print(f"✅ {cmd}") 56 | if result.stdout.strip(): 57 | # Only print first few lines to avoid spam 58 | lines = result.stdout.strip().split('\n') 59 | if len(lines) > 3: 60 | print(f" Output: {lines[0]}...{lines[-1]}") 61 | else: 62 | print(f" Output: {result.stdout.strip()}") 63 | return True, result.stdout 64 | except subprocess.CalledProcessError as e: 65 | print(f"❌ {cmd}") 66 | if e.stderr: 67 | print(f" Error: {e.stderr.strip()}") 68 | return False, e.stderr 69 | 70 | def check_python(): 71 | """Check Python version compatibility""" 72 | print("\n🐍 Checking Python installation...") 73 | 74 | python_commands = ["python3", "python"] 75 | for py_cmd in python_commands: 76 | success, output = run_command_silent(f"{py_cmd} --version") 77 | if success: 78 | version_info = output.strip() 79 | print(f"✅ Found: {py_cmd} - {version_info}") 80 | 81 | # Check if version is compatible (3.7+) 82 | try: 83 | version_str = version_info.split()[1] 84 | major, minor = map(int, version_str.split('.')[:2]) 85 | if major >= 3 and minor >= 7: 86 | print(f"✅ Python version is compatible") 87 | return py_cmd 88 | else: 89 | print(f"⚠️ Python {major}.{minor} found, but 3.7+ recommended") 90 | return py_cmd # Still try to use it 91 | except: 92 | return py_cmd # If we can't parse version, still try 93 | 94 | print("❌ No Python installation found!") 95 | return None 96 | 97 | def check_pip(): 98 | """Check which pip command is available""" 99 | pip_commands = [ 100 | "pip3", 101 | "pip", 102 | "python3 -m pip", 103 | "python -m pip" 104 | ] 105 | 106 | print("\n🔍 Checking for pip installation...") 107 | 108 | for pip_cmd in pip_commands: 109 | success, output = run_command_silent(f"{pip_cmd} --version") 110 | if success: 111 | print(f"✅ Found: {pip_cmd} - {output.strip()}") 112 | return pip_cmd 113 | 114 | print("❌ No pip found!") 115 | return None 116 | 117 | def install_pip(system, package_manager): 118 | """Try to install pip if not found""" 119 | print("\n🔧 Attempting to install pip...") 120 | 121 | # System-specific installation methods 122 | install_methods = [] 123 | 124 | if package_manager: 125 | if package_manager == 'apt': 126 | install_methods.extend([ 127 | "sudo apt update && sudo apt install -y python3-pip python3-venv python3-dev", 128 | "sudo apt install -y python3-pip" 129 | ]) 130 | elif package_manager == 'yum': 131 | install_methods.extend([ 132 | "sudo yum install -y python3-pip python3-devel", 133 | "sudo yum install -y python3-pip" 134 | ]) 135 | elif package_manager == 'dnf': 136 | install_methods.extend([ 137 | "sudo dnf install -y python3-pip python3-devel", 138 | "sudo dnf install -y python3-pip" 139 | ]) 140 | elif package_manager == 'pacman': 141 | install_methods.append("sudo pacman -S python-pip") 142 | elif package_manager == 'brew': 143 | install_methods.append("brew install python3") 144 | elif package_manager == 'pkg': 145 | install_methods.append("sudo pkg install py39-pip") 146 | 147 | # Universal methods 148 | install_methods.extend([ 149 | "curl https://bootstrap.pypa.io/get-pip.py | python3", 150 | "wget -O - https://bootstrap.pypa.io/get-pip.py | python3", 151 | "python3 -m ensurepip --upgrade" 152 | ]) 153 | 154 | for method in install_methods: 155 | print(f"🔧 Trying: {method}") 156 | success, output = run_command(method) 157 | if success: 158 | print("✅ pip installation successful!") 159 | return check_pip() # Check again after installation 160 | 161 | print("❌ Could not install pip automatically") 162 | return None 163 | 164 | def get_all_dependencies(): 165 | """Get all required dependencies for mapper.py""" 166 | # Core dependencies from analyzing mapper.py 167 | dependencies = [ 168 | "requests", # HTTP library 169 | "urllib3", # HTTP client library 170 | "pyserial", # Serial communication 171 | "Flask", # Web framework 172 | "flask-socketio", # WebSocket support for Flask 173 | "Werkzeug", # WSGI utility library 174 | "Jinja2", # Template engine 175 | "click", # Command line interface 176 | "itsdangerous", # Cryptographic signing 177 | "MarkupSafe", # String handling library 178 | "wheel", # Built-package format 179 | "setuptools", # Package development tools 180 | "eventlet", # Async networking library (for socketio) 181 | "python-socketio", # Socket.IO client/server 182 | ] 183 | 184 | # Optional but helpful packages 185 | optional_dependencies = [ 186 | "pip-tools", # Dependency management 187 | "psutil", # System utilities 188 | "colorama", # Cross-platform colored terminal text 189 | ] 190 | 191 | return dependencies, optional_dependencies 192 | 193 | def install_dependencies(pip_cmd, system): 194 | """Install all required dependencies""" 195 | print("\n📦 Installing dependencies...") 196 | 197 | dependencies, optional_deps = get_all_dependencies() 198 | all_packages = dependencies + optional_deps 199 | 200 | print(f"Core dependencies: {len(dependencies)}") 201 | print(f"Optional dependencies: {len(optional_deps)}") 202 | print(f"Total packages: {len(all_packages)}") 203 | 204 | # Try different installation strategies 205 | strategies = [ 206 | ("break-system-packages", f"{pip_cmd} install --break-system-packages"), 207 | ("sudo break-system-packages", f"sudo {pip_cmd} install --break-system-packages"), 208 | ("user install", f"{pip_cmd} install --user"), 209 | ("sudo install", f"sudo {pip_cmd} install"), 210 | ("force reinstall", f"{pip_cmd} install --force-reinstall"), 211 | ("basic install", f"{pip_cmd} install"), 212 | ] 213 | 214 | # Try installing all packages at once first 215 | packages_str = " ".join(all_packages) 216 | 217 | for strategy_name, base_cmd in strategies: 218 | print(f"\n🔧 Trying strategy: {strategy_name}") 219 | install_cmd = f"{base_cmd} {packages_str}" 220 | print(f" Command: {install_cmd}") 221 | 222 | success, output = run_command(install_cmd) 223 | if success: 224 | print(f"\n🎉 All dependencies installed successfully using {strategy_name}!") 225 | return True, "all" 226 | 227 | # If batch installation failed, try installing core packages individually 228 | print("\n⚠️ Batch installation failed, trying individual package installation...") 229 | 230 | failed_packages = [] 231 | successful_packages = [] 232 | 233 | for package in dependencies: # Only try core dependencies individually 234 | package_installed = False 235 | 236 | for strategy_name, base_cmd in strategies[:3]: # Try top 3 strategies 237 | install_cmd = f"{base_cmd} {package}" 238 | print(f"🔧 Installing {package} with {strategy_name}...") 239 | 240 | success, output = run_command(install_cmd) 241 | if success: 242 | successful_packages.append(package) 243 | package_installed = True 244 | break 245 | 246 | if not package_installed: 247 | failed_packages.append(package) 248 | 249 | print(f"\n📊 Installation Summary:") 250 | print(f"✅ Successful: {len(successful_packages)} packages") 251 | if successful_packages: 252 | print(f" {', '.join(successful_packages)}") 253 | 254 | if failed_packages: 255 | print(f"❌ Failed: {len(failed_packages)} packages") 256 | print(f" {', '.join(failed_packages)}") 257 | return False, "partial" 258 | else: 259 | print(f"🎉 All core dependencies installed successfully!") 260 | return True, "individual" 261 | 262 | def test_imports(): 263 | """Test if all critical imports work""" 264 | print("\n🧪 Testing imports...") 265 | 266 | critical_imports = [ 267 | ("os", "import os"), 268 | ("requests", "import requests"), 269 | ("serial", "import serial; import serial.tools.list_ports"), 270 | ("flask", "from flask import Flask"), 271 | ("flask_socketio", "from flask_socketio import SocketIO"), 272 | ] 273 | 274 | failed_imports = [] 275 | 276 | for name, import_statement in critical_imports: 277 | try: 278 | exec(import_statement) 279 | print(f"✅ {name}") 280 | except ImportError as e: 281 | print(f"❌ {name} - {e}") 282 | failed_imports.append(name) 283 | except Exception as e: 284 | print(f"⚠️ {name} - {e}") 285 | 286 | if failed_imports: 287 | print(f"\n❌ Some imports failed: {', '.join(failed_imports)}") 288 | return False 289 | else: 290 | print(f"\n✅ All critical imports successful!") 291 | return True 292 | 293 | def create_test_script(): 294 | """Create a test script to verify installation""" 295 | test_script = """#!/usr/bin/env python3 296 | # Quick test script for mapper.py dependencies 297 | 298 | try: 299 | import os, time, json, csv, logging, threading 300 | import requests, urllib3, serial 301 | from flask import Flask 302 | from flask_socketio import SocketIO 303 | print("✅ All critical imports successful!") 304 | 305 | # Test serial port detection 306 | import serial.tools.list_ports 307 | ports = list(serial.tools.list_ports.comports()) 308 | print(f"✅ Serial port detection working ({len(ports)} ports found)") 309 | 310 | # Test Flask + SocketIO 311 | app = Flask(__name__) 312 | socketio = SocketIO(app) 313 | print("✅ Flask + SocketIO working") 314 | 315 | print("🎉 mapper.py dependencies are ready!") 316 | 317 | except ImportError as e: 318 | print(f"❌ Import error: {e}") 319 | except Exception as e: 320 | print(f"❌ Error: {e}") 321 | """ 322 | 323 | try: 324 | with open("test_dependencies.py", "w") as f: 325 | f.write(test_script) 326 | print("✅ Created test_dependencies.py") 327 | return True 328 | except Exception as e: 329 | print(f"❌ Failed to create test script: {e}") 330 | return False 331 | 332 | def print_final_summary(success, method, system): 333 | """Print final summary and next steps""" 334 | print("\n" + "🎯" + "="*60 + "🎯") 335 | print(" INSTALLATION SUMMARY") 336 | print("🎯" + "="*60 + "🎯") 337 | 338 | if success: 339 | print("✅ INSTALLATION SUCCESSFUL!") 340 | print(f"📦 Method used: {method}") 341 | print(f"💻 System: {system}") 342 | 343 | print(f"\n🚀 NEXT STEPS:") 344 | print(f" 1. Test your setup: python3 test_dependencies.py") 345 | print(f" 2. Run your mapper: python3 mapper.py") 346 | print(f" 3. For headless mode: python3 mapper.py --headless") 347 | print(f" 4. For help: python3 mapper.py --help") 348 | 349 | else: 350 | print("❌ INSTALLATION INCOMPLETE") 351 | print(f"\n🔧 TROUBLESHOOTING:") 352 | print(f" 1. Try with sudo: sudo python3 install_universal.py") 353 | print(f" 2. Manual install: pip3 install requests flask pyserial flask-socketio") 354 | print(f" 3. Use virtual environment: python3 -m venv venv && source venv/bin/activate") 355 | print(f" 4. Check system package manager (apt, yum, dnf, etc.)") 356 | 357 | print("\n📝 FILES CREATED:") 358 | print(" • test_dependencies.py - Test script") 359 | print(" • install_universal.py - This installer") 360 | 361 | print("🎯" + "="*60 + "🎯") 362 | 363 | def main(): 364 | """Main installation routine""" 365 | print_banner() 366 | 367 | # Detect system 368 | system, package_manager = detect_system() 369 | 370 | # Check Python 371 | python_cmd = check_python() 372 | if not python_cmd: 373 | print("\n❌ Python is required but not found!") 374 | return False 375 | 376 | # Check/install pip 377 | pip_cmd = check_pip() 378 | if not pip_cmd: 379 | pip_cmd = install_pip(system, package_manager) 380 | 381 | if not pip_cmd: 382 | print("\n❌ Cannot proceed without pip!") 383 | print("\n🔧 Manual pip installation:") 384 | if package_manager == 'apt': 385 | print(" sudo apt update && sudo apt install python3-pip") 386 | elif package_manager == 'yum': 387 | print(" sudo yum install python3-pip") 388 | elif package_manager == 'dnf': 389 | print(" sudo dnf install python3-pip") 390 | else: 391 | print(" curl https://bootstrap.pypa.io/get-pip.py | python3") 392 | return False 393 | 394 | # Install dependencies 395 | success, method = install_dependencies(pip_cmd, system) 396 | 397 | # Test imports 398 | import_success = test_imports() 399 | 400 | # Create test script 401 | create_test_script() 402 | 403 | # Final summary 404 | overall_success = success and import_success 405 | print_final_summary(overall_success, method, system) 406 | 407 | return overall_success 408 | 409 | if __name__ == "__main__": 410 | try: 411 | success = main() 412 | sys.exit(0 if success else 1) 413 | except KeyboardInterrupt: 414 | print("\n\n⚠️ Installation interrupted by user") 415 | sys.exit(1) 416 | except Exception as e: 417 | print(f"\n\n❌ Unexpected error: {e}") 418 | sys.exit(1) -------------------------------------------------------------------------------- /boards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/boards.png -------------------------------------------------------------------------------- /eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/eye.png -------------------------------------------------------------------------------- /eyepurple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /firmware/esp32c3-wifi-rid-node-mode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/firmware/esp32c3-wifi-rid-node-mode.bin -------------------------------------------------------------------------------- /firmware/esp32c3-wifi-rid.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/firmware/esp32c3-wifi-rid.bin -------------------------------------------------------------------------------- /firmware/esp32s3-dual-rid-apple-maps.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/firmware/esp32s3-dual-rid-apple-maps.bin -------------------------------------------------------------------------------- /firmware/esp32s3-dual-rid-node-mode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/firmware/esp32s3-dual-rid-node-mode.bin -------------------------------------------------------------------------------- /firmware/esp32s3-dual-rid.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/firmware/esp32s3-dual-rid.bin -------------------------------------------------------------------------------- /mapper_test/mapper_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Test Mapper for Mesh Mapper - Arizona Desert Drone Simulation 4 | ============================================================ 5 | 6 | This script simulates 5 drones flying in the Arizona desert for testing and map validation. 7 | It sends realistic detection data to the main mesh-mapper application via HTTP API. 8 | 9 | Features: 10 | - 5 distinct drones with unique flight patterns 11 | - Arizona desert coordinates (Sonoran Desert region) 12 | - Realistic altitude variations 13 | - Pilot locations 14 | - Signal strength simulation (RSSI) 15 | - Basic ID and FAA data simulation 16 | - Continuous flight path updates 17 | 18 | Usage: 19 | python test_mapper.py [--host HOST] [--port PORT] [--duration MINUTES] 20 | """ 21 | 22 | import json 23 | import time 24 | import random 25 | import requests 26 | import argparse 27 | import threading 28 | from datetime import datetime 29 | from typing import Dict, List, Tuple 30 | import math 31 | 32 | # Arizona Desert Test Area - Sonoran Desert coordinates 33 | # Centered around Phoenix/Scottsdale area 34 | ARIZONA_DESERT_CENTER = { 35 | 'lat': 33.4942, # Phoenix area 36 | 'lng': -111.9261 37 | } 38 | 39 | # Test area bounds (roughly 50km x 50km area) 40 | TEST_AREA_BOUNDS = { 41 | 'north': 33.7, 42 | 'south': 33.3, 43 | 'east': -111.7, 44 | 'west': -112.2 45 | } 46 | 47 | class DroneSimulator: 48 | """Simulates a single drone's flight pattern in the Arizona desert""" 49 | 50 | def __init__(self, drone_id: int, mac_address: str, basic_id: str): 51 | self.drone_id = drone_id 52 | self.mac_address = mac_address 53 | self.basic_id = basic_id 54 | self.current_position = self._generate_start_position() 55 | self.target_position = self._generate_target_position() 56 | self.pilot_position = self._generate_pilot_position() 57 | self.altitude = random.randint(50, 400) # FAA allowed range 58 | self.speed = random.uniform(5, 25) # m/s (roughly 11-56 mph) 59 | self.direction = random.uniform(0, 360) # degrees 60 | self.last_update = time.time() 61 | self.flight_pattern = self._choose_flight_pattern() 62 | 63 | # FAA data simulation 64 | self.faa_data = self._generate_faa_data() 65 | 66 | def _generate_start_position(self) -> Dict[str, float]: 67 | """Generate a random starting position within Arizona desert bounds""" 68 | lat = random.uniform(TEST_AREA_BOUNDS['south'], TEST_AREA_BOUNDS['north']) 69 | lng = random.uniform(TEST_AREA_BOUNDS['west'], TEST_AREA_BOUNDS['east']) 70 | return {'lat': lat, 'lng': lng} 71 | 72 | def _generate_target_position(self) -> Dict[str, float]: 73 | """Generate a target position for flight pattern""" 74 | lat = random.uniform(TEST_AREA_BOUNDS['south'], TEST_AREA_BOUNDS['north']) 75 | lng = random.uniform(TEST_AREA_BOUNDS['west'], TEST_AREA_BOUNDS['east']) 76 | return {'lat': lat, 'lng': lng} 77 | 78 | def _generate_pilot_position(self) -> Dict[str, float]: 79 | """Generate pilot position (usually stationary, within reasonable distance)""" 80 | # Pilot typically within 5km of drone start position 81 | offset_lat = random.uniform(-0.045, 0.045) # ~5km 82 | offset_lng = random.uniform(-0.045, 0.045) 83 | 84 | pilot_lat = self.current_position['lat'] + offset_lat 85 | pilot_lng = self.current_position['lng'] + offset_lng 86 | 87 | # Ensure pilot stays within bounds 88 | pilot_lat = max(TEST_AREA_BOUNDS['south'], min(TEST_AREA_BOUNDS['north'], pilot_lat)) 89 | pilot_lng = max(TEST_AREA_BOUNDS['west'], min(TEST_AREA_BOUNDS['east'], pilot_lng)) 90 | 91 | return {'lat': pilot_lat, 'lng': pilot_lng} 92 | 93 | def _choose_flight_pattern(self) -> str: 94 | """Choose a flight pattern for this drone""" 95 | patterns = ['linear', 'circular', 'waypoint', 'search_pattern', 'hover'] 96 | return random.choice(patterns) 97 | 98 | def _generate_faa_data(self) -> Dict: 99 | """Generate realistic FAA registration data""" 100 | manufacturers = ["DJI", "Autel", "Parrot", "Skydio", "Yuneec"] 101 | models = ["Mavic 3", "Air 2S", "Mini 3 Pro", "Phantom 4", "Inspire 2", "EVO II", "ANAFI", "X2"] 102 | 103 | return { 104 | "registrant_name": f"Test Pilot {self.drone_id}", 105 | "registrant_type": "Individual", 106 | "manufacturer": random.choice(manufacturers), 107 | "model": random.choice(models), 108 | "registration_date": "2023-01-15", 109 | "expiration_date": "2026-01-15", 110 | "status": "Active", 111 | "serial_number": f"TST{self.drone_id:03d}{random.randint(1000, 9999)}", 112 | "weight": random.uniform(0.5, 25.0), # kg 113 | "purpose": random.choice(["Recreation", "Commercial", "Educational", "Research"]) 114 | } 115 | 116 | def _calculate_distance(self, pos1: Dict[str, float], pos2: Dict[str, float]) -> float: 117 | """Calculate distance between two positions in meters""" 118 | lat1, lng1 = math.radians(pos1['lat']), math.radians(pos1['lng']) 119 | lat2, lng2 = math.radians(pos2['lat']), math.radians(pos2['lng']) 120 | 121 | dlat = lat2 - lat1 122 | dlng = lng2 - lng1 123 | 124 | a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlng/2)**2 125 | c = 2 * math.asin(math.sqrt(a)) 126 | 127 | return 6371000 * c # Earth radius in meters 128 | 129 | def update_position(self): 130 | """Update drone position based on flight pattern""" 131 | current_time = time.time() 132 | dt = current_time - self.last_update 133 | self.last_update = current_time 134 | 135 | if self.flight_pattern == 'linear': 136 | self._update_linear_flight(dt) 137 | elif self.flight_pattern == 'circular': 138 | self._update_circular_flight(dt) 139 | elif self.flight_pattern == 'waypoint': 140 | self._update_waypoint_flight(dt) 141 | elif self.flight_pattern == 'search_pattern': 142 | self._update_search_pattern(dt) 143 | elif self.flight_pattern == 'hover': 144 | self._update_hover_pattern(dt) 145 | 146 | # Update altitude with small variations 147 | self.altitude += random.uniform(-2, 2) 148 | self.altitude = max(30, min(400, self.altitude)) # Keep within legal limits 149 | 150 | def _update_linear_flight(self, dt: float): 151 | """Linear flight pattern - fly towards target""" 152 | distance_to_target = self._calculate_distance(self.current_position, self.target_position) 153 | 154 | if distance_to_target < 100: # Within 100m of target, choose new target 155 | self.target_position = self._generate_target_position() 156 | return 157 | 158 | # Calculate movement 159 | lat_diff = self.target_position['lat'] - self.current_position['lat'] 160 | lng_diff = self.target_position['lng'] - self.current_position['lng'] 161 | 162 | # Normalize and apply speed 163 | distance = math.sqrt(lat_diff**2 + lng_diff**2) 164 | if distance > 0: 165 | move_lat = (lat_diff / distance) * (self.speed * dt) / 111000 # Convert m to degrees 166 | move_lng = (lng_diff / distance) * (self.speed * dt) / (111000 * math.cos(math.radians(self.current_position['lat']))) 167 | 168 | self.current_position['lat'] += move_lat 169 | self.current_position['lng'] += move_lng 170 | 171 | def _update_circular_flight(self, dt: float): 172 | """Circular flight pattern""" 173 | radius = 0.01 # ~1km radius in degrees 174 | angular_speed = self.speed / (radius * 111000) # Convert to angular velocity 175 | 176 | center_lat = ARIZONA_DESERT_CENTER['lat'] 177 | center_lng = ARIZONA_DESERT_CENTER['lng'] 178 | 179 | # Calculate current angle 180 | current_angle = math.atan2( 181 | self.current_position['lng'] - center_lng, 182 | self.current_position['lat'] - center_lat 183 | ) 184 | 185 | # Update angle 186 | current_angle += angular_speed * dt 187 | 188 | # Calculate new position 189 | self.current_position['lat'] = center_lat + radius * math.cos(current_angle) 190 | self.current_position['lng'] = center_lng + radius * math.sin(current_angle) 191 | 192 | def _update_waypoint_flight(self, dt: float): 193 | """Waypoint-based flight pattern""" 194 | # Similar to linear but with multiple waypoints 195 | self._update_linear_flight(dt) 196 | 197 | # Randomly change direction occasionally 198 | if random.random() < 0.01: # 1% chance per update 199 | self.target_position = self._generate_target_position() 200 | 201 | def _update_search_pattern(self, dt: float): 202 | """Search/survey pattern (back and forth)""" 203 | # Implement a lawn-mower pattern 204 | move_distance = self.speed * dt / 111000 # Convert to degrees 205 | 206 | # Move in current direction 207 | self.current_position['lat'] += move_distance * math.cos(math.radians(self.direction)) 208 | self.current_position['lng'] += move_distance * math.sin(math.radians(self.direction)) 209 | 210 | # Check bounds and reverse direction if needed 211 | if (self.current_position['lat'] >= TEST_AREA_BOUNDS['north'] or 212 | self.current_position['lat'] <= TEST_AREA_BOUNDS['south'] or 213 | self.current_position['lng'] >= TEST_AREA_BOUNDS['east'] or 214 | self.current_position['lng'] <= TEST_AREA_BOUNDS['west']): 215 | 216 | self.direction = (self.direction + 180) % 360 217 | 218 | def _update_hover_pattern(self, dt: float): 219 | """Hovering pattern with small movements""" 220 | # Small random movements around current position 221 | move_distance = random.uniform(-0.0001, 0.0001) # Very small movements 222 | self.current_position['lat'] += move_distance 223 | self.current_position['lng'] += move_distance 224 | 225 | def generate_detection(self) -> Dict: 226 | """Generate a detection object for this drone""" 227 | current_time = time.time() 228 | 229 | # Simulate RSSI based on distance from pilot 230 | pilot_distance = self._calculate_distance(self.current_position, self.pilot_position) 231 | # RSSI typically -30 to -90 dBm, closer = stronger signal 232 | base_rssi = -40 233 | distance_factor = min(pilot_distance / 1000, 50) # Max 50km consideration 234 | rssi = base_rssi - (distance_factor * 1.0) + random.uniform(-5, 5) 235 | rssi = max(-90, min(-30, rssi)) # Clamp to realistic range 236 | 237 | detection = { 238 | "mac": self.mac_address, 239 | "rssi": round(rssi, 1), 240 | "drone_lat": round(self.current_position['lat'], 6), 241 | "drone_long": round(self.current_position['lng'], 6), 242 | "drone_altitude": round(self.altitude, 1), 243 | "pilot_lat": round(self.pilot_position['lat'], 6), 244 | "pilot_long": round(self.pilot_position['lng'], 6), 245 | "basic_id": self.basic_id, 246 | "faa_data": self.faa_data, 247 | "last_update": current_time, 248 | "status": "active" 249 | } 250 | 251 | return detection 252 | 253 | class ArizonaDesertTestSuite: 254 | """Main test suite for Arizona desert drone simulation""" 255 | 256 | def __init__(self, host: str = 'localhost', port: int = 5000): 257 | self.host = host 258 | self.port = port 259 | self.base_url = f"http://{host}:{port}" 260 | self.drones = [] 261 | self.running = False 262 | 263 | # Initialize 5 test drones 264 | self._initialize_drones() 265 | 266 | def _initialize_drones(self): 267 | """Initialize 5 test drones with unique configurations""" 268 | drone_configs = [ 269 | {"id": 1, "mac": "AA:BB:CC:DD:EE:01", "basic_id": "AZTEST001", "name": "Desert Eagle"}, 270 | {"id": 2, "mac": "AA:BB:CC:DD:EE:02", "basic_id": "AZTEST002", "name": "Cactus Hawk"}, 271 | {"id": 3, "mac": "AA:BB:CC:DD:EE:03", "basic_id": "AZTEST003", "name": "Saguaro Scout"}, 272 | {"id": 4, "mac": "AA:BB:CC:DD:EE:04", "basic_id": "AZTEST004", "name": "Mesa Phantom"}, 273 | {"id": 5, "mac": "AA:BB:CC:DD:EE:05", "basic_id": "AZTEST005", "name": "Sonoran Surveyor"} 274 | ] 275 | 276 | for config in drone_configs: 277 | drone = DroneSimulator(config["id"], config["mac"], config["basic_id"]) 278 | drone.name = config["name"] 279 | self.drones.append(drone) 280 | print(f"Initialized {config['name']} (MAC: {config['mac']}) at " 281 | f"lat {drone.current_position['lat']:.6f}, lng {drone.current_position['lng']:.6f}") 282 | 283 | def test_connection(self) -> bool: 284 | """Test connection to the mesh-mapper API""" 285 | try: 286 | response = requests.get(f"{self.base_url}/api/detections", timeout=5) 287 | if response.status_code == 200: 288 | print(f"✓ Successfully connected to mesh-mapper at {self.base_url}") 289 | return True 290 | else: 291 | print(f"✗ Connection failed: HTTP {response.status_code}") 292 | return False 293 | except requests.exceptions.RequestException as e: 294 | print(f"✗ Connection failed: {e}") 295 | return False 296 | 297 | def send_detection(self, detection: Dict) -> bool: 298 | """Send a detection to the mesh-mapper API""" 299 | try: 300 | response = requests.post( 301 | f"{self.base_url}/api/detections", 302 | json=detection, 303 | headers={'Content-Type': 'application/json'}, 304 | timeout=5 305 | ) 306 | return response.status_code == 200 307 | except requests.exceptions.RequestException as e: 308 | print(f"Failed to send detection: {e}") 309 | return False 310 | 311 | def drone_simulation_thread(self, drone: DroneSimulator, update_interval: float): 312 | """Thread function for individual drone simulation""" 313 | print(f"Started simulation thread for {drone.name}") 314 | 315 | while self.running: 316 | try: 317 | # Update drone position 318 | drone.update_position() 319 | 320 | # Generate and send detection 321 | detection = drone.generate_detection() 322 | success = self.send_detection(detection) 323 | 324 | if success: 325 | print(f"🛩️ {drone.name}: lat {detection['drone_lat']:.6f}, " 326 | f"lng {detection['drone_long']:.6f}, alt {detection['drone_altitude']:.1f}m, " 327 | f"RSSI {detection['rssi']:.1f}dBm") 328 | else: 329 | print(f"⚠️ Failed to send detection for {drone.name}") 330 | 331 | time.sleep(update_interval) 332 | 333 | except Exception as e: 334 | print(f"Error in drone simulation for {drone.name}: {e}") 335 | time.sleep(1) 336 | 337 | print(f"Stopped simulation thread for {drone.name}") 338 | 339 | def start_simulation(self, duration_minutes: float = 60, update_interval: float = 2.0): 340 | """Start the Arizona desert drone simulation""" 341 | if not self.test_connection(): 342 | print("Cannot start simulation - connection test failed") 343 | return False 344 | 345 | print(f"\n🏜️ Starting Arizona Desert Drone Simulation") 346 | print(f" Duration: {duration_minutes} minutes") 347 | print(f" Update interval: {update_interval} seconds") 348 | print(f" Test area: {TEST_AREA_BOUNDS}") 349 | print(f" Number of drones: {len(self.drones)}") 350 | print("="*60) 351 | 352 | self.running = True 353 | threads = [] 354 | 355 | # Start simulation thread for each drone 356 | for drone in self.drones: 357 | thread = threading.Thread( 358 | target=self.drone_simulation_thread, 359 | args=(drone, update_interval), 360 | daemon=True 361 | ) 362 | thread.start() 363 | threads.append(thread) 364 | 365 | try: 366 | # Run for specified duration 367 | time.sleep(duration_minutes * 60) 368 | except KeyboardInterrupt: 369 | print("\n🛑 Simulation interrupted by user") 370 | 371 | # Stop simulation 372 | print("\n🏁 Stopping simulation...") 373 | self.running = False 374 | 375 | # Wait for threads to finish 376 | for thread in threads: 377 | thread.join(timeout=5) 378 | 379 | print("✓ Arizona Desert Drone Simulation completed") 380 | return True 381 | 382 | def generate_summary_report(self): 383 | """Generate a summary report of the test configuration""" 384 | print("\n" + "="*60) 385 | print("ARIZONA DESERT DRONE TEST CONFIGURATION") 386 | print("="*60) 387 | print(f"Test Area: Arizona Sonoran Desert") 388 | print(f"Center: {ARIZONA_DESERT_CENTER['lat']:.6f}, {ARIZONA_DESERT_CENTER['lng']:.6f}") 389 | print(f"Bounds: N{TEST_AREA_BOUNDS['north']:.3f} S{TEST_AREA_BOUNDS['south']:.3f} " 390 | f"E{TEST_AREA_BOUNDS['east']:.3f} W{TEST_AREA_BOUNDS['west']:.3f}") 391 | print(f"Area Size: ~50km x 50km") 392 | print() 393 | 394 | for i, drone in enumerate(self.drones, 1): 395 | print(f"Drone {i}: {drone.name}") 396 | print(f" MAC: {drone.mac_address}") 397 | print(f" Basic ID: {drone.basic_id}") 398 | print(f" Start Position: {drone.current_position['lat']:.6f}, {drone.current_position['lng']:.6f}") 399 | print(f" Pilot Position: {drone.pilot_position['lat']:.6f}, {drone.pilot_position['lng']:.6f}") 400 | print(f" Flight Pattern: {drone.flight_pattern}") 401 | print(f" FAA Registration: {drone.faa_data['manufacturer']} {drone.faa_data['model']}") 402 | print() 403 | 404 | def main(): 405 | """Main function with command line interface""" 406 | parser = argparse.ArgumentParser( 407 | description="Arizona Desert Drone Simulation for Mesh Mapper Testing" 408 | ) 409 | parser.add_argument( 410 | '--host', 411 | default='localhost', 412 | help='Mesh-mapper host (default: localhost)' 413 | ) 414 | parser.add_argument( 415 | '--port', 416 | type=int, 417 | default=5000, 418 | help='Mesh-mapper port (default: 5000)' 419 | ) 420 | parser.add_argument( 421 | '--duration', 422 | type=float, 423 | default=30, 424 | help='Simulation duration in minutes (default: 30)' 425 | ) 426 | parser.add_argument( 427 | '--interval', 428 | type=float, 429 | default=2.0, 430 | help='Update interval in seconds (default: 2.0)' 431 | ) 432 | parser.add_argument( 433 | '--summary-only', 434 | action='store_true', 435 | help='Show configuration summary only, do not run simulation' 436 | ) 437 | 438 | args = parser.parse_args() 439 | 440 | # Create test suite 441 | test_suite = ArizonaDesertTestSuite(args.host, args.port) 442 | 443 | # Show configuration summary 444 | test_suite.generate_summary_report() 445 | 446 | if args.summary_only: 447 | print("Summary only mode - simulation not started") 448 | return 449 | 450 | # Start simulation 451 | success = test_suite.start_simulation(args.duration, args.interval) 452 | 453 | if success: 454 | print("\n✅ Test completed successfully!") 455 | print("Check the mesh-mapper web interface to view the simulated drone data.") 456 | else: 457 | print("\n❌ Test failed - check connection and try again.") 458 | 459 | if __name__ == "__main__": 460 | main() 461 | -------------------------------------------------------------------------------- /node-mode-dualcore/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /node-mode-dualcore/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/node-mode-dualcore/firmware.bin -------------------------------------------------------------------------------- /node-mode-dualcore/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the convention is to give header files names that end with `.h'. 29 | 30 | Read more about using header files in official GCC documentation: 31 | 32 | * Include Syntax 33 | * Include Operation 34 | * Once-Only Headers 35 | * Computed Includes 36 | 37 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 38 | -------------------------------------------------------------------------------- /node-mode-dualcore/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into the executable file. 4 | 5 | The source code of each library should be placed in a separate directory 6 | ("lib/your_library_name/[Code]"). 7 | 8 | For example, see the structure of the following example libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Example contents of `src/main.c` using Foo and Bar: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | The PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries by scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /node-mode-dualcore/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:seeed_xiao_esp32c3] 12 | platform = espressif32 13 | board = seeed_xiao_esp32c3 14 | framework = arduino 15 | 16 | [env:seeed_xiao_esp32s3] 17 | platform = espressif32 18 | board = seeed_xiao_esp32s3 19 | framework = arduino 20 | -------------------------------------------------------------------------------- /node-mode-dualcore/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * node-mode with dual Wi-Fi and BLE support for ESP32S3 3 | */ 4 | #if !defined(ARDUINO_ARCH_ESP32) 5 | #error "This program requires an ESP32" 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "opendroneid.h" 17 | #include "odid_wifi.h" 18 | #include 19 | 20 | // UART pin definitions for Serial1 on esp32s3 21 | const int SERIAL1_RX_PIN = 6; // GPIO6 22 | const int SERIAL1_TX_PIN = 5; // GPIO5 23 | 24 | // Structure to hold UAV detection data 25 | struct uav_data { 26 | uint8_t mac[6]; 27 | int rssi; 28 | uint32_t last_seen; 29 | char op_id[ODID_ID_SIZE + 1]; 30 | char uav_id[ODID_ID_SIZE + 1]; 31 | double lat_d; 32 | double long_d; 33 | double base_lat_d; 34 | double base_long_d; 35 | int altitude_msl; 36 | int height_agl; 37 | int speed; 38 | int heading; 39 | int flag; 40 | }; 41 | 42 | #define MAX_UAVS 8 43 | uav_data uavs[MAX_UAVS] = {0}; 44 | BLEScan* pBLEScan = nullptr; 45 | ODID_UAS_Data UAS_data; 46 | unsigned long last_status = 0; 47 | 48 | // Forward declarations 49 | void callback(void *, wifi_promiscuous_pkt_type_t); 50 | void send_json_fast(const uav_data *UAV); 51 | void print_compact_message(const uav_data *UAV); 52 | 53 | // Get next available UAV slot or reuse existing one 54 | uav_data* next_uav(uint8_t* mac) { 55 | for (int i = 0; i < MAX_UAVS; i++) { 56 | if (memcmp(uavs[i].mac, mac, 6) == 0) 57 | return &uavs[i]; 58 | } 59 | for (int i = 0; i < MAX_UAVS; i++) { 60 | if (uavs[i].mac[0] == 0) 61 | return &uavs[i]; 62 | } 63 | return &uavs[0]; // Fallback to first slot if all are used 64 | } 65 | 66 | // BLE Advertisement callback handler 67 | class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { 68 | public: 69 | void onResult(BLEAdvertisedDevice device) override { 70 | int len = device.getPayloadLength(); 71 | if (len <= 0) return; 72 | 73 | uint8_t* payload = device.getPayload(); 74 | if (len > 5 && payload[1] == 0x16 && payload[2] == 0xFA && 75 | payload[3] == 0xFF && payload[4] == 0x0D) { 76 | uint8_t* mac = (uint8_t*) device.getAddress().getNative(); 77 | uav_data* UAV = next_uav(mac); 78 | UAV->last_seen = millis(); 79 | UAV->rssi = device.getRSSI(); 80 | UAV->flag = 1; 81 | memcpy(UAV->mac, mac, 6); 82 | 83 | uint8_t* odid = &payload[6]; 84 | switch (odid[0] & 0xF0) { 85 | case 0x00: { 86 | ODID_BasicID_data basic; 87 | decodeBasicIDMessage(&basic, (ODID_BasicID_encoded*) odid); 88 | strncpy(UAV->uav_id, (char*) basic.UASID, ODID_ID_SIZE); 89 | break; 90 | } 91 | case 0x10: { 92 | ODID_Location_data loc; 93 | decodeLocationMessage(&loc, (ODID_Location_encoded*) odid); 94 | UAV->lat_d = loc.Latitude; 95 | UAV->long_d = loc.Longitude; 96 | UAV->altitude_msl = (int) loc.AltitudeGeo; 97 | UAV->height_agl = (int) loc.Height; 98 | UAV->speed = (int) loc.SpeedHorizontal; 99 | UAV->heading = (int) loc.Direction; 100 | break; 101 | } 102 | case 0x40: { 103 | ODID_System_data sys; 104 | decodeSystemMessage(&sys, (ODID_System_encoded*) odid); 105 | UAV->base_lat_d = sys.OperatorLatitude; 106 | UAV->base_long_d = sys.OperatorLongitude; 107 | break; 108 | } 109 | case 0x50: { 110 | ODID_OperatorID_data op; 111 | decodeOperatorIDMessage(&op, (ODID_OperatorID_encoded*) odid); 112 | strncpy(UAV->op_id, (char*) op.OperatorId, ODID_ID_SIZE); 113 | break; 114 | } 115 | } 116 | } 117 | } 118 | }; 119 | 120 | // Initialize USB Serial (for JSON output) and Serial1 (for mesh/UART) 121 | void initializeSerial() { 122 | Serial.begin(115200); 123 | Serial1.begin(115200, SERIAL_8N1, SERIAL1_RX_PIN, SERIAL1_TX_PIN); 124 | Serial.println("USB Serial (for JSON) and UART (Serial1) initialized."); 125 | } 126 | 127 | // Sends JSON payload as fast as possible over USB Serial (includes basic_id). 128 | void send_json_fast(const uav_data *UAV) { 129 | char mac_str[18]; 130 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 131 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 132 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 133 | char json_msg[256]; 134 | snprintf(json_msg, sizeof(json_msg), 135 | "{\"mac\":\"%s\",\"rssi\":%d,\"drone_lat\":%.6f,\"drone_long\":%.6f," 136 | "\"drone_altitude\":%d,\"pilot_lat\":%.6f,\"pilot_long\":%.6f," 137 | "\"basic_id\":\"%s\"}", 138 | mac_str, UAV->rssi, UAV->lat_d, UAV->long_d, UAV->altitude_msl, 139 | UAV->base_lat_d, UAV->base_long_d, UAV->uav_id); 140 | Serial.println(json_msg); 141 | } 142 | 143 | // Modified function: emits two JSON messages over Serial1 144 | void print_compact_message(const uav_data *UAV) { 145 | static unsigned long lastSendTime = 0; 146 | const unsigned long sendInterval = 3000; // 3-second interval for UART messages 147 | if (millis() - lastSendTime < sendInterval) return; 148 | lastSendTime = millis(); 149 | 150 | // Format MAC address 151 | char mac_str[18]; 152 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 153 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 154 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 155 | 156 | // First JSON: MAC address and drone coordinates 157 | char json_drone[128]; 158 | int len_drone = snprintf(json_drone, sizeof(json_drone), 159 | "{\"mac\":\"%s\",\"drone_lat\":%.6f,\"drone_long\":%.6f}", 160 | mac_str, UAV->lat_d, UAV->long_d); 161 | if (Serial1.availableForWrite() >= len_drone) { 162 | Serial1.println(json_drone); 163 | } 164 | 165 | // Second JSON: remote ID and pilot coordinates 166 | char json_pilot[128]; 167 | snprintf(json_pilot, sizeof(json_pilot), 168 | "{\"remote_id\":\"%s\",\"pilot_lat\":%.6f,\"pilot_long\":%.6f}", 169 | UAV->uav_id, UAV->base_lat_d, UAV->base_long_d); 170 | Serial1.println(json_pilot); 171 | } 172 | 173 | // Wi-Fi promiscuous packet callback 174 | void callback(void *buffer, wifi_promiscuous_pkt_type_t type) { 175 | if (type != WIFI_PKT_MGMT) return; 176 | 177 | wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t *)buffer; 178 | uint8_t *payload = packet->payload; 179 | int length = packet->rx_ctrl.sig_len; 180 | 181 | static const uint8_t nan_dest[6] = {0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00}; 182 | if (memcmp(nan_dest, &payload[4], 6) == 0) { 183 | if (odid_wifi_receive_message_pack_nan_action_frame(&UAS_data, nullptr, payload, length) == 0) { 184 | uav_data UAV; 185 | memset(&UAV, 0, sizeof(UAV)); 186 | memcpy(UAV.mac, &payload[10], 6); 187 | UAV.rssi = packet->rx_ctrl.rssi; 188 | UAV.last_seen = millis(); 189 | 190 | if (UAS_data.BasicIDValid[0]) { 191 | strncpy(UAV.uav_id, (char *)UAS_data.BasicID[0].UASID, ODID_ID_SIZE); 192 | } 193 | if (UAS_data.LocationValid) { 194 | UAV.lat_d = UAS_data.Location.Latitude; 195 | UAV.long_d = UAS_data.Location.Longitude; 196 | UAV.altitude_msl = (int)UAS_data.Location.AltitudeGeo; 197 | UAV.height_agl = (int)UAS_data.Location.Height; 198 | UAV.speed = (int)UAS_data.Location.SpeedHorizontal; 199 | UAV.heading = (int)UAS_data.Location.Direction; 200 | } 201 | if (UAS_data.SystemValid) { 202 | UAV.base_lat_d = UAS_data.System.OperatorLatitude; 203 | UAV.base_long_d = UAS_data.System.OperatorLongitude; 204 | } 205 | if (UAS_data.OperatorIDValid) { 206 | strncpy(UAV.op_id, (char *)UAS_data.OperatorID.OperatorId, ODID_ID_SIZE); 207 | } 208 | 209 | uav_data* dbUAV = next_uav(UAV.mac); 210 | memcpy(dbUAV, &UAV, sizeof(UAV)); 211 | dbUAV->flag = 1; 212 | } 213 | } 214 | else if (payload[0] == 0x80) { 215 | int offset = 36; 216 | while (offset < length) { 217 | int typ = payload[offset]; 218 | int len = payload[offset + 1]; 219 | if ((typ == 0xdd) && 220 | (((payload[offset + 2] == 0x90 && payload[offset + 3] == 0x3a && payload[offset + 4] == 0xe6)) || 221 | ((payload[offset + 2] == 0xfa && payload[offset + 3] == 0x0b && payload[offset + 4] == 0xbc)))) { 222 | int j = offset + 7; 223 | if (j < length) { 224 | memset(&UAS_data, 0, sizeof(UAS_data)); 225 | odid_message_process_pack(&UAS_data, &payload[j], length - j); 226 | 227 | uav_data UAV; 228 | memset(&UAV, 0, sizeof(UAV)); 229 | memcpy(UAV.mac, &payload[10], 6); 230 | UAV.rssi = packet->rx_ctrl.rssi; 231 | UAV.last_seen = millis(); 232 | 233 | if (UAS_data.BasicIDValid[0]) { 234 | strncpy(UAV.uav_id, (char *)UAS_data.BasicID[0].UASID, ODID_ID_SIZE); 235 | } 236 | if (UAS_data.LocationValid) { 237 | UAV.lat_d = UAS_data.Location.Latitude; 238 | UAV.long_d = UAS_data.Location.Longitude; 239 | UAV.altitude_msl = (int)UAS_data.Location.AltitudeGeo; 240 | UAV.height_agl = (int)UAS_data.Location.Height; 241 | UAV.speed = (int)UAS_data.Location.SpeedHorizontal; 242 | UAV.heading = (int)UAS_data.Location.Direction; 243 | } 244 | if (UAS_data.SystemValid) { 245 | UAV.base_lat_d = UAS_data.System.OperatorLatitude; 246 | UAV.base_long_d = UAS_data.System.OperatorLongitude; 247 | } 248 | if (UAS_data.OperatorIDValid) { 249 | strncpy(UAV.op_id, (char *)UAS_data.OperatorID.OperatorId, ODID_ID_SIZE); 250 | } 251 | 252 | uav_data* dbUAV = next_uav(UAV.mac); 253 | memcpy(dbUAV, &UAV, sizeof(UAV)); 254 | dbUAV->flag = 1; 255 | } 256 | } 257 | offset += len + 2; 258 | } 259 | } 260 | } 261 | 262 | // BLE scanning task running on core 0 263 | void bleScanTask(void *parameter) { 264 | for(;;) { 265 | BLEScanResults* foundDevices = pBLEScan->start(1, false); 266 | pBLEScan->clearResults(); 267 | 268 | for (int i = 0; i < MAX_UAVS; i++) { 269 | if (uavs[i].flag) { 270 | send_json_fast(&uavs[i]); 271 | print_compact_message(&uavs[i]); 272 | uavs[i].flag = 0; 273 | } 274 | } 275 | 276 | unsigned long current_millis = millis(); 277 | if ((current_millis - last_status) > 60000UL) { 278 | Serial.println("{\"heartbeat\":\"Device is active and running.\"}"); 279 | last_status = current_millis; 280 | } 281 | 282 | delay(100); 283 | } 284 | } 285 | 286 | // Wi-Fi processing task running on core 1 287 | 288 | void wifiProcessTask(void *parameter) { 289 | for(;;) { 290 | for (int i = 0; i < MAX_UAVS; i++) { 291 | if (uavs[i].flag) { 292 | send_json_fast(&uavs[i]); 293 | print_compact_message(&uavs[i]); 294 | uavs[i].flag = 0; 295 | } 296 | } 297 | delay(10); 298 | } 299 | } 300 | 301 | // Task to forward incoming JSON from Serial1 (UART) to USB Serial 302 | void uartForwardTask(void *parameter) { 303 | for (;;) { 304 | while (Serial1.available()) { 305 | char c = Serial1.read(); 306 | Serial.write(c); 307 | } 308 | delay(3000); // 3-second polling interval for UART-to-USB echo 309 | } 310 | } 311 | 312 | void setup() { 313 | delay(6000); // 6-second boot delay (necessary for xiao meshtastic) 314 | setCpuFrequencyMhz(160); 315 | nvs_flash_init(); 316 | initializeSerial(); 317 | 318 | // Initialize Wi-Fi 319 | WiFi.mode(WIFI_STA); 320 | WiFi.disconnect(); 321 | 322 | esp_wifi_set_promiscuous(true); 323 | esp_wifi_set_promiscuous_rx_cb(&callback); 324 | esp_wifi_set_channel(6, WIFI_SECOND_CHAN_NONE); 325 | 326 | // Initialize BLE scanning 327 | BLEDevice::init("DroneID"); 328 | pBLEScan = BLEDevice::getScan(); 329 | pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); 330 | pBLEScan->setActiveScan(true); 331 | pBLEScan->setInterval(100); 332 | pBLEScan->setWindow(99); 333 | 334 | // Initialize UAV tracking array 335 | memset(uavs, 0, sizeof(uavs)); 336 | 337 | // Create tasks for BLE scanning and Wi-Fi processing on separate cores 338 | xTaskCreatePinnedToCore(bleScanTask, "BLEScanTask", 10000, NULL, 1, NULL, 0); 339 | xTaskCreatePinnedToCore(wifiProcessTask, "WiFiProcessTask", 10000, NULL, 1, NULL, 1); 340 | xTaskCreatePinnedToCore(uartForwardTask, "UARTForwardTask", 4096, NULL, 1, NULL, 1); 341 | } 342 | 343 | void loop() { 344 | // Main tasks are handled by the FreeRTOS tasks on separate cores 345 | } 346 | -------------------------------------------------------------------------------- /node-mode-dualcore/src/odid_wifi.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2019 Intel Corporation 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | Open Drone ID C Library 7 | 8 | Maintainer: 9 | Gabriel Cox 10 | gabriel.c.cox@intel.com 11 | */ 12 | 13 | #ifndef _ODID_WIFI_H_ 14 | #define _ODID_WIFI_H_ 15 | 16 | /** 17 | * IEEE 802.11 structs to build management action frame 18 | */ 19 | struct __attribute__((__packed__)) ieee80211_mgmt { 20 | uint16_t frame_control; 21 | uint16_t duration; 22 | uint8_t da[6]; 23 | uint8_t sa[6]; 24 | uint8_t bssid[6]; 25 | uint16_t seq_ctrl; 26 | }; 27 | 28 | struct __attribute__((__packed__)) ieee80211_beacon { 29 | uint64_t timestamp; 30 | uint16_t beacon_interval; 31 | uint16_t capability; 32 | }; 33 | 34 | struct __attribute__((__packed__)) ieee80211_ssid { 35 | uint8_t element_id; 36 | uint8_t length; 37 | uint8_t ssid[]; 38 | }; 39 | 40 | struct __attribute__((__packed__)) ieee80211_supported_rates { 41 | uint8_t element_id; 42 | uint8_t length; 43 | uint8_t supported_rates; 44 | }; 45 | 46 | struct __attribute__((__packed__)) ieee80211_vendor_specific { 47 | uint8_t element_id; 48 | uint8_t length; 49 | uint8_t oui[3]; 50 | uint8_t oui_type; 51 | }; 52 | 53 | struct __attribute__((__packed__)) nan_service_discovery { 54 | uint8_t category; 55 | uint8_t action_code; 56 | uint8_t oui[3]; 57 | uint8_t oui_type; 58 | }; 59 | 60 | struct __attribute__((__packed__)) nan_attribute_header { 61 | uint8_t attribute_id; 62 | uint16_t length; 63 | }; 64 | 65 | struct __attribute__((__packed__)) nan_master_indication_attribute { 66 | struct nan_attribute_header header; 67 | uint8_t master_preference; 68 | uint8_t random_factor; 69 | }; 70 | 71 | struct __attribute__((__packed__)) nan_cluster_attribute { 72 | struct nan_attribute_header header; 73 | uint8_t device_mac[6]; 74 | uint8_t random_factor; 75 | uint8_t master_preference; 76 | uint8_t hop_count_to_anchor_master; 77 | uint8_t anchor_master_beacon_transmission_time[4]; 78 | }; 79 | 80 | struct __attribute__((__packed__)) nan_service_id_list_attribute { 81 | struct nan_attribute_header header; 82 | uint8_t service_id[6]; 83 | }; 84 | 85 | struct __attribute__((__packed__)) nan_service_descriptor_attribute { 86 | struct nan_attribute_header header; 87 | uint8_t service_id[6]; 88 | uint8_t instance_id; 89 | uint8_t requestor_instance_id; 90 | uint8_t service_control; 91 | uint8_t service_info_length; 92 | }; 93 | 94 | struct __attribute__((__packed__)) nan_service_descriptor_extension_attribute { 95 | struct nan_attribute_header header; 96 | uint8_t instance_id; 97 | uint16_t control; 98 | uint8_t service_update_indicator; 99 | }; 100 | 101 | struct __attribute__((__packed__)) ODID_service_info { 102 | uint8_t message_counter; 103 | ODID_MessagePack_encoded odid_message_pack[]; 104 | }; 105 | 106 | #endif // _ODID_WIFI_H_ 107 | -------------------------------------------------------------------------------- /node-mode-dualcore/src/wifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2020 Simon Wunderlich, Marek Sobe 3 | Copyright (C) 2020 Doodle Labs 4 | 5 | SPDX-License-Identifier: Apache-2.0 6 | 7 | Open Drone ID C Library 8 | 9 | Maintainer: 10 | Simon Wunderlich 11 | sw@simonwunderlich.de 12 | */ 13 | #include 14 | #include 15 | #if defined(ARDUINO_ARCH_ESP32) 16 | #include 17 | int clock_gettime(clockid_t, struct timespec *); 18 | #else 19 | #include 20 | #include 21 | #include 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include "opendroneid.h" 28 | #include "odid_wifi.h" 29 | 30 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 31 | #if defined(IDF_VER) 32 | #include 33 | #define cpu_to_be16(x) (bswap16(x)) 34 | #define cpu_to_be32(x) (bswap32(x)) 35 | #else 36 | #include 37 | #define cpu_to_be16(x) (bswap_16(x)) 38 | #define cpu_to_be32(x) (bswap_32(x)) 39 | #endif 40 | #define cpu_to_le16(x) (x) 41 | #define cpu_to_le64(x) (x) 42 | #else 43 | #define cpu_to_be16(x) (x) 44 | #define cpu_to_be32(x) (x) 45 | #define cpu_to_le16(x) (bswap_16(x)) 46 | #define cpu_to_le64(x) (bswap_64(x)) 47 | #endif 48 | 49 | #define IEEE80211_FCTL_FTYPE 0x000c 50 | #define IEEE80211_FCTL_STYPE 0x00f0 51 | 52 | #define IEEE80211_FTYPE_MGMT 0x0000 53 | #define IEEE80211_STYPE_ACTION 0x00D0 54 | #define IEEE80211_STYPE_BEACON 0x0080 55 | 56 | /* IEEE 802.11-2016 capability info */ 57 | #define IEEE80211_CAPINFO_ESS 0x0001 58 | #define IEEE80211_CAPINFO_IBSS 0x0002 59 | #define IEEE80211_CAPINFO_CF_POLLABLE 0x0004 60 | #define IEEE80211_CAPINFO_CF_POLLREQ 0x0008 61 | #define IEEE80211_CAPINFO_PRIVACY 0x0010 62 | #define IEEE80211_CAPINFO_SHORT_PREAMBLE 0x0020 63 | /* bits 6-7 reserved */ 64 | #define IEEE80211_CAPINFO_SPECTRUM_MGMT 0x0100 65 | #define IEEE80211_CAPINFO_QOS 0x0200 66 | #define IEEE80211_CAPINFO_SHORT_SLOTTIME 0x0400 67 | #define IEEE80211_CAPINFO_APSD 0x0800 68 | #define IEEE80211_CAPINFO_RADIOMEAS 0x1000 69 | /* bit 13 reserved */ 70 | #define IEEE80211_CAPINFO_DEL_BLOCK_ACK 0x4000 71 | #define IEEE80211_CAPINFO_IMM_BLOCK_ACK 0x8000 72 | 73 | /* IEEE 802.11 Element IDs */ 74 | #define IEEE80211_ELEMID_SSID 0x00 75 | #define IEEE80211_ELEMID_RATES 0x01 76 | #define IEEE80211_ELEMID_VENDOR 0xDD 77 | 78 | /* Neighbor Awareness Networking Specification v3.1 in section 2.8.2 79 | * The NAN Cluster ID is a MAC address that takes a value from 80 | * 50-6F-9A-01-00-00 to 50-6F-9A-01-FF-FF and is carried in the A3 field of 81 | * some of the NAN frames. The NAN Cluster ID is randomly chosen by the device 82 | * that initiates the NAN Cluster. 83 | * However, the ASTM Remote ID specification v1.1 specifies that the NAN 84 | * cluster ID must be fixed to the value 50-6F-9A-01-00-FF. 85 | */ 86 | static const uint8_t *get_nan_cluster_id(void) 87 | { 88 | static const uint8_t cluster_id[6] = { 0x50, 0x6F, 0x9A, 0x01, 0x00, 0xFF }; 89 | return cluster_id; 90 | } 91 | 92 | static int buf_fill_ieee80211_mgmt(uint8_t *buf, size_t *len, size_t buf_size, 93 | const uint16_t subtype, 94 | const uint8_t *dst_addr, 95 | const uint8_t *src_addr, 96 | const uint8_t *bssid) 97 | { 98 | if (*len + sizeof(struct ieee80211_mgmt) > buf_size) 99 | return -ENOMEM; 100 | 101 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)(buf + *len); 102 | mgmt->frame_control = (uint16_t) cpu_to_le16(IEEE80211_FTYPE_MGMT | subtype); 103 | mgmt->duration = cpu_to_le16(0x0000); 104 | memcpy(mgmt->da, dst_addr, sizeof(mgmt->da)); 105 | memcpy(mgmt->sa, src_addr, sizeof(mgmt->sa)); 106 | memcpy(mgmt->bssid, bssid, sizeof(mgmt->bssid)); 107 | mgmt->seq_ctrl = cpu_to_le16(0x0000); 108 | *len += sizeof(*mgmt); 109 | 110 | return 0; 111 | } 112 | 113 | static int buf_fill_ieee80211_beacon(uint8_t *buf, size_t *len, size_t buf_size, uint16_t interval_tu) 114 | { 115 | if (*len + sizeof(struct ieee80211_beacon) > buf_size) 116 | return -ENOMEM; 117 | 118 | struct ieee80211_beacon *beacon = (struct ieee80211_beacon *)(buf + *len); 119 | struct timespec ts; 120 | uint64_t mono_us = 0; 121 | 122 | #if defined(CLOCK_MONOTONIC) 123 | clock_gettime(CLOCK_MONOTONIC, &ts); 124 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 125 | #elif defined(CLOCK_REALTIME) 126 | clock_gettime(CLOCK_REALTIME, &ts); 127 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 128 | #elif defined(ARDUINO) 129 | #warning "No REALTIME or MONOTONIC clock, using micros()." 130 | mono_us = micros(); 131 | #else 132 | #warning "Unable to set wifi timestamp." 133 | #endif 134 | beacon->timestamp = cpu_to_le64(mono_us); 135 | beacon->beacon_interval = cpu_to_le16(interval_tu); 136 | beacon->capability = cpu_to_le16(IEEE80211_CAPINFO_SHORT_SLOTTIME | IEEE80211_CAPINFO_SHORT_PREAMBLE); 137 | *len += sizeof(*beacon); 138 | 139 | return 0; 140 | } 141 | 142 | void drone_export_gps_data(ODID_UAS_Data *UAS_Data, char *buf, size_t buf_size) 143 | { 144 | ptrdiff_t len = 0; 145 | 146 | #define mprintf(...) {\ 147 | len += snprintf(buf + len, buf_size - (size_t)len, __VA_ARGS__); \ 148 | if ((len < 0) || ((size_t)len >= buf_size)) \ 149 | return; \ 150 | } 151 | 152 | mprintf("{\n\t\"Version\": \"1.1\",\n\t\"Response\": {\n"); 153 | 154 | mprintf("\t\t\"BasicID\": {\n"); 155 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 156 | if (!UAS_Data->BasicIDValid[i]) 157 | continue; 158 | mprintf("\t\t\t\"UAType%d\": %d,\n", i, UAS_Data->BasicID[i].UAType); 159 | mprintf("\t\t\t\"IDType%d\": %d,\n", i, UAS_Data->BasicID[i].IDType); 160 | mprintf("\t\t\t\"UASID%d\": \"%s\",\n", i, UAS_Data->BasicID[i].UASID); 161 | } 162 | mprintf("\t\t},\n"); 163 | 164 | mprintf("\t\t\"Location\": {\n"); 165 | mprintf("\t\t\t\"Status\": %d,\n", (int)UAS_Data->Location.Status); 166 | mprintf("\t\t\t\"Direction\": %f,\n", (double) UAS_Data->Location.Direction); 167 | mprintf("\t\t\t\"SpeedHorizontal\": %f,\n", (double) UAS_Data->Location.SpeedHorizontal); 168 | mprintf("\t\t\t\"SpeedVertical\": %f,\n", (double) UAS_Data->Location.SpeedVertical); 169 | mprintf("\t\t\t\"Latitude\": %f,\n", UAS_Data->Location.Latitude); 170 | mprintf("\t\t\t\"Longitude\": %f,\n", UAS_Data->Location.Longitude); 171 | mprintf("\t\t\t\"AltitudeBaro\": %f,\n", (double) UAS_Data->Location.AltitudeBaro); 172 | mprintf("\t\t\t\"AltitudeGeo\": %f,\n", (double) UAS_Data->Location.AltitudeGeo); 173 | mprintf("\t\t\t\"HeightType\": %d,\n", UAS_Data->Location.HeightType); 174 | mprintf("\t\t\t\"Height\": %f,\n", (double) UAS_Data->Location.Height); 175 | mprintf("\t\t\t\"HorizAccuracy\": %d,\n", UAS_Data->Location.HorizAccuracy); 176 | mprintf("\t\t\t\"VertAccuracy\": %d,\n", UAS_Data->Location.VertAccuracy); 177 | mprintf("\t\t\t\"BaroAccuracy\": %d,\n", UAS_Data->Location.BaroAccuracy); 178 | mprintf("\t\t\t\"SpeedAccuracy\": %d,\n", UAS_Data->Location.SpeedAccuracy); 179 | mprintf("\t\t\t\"TSAccuracy\": %d,\n", UAS_Data->Location.TSAccuracy); 180 | mprintf("\t\t\t\"TimeStamp\": %f,\n", (double) UAS_Data->Location.TimeStamp); 181 | mprintf("\t\t},\n"); 182 | 183 | mprintf("\t\t\"Authentication\": {\n"); 184 | mprintf("\t\t\t\"AuthType\": %d,\n", UAS_Data->Auth[0].AuthType); 185 | mprintf("\t\t\t\"LastPageIndex\": %d,\n", UAS_Data->Auth[0].LastPageIndex); 186 | mprintf("\t\t\t\"Length\": %d,\n", UAS_Data->Auth[0].Length); 187 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->Auth[0].Timestamp); 188 | for (int i = 0; i <= UAS_Data->Auth[0].LastPageIndex; i++) { 189 | mprintf("\t\t\t\"AuthData Page %d,\": \"%s\"\n", i, UAS_Data->Auth[i].AuthData); 190 | } 191 | mprintf("\t\t},\n"); 192 | 193 | mprintf("\t\t\"SelfID\": {\n"); 194 | mprintf("\t\t\t\"Description Type\": %d,\n", UAS_Data->SelfID.DescType); 195 | mprintf("\t\t\t\"Description\": \"%s\",\n", UAS_Data->SelfID.Desc); 196 | mprintf("\t\t},\n"); 197 | 198 | mprintf("\t\t\"Operator\": {\n"); 199 | mprintf("\t\t\t\"OperatorLocationType\": %d,\n", UAS_Data->System.OperatorLocationType); 200 | mprintf("\t\t\t\"ClassificationType\": %d,\n", UAS_Data->System.ClassificationType); 201 | mprintf("\t\t\t\"OperatorLatitude\": %f,\n", UAS_Data->System.OperatorLatitude); 202 | mprintf("\t\t\t\"OperatorLongitude\": %f,\n", UAS_Data->System.OperatorLongitude); 203 | mprintf("\t\t\t\"AreaCount\": %d,\n", UAS_Data->System.AreaCount); 204 | mprintf("\t\t\t\"AreaRadius\": %d,\n", UAS_Data->System.AreaRadius); 205 | mprintf("\t\t\t\"AreaCeiling\": %f,\n", (double) UAS_Data->System.AreaCeiling); 206 | mprintf("\t\t\t\"AreaFloor\": %f,\n", (double) UAS_Data->System.AreaFloor); 207 | mprintf("\t\t\t\"CategoryEU\": %d,\n", UAS_Data->System.CategoryEU); 208 | mprintf("\t\t\t\"ClassEU\": %d,\n", UAS_Data->System.ClassEU); 209 | mprintf("\t\t\t\"OperatorAltitudeGeo\": %f,\n", (double) UAS_Data->System.OperatorAltitudeGeo); 210 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->System.Timestamp); 211 | mprintf("\t\t}\n"); 212 | 213 | mprintf("\t\t\"OperatorID\": {\n"); 214 | mprintf("\t\t\t\"OperatorIdType\": %d,\n", UAS_Data->OperatorID.OperatorIdType); 215 | mprintf("\t\t\t\"OperatorId\": \"%s\",\n", UAS_Data->OperatorID.OperatorId); 216 | mprintf("\t\t},\n"); 217 | 218 | mprintf("\t}\n}"); 219 | } 220 | 221 | int odid_message_build_pack(ODID_UAS_Data *UAS_Data, void *pack, size_t buflen) 222 | { 223 | ODID_MessagePack_data msg_pack; 224 | ODID_MessagePack_encoded *msg_pack_enc; 225 | size_t len; 226 | 227 | /* create a complete message pack */ 228 | msg_pack.SingleMessageSize = ODID_MESSAGE_SIZE; 229 | msg_pack.MsgPackSize = 0; 230 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 231 | if (UAS_Data->BasicIDValid[i]) { 232 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 233 | return -EINVAL; 234 | if (encodeBasicIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->BasicID[i]) == ODID_SUCCESS) 235 | msg_pack.MsgPackSize++; 236 | } 237 | } 238 | if (UAS_Data->LocationValid) { 239 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 240 | return -EINVAL; 241 | if (encodeLocationMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Location) == ODID_SUCCESS) 242 | msg_pack.MsgPackSize++; 243 | } 244 | for (int i = 0; i < ODID_AUTH_MAX_PAGES; i++) 245 | { 246 | if (UAS_Data->AuthValid[i]) { 247 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 248 | return -EINVAL; 249 | if (encodeAuthMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Auth[i]) == ODID_SUCCESS) 250 | msg_pack.MsgPackSize++; 251 | } 252 | } 253 | if (UAS_Data->SelfIDValid) { 254 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 255 | return -EINVAL; 256 | if (encodeSelfIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->SelfID) == ODID_SUCCESS) 257 | msg_pack.MsgPackSize++; 258 | } 259 | if (UAS_Data->SystemValid) { 260 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 261 | return -EINVAL; 262 | if (encodeSystemMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->System) == ODID_SUCCESS) 263 | msg_pack.MsgPackSize++; 264 | } 265 | if (UAS_Data->OperatorIDValid) { 266 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 267 | return -EINVAL; 268 | if (encodeOperatorIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->OperatorID) == ODID_SUCCESS) 269 | msg_pack.MsgPackSize++; 270 | } 271 | 272 | /* check that there is at least one message to send. */ 273 | if (msg_pack.MsgPackSize == 0) 274 | return -EINVAL; 275 | 276 | /* calculate the exact encoded message pack size. */ 277 | len = sizeof(*msg_pack_enc) - (ODID_PACK_MAX_MESSAGES - msg_pack.MsgPackSize) * ODID_MESSAGE_SIZE; 278 | 279 | /* check if there is enough space for the message pack. */ 280 | if (len > buflen) 281 | return -ENOMEM; 282 | 283 | msg_pack_enc = (ODID_MessagePack_encoded *) pack; 284 | if (encodeMessagePack(msg_pack_enc, &msg_pack) != ODID_SUCCESS) 285 | return -1; 286 | 287 | return (int) len; 288 | } 289 | 290 | int odid_wifi_build_nan_sync_beacon_frame(char *mac, uint8_t *buf, size_t buf_size) 291 | { 292 | /* Broadcast address */ 293 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 294 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 295 | /* "org.opendroneid.remoteid" hash */ 296 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 297 | const uint8_t *cluster_id = get_nan_cluster_id(); 298 | struct ieee80211_vendor_specific *vendor; 299 | struct nan_master_indication_attribute *master_indication_attr; 300 | struct nan_cluster_attribute *cluster_attr; 301 | struct nan_service_id_list_attribute *nsila; 302 | int ret; 303 | size_t len = 0; 304 | 305 | /* IEEE 802.11 Management Header */ 306 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, cluster_id); 307 | if (ret <0) 308 | return ret; 309 | 310 | /* Beacon */ 311 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, 0x0200); 312 | if (ret <0) 313 | return ret; 314 | 315 | /* Vendor Specific */ 316 | if (len + sizeof(*vendor) > buf_size) 317 | return -ENOMEM; 318 | 319 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 320 | memset(vendor, 0, sizeof(*vendor)); 321 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 322 | vendor->length = 0x22; 323 | memcpy(vendor->oui, wifi_alliance_oui, sizeof(vendor->oui)); 324 | vendor->oui_type = 0x13; 325 | len += sizeof(*vendor); 326 | 327 | /* NAN Master Indication attribute */ 328 | if (len + sizeof(*master_indication_attr) > buf_size) 329 | return -ENOMEM; 330 | 331 | master_indication_attr = (struct nan_master_indication_attribute *)(buf + len); 332 | memset(master_indication_attr, 0, sizeof(*master_indication_attr)); 333 | master_indication_attr->header.attribute_id = 0x00; 334 | master_indication_attr->header.length = cpu_to_le16(0x0002); 335 | /* Information that is used to indicate a NAN Device’s preference to serve 336 | * as the role of Master, with a larger value indicating a higher 337 | * preference. Values 1 and 255 are used for testing purposes only. 338 | */ 339 | master_indication_attr->master_preference = 0xFE; 340 | /* Random factor value 0xEA is recommended by the European Standard */ 341 | master_indication_attr->random_factor = 0xEA; 342 | len += sizeof(*master_indication_attr); 343 | 344 | /* NAN Cluster attribute */ 345 | if (len + sizeof(*cluster_attr) > buf_size) 346 | return -ENOMEM; 347 | 348 | cluster_attr = (struct nan_cluster_attribute *)(buf + len); 349 | memset(cluster_attr, 0, sizeof(*cluster_attr)); 350 | cluster_attr->header.attribute_id = 0x1; 351 | cluster_attr->header.length = cpu_to_le16(0x000D); 352 | memcpy(cluster_attr->device_mac, mac, sizeof(cluster_attr->device_mac)); 353 | cluster_attr->random_factor = 0xEA; 354 | cluster_attr->master_preference = 0xFE; 355 | cluster_attr->hop_count_to_anchor_master = 0x00; 356 | memset(cluster_attr->anchor_master_beacon_transmission_time, 0, sizeof(cluster_attr->anchor_master_beacon_transmission_time)); 357 | len += sizeof(*cluster_attr); 358 | 359 | /* NAN attributes */ 360 | if (len + sizeof(*nsila) > buf_size) 361 | return -ENOMEM; 362 | 363 | nsila = (struct nan_service_id_list_attribute *)(buf + len); 364 | memset(nsila, 0, sizeof(*nsila)); 365 | nsila->header.attribute_id = 0x02; 366 | nsila->header.length = cpu_to_le16(0x0006); 367 | memcpy(nsila->service_id, service_id, sizeof(service_id)); 368 | len += sizeof(*nsila); 369 | 370 | return (int) len; 371 | } 372 | 373 | int odid_wifi_build_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, char *mac, 374 | uint8_t send_counter, 375 | uint8_t *buf, size_t buf_size) 376 | { 377 | /* Neighbor Awareness Networking Specification v3.0 in section 2.8.1 378 | * NAN Network ID calls for the destination mac to be 51-6F-9A-01-00-00 */ 379 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 380 | /* "org.opendroneid.remoteid" hash */ 381 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 382 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 383 | const uint8_t *cluster_id = get_nan_cluster_id(); 384 | struct nan_service_discovery *nsd; 385 | struct nan_service_descriptor_attribute *nsda; 386 | struct nan_service_descriptor_extension_attribute *nsdea; 387 | struct ODID_service_info *si; 388 | int ret; 389 | size_t len = 0; 390 | 391 | /* IEEE 802.11 Management Header */ 392 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_ACTION, target_addr, (uint8_t *)mac, cluster_id); 393 | if (ret <0) 394 | return ret; 395 | 396 | /* NAN Service Discovery header */ 397 | if (len + sizeof(*nsd) > buf_size) 398 | return -ENOMEM; 399 | 400 | nsd = (struct nan_service_discovery *)(buf + len); 401 | memset(nsd, 0, sizeof(*nsd)); 402 | nsd->category = 0x04; /* IEEE 802.11 Public Action frame */ 403 | nsd->action_code = 0x09; /* IEEE 802.11 Public Action frame Vendor Specific*/ 404 | memcpy(nsd->oui, wifi_alliance_oui, sizeof(nsd->oui)); 405 | nsd->oui_type = 0x13; /* Identify Type and version of the NAN */ 406 | len += sizeof(*nsd); 407 | 408 | /* NAN Attribute for Service Descriptor header */ 409 | if (len + sizeof(*nsda) > buf_size) 410 | return -ENOMEM; 411 | 412 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 413 | nsda->header.attribute_id = 0x3; /* Service Descriptor Attribute type */ 414 | memcpy(nsda->service_id, service_id, sizeof(service_id)); 415 | /* always 1 */ 416 | nsda->instance_id = 0x01; /* always 1 */ 417 | nsda->requestor_instance_id = 0x00; /* from triggering frame */ 418 | nsda->service_control = 0x10; /* follow up */ 419 | len += sizeof(*nsda); 420 | 421 | /* ODID Service Info Attribute header */ 422 | if (len + sizeof(*si) > buf_size) 423 | return -ENOMEM; 424 | 425 | si = (struct ODID_service_info *)(buf + len); 426 | memset(si, 0, sizeof(*si)); 427 | si->message_counter = send_counter; 428 | len += sizeof(*si); 429 | 430 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 431 | if (ret < 0) 432 | return ret; 433 | len += ret; 434 | 435 | /* set the lengths according to the message pack lengths */ 436 | nsda->service_info_length = sizeof(*si) + ret; 437 | nsda->header.length = cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length); 438 | 439 | /* NAN Attribute for Service Descriptor extension header */ 440 | if (len + sizeof(*nsdea) > buf_size) 441 | return -ENOMEM; 442 | 443 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 444 | nsdea->header.attribute_id = 0xE; 445 | nsdea->header.length = cpu_to_le16(0x0004); 446 | nsdea->instance_id = 0x01; 447 | nsdea->control = cpu_to_le16(0x0200); 448 | nsdea->service_update_indicator = send_counter; 449 | len += sizeof(*nsdea); 450 | 451 | return (int) len; 452 | } 453 | 454 | int odid_wifi_build_message_pack_beacon_frame(ODID_UAS_Data *UAS_Data, char *mac, 455 | const char *SSID, size_t SSID_len, 456 | uint16_t interval_tu, uint8_t send_counter, 457 | uint8_t *buf, size_t buf_size) 458 | { 459 | /* Broadcast address */ 460 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 461 | uint8_t asd_stan_oui[3] = { 0xFA, 0x0B, 0xBC }; 462 | /* Mgmt Beacon frame mandatory fields + IE 221 */ 463 | struct ieee80211_ssid *ssid_s; 464 | struct ieee80211_supported_rates *rates; 465 | struct ieee80211_vendor_specific *vendor; 466 | 467 | /* Message Pack */ 468 | struct ODID_service_info *si; 469 | 470 | int ret; 471 | size_t len = 0; 472 | 473 | /* IEEE 802.11 Management Header */ 474 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, (uint8_t *)mac); 475 | if (ret <0) 476 | return ret; 477 | 478 | /* Mandatory Beacon as of 802.11-2016 Part 11 */ 479 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, interval_tu); 480 | if (ret <0) 481 | return ret; 482 | 483 | /* SSID: 1-32 bytes */ 484 | if (len + sizeof(*ssid_s) > buf_size) 485 | return -ENOMEM; 486 | 487 | ssid_s = (struct ieee80211_ssid *)(buf + len); 488 | if(!SSID || (SSID_len ==0) || (SSID_len > 32)) 489 | return -EINVAL; 490 | ssid_s->element_id = IEEE80211_ELEMID_SSID; 491 | ssid_s->length = (uint8_t) SSID_len; 492 | memcpy(ssid_s->ssid, SSID, ssid_s->length); 493 | len += sizeof(*ssid_s) + SSID_len; 494 | 495 | /* Supported Rates: 1 record at minimum */ 496 | if (len + sizeof(*rates) > buf_size) 497 | return -ENOMEM; 498 | 499 | rates = (struct ieee80211_supported_rates *)(buf + len); 500 | rates->element_id = IEEE80211_ELEMID_RATES; 501 | rates->length = 1; // One rate only 502 | rates->supported_rates = 0x8C; // 6 Mbps 503 | len += sizeof(*rates); 504 | 505 | /* Vendor Specific Information Element (IE 221) */ 506 | if (len + sizeof(*vendor) > buf_size) 507 | return -ENOMEM; 508 | 509 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 510 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 511 | vendor->length = 0x00; // Length updated at end of function 512 | memcpy(vendor->oui, asd_stan_oui, sizeof(vendor->oui)); 513 | vendor->oui_type = 0x0D; 514 | len += sizeof(*vendor); 515 | 516 | /* ODID Service Info Attribute header */ 517 | if (len + sizeof(*si) > buf_size) 518 | return -ENOMEM; 519 | 520 | si = (struct ODID_service_info *)(buf + len); 521 | memset(si, 0, sizeof(*si)); 522 | si->message_counter = send_counter; 523 | len += sizeof(*si); 524 | 525 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 526 | if (ret < 0) 527 | return ret; 528 | len += ret; 529 | 530 | /* set the lengths according to the message pack lengths */ 531 | vendor->length = sizeof(vendor->oui) + sizeof(vendor->oui_type) + sizeof(*si) + ret; 532 | 533 | return (int) len; 534 | } 535 | 536 | int odid_message_process_pack(ODID_UAS_Data *UAS_Data, uint8_t *pack, size_t buflen) 537 | { 538 | ODID_MessagePack_encoded *msg_pack_enc = (ODID_MessagePack_encoded *) pack; 539 | size_t size = sizeof(*msg_pack_enc) - ODID_MESSAGE_SIZE * (ODID_PACK_MAX_MESSAGES - msg_pack_enc->MsgPackSize); 540 | if (size > buflen) 541 | return -ENOMEM; 542 | 543 | odid_initUasData(UAS_Data); 544 | 545 | if (decodeMessagePack(UAS_Data, msg_pack_enc) != ODID_SUCCESS) 546 | return -1; 547 | 548 | return (int) size; 549 | } 550 | 551 | int odid_wifi_receive_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, 552 | char *mac, uint8_t *buf, size_t buf_size) 553 | { 554 | struct ieee80211_mgmt *mgmt; 555 | struct nan_service_discovery *nsd; 556 | struct nan_service_descriptor_attribute *nsda; 557 | struct nan_service_descriptor_extension_attribute *nsdea; 558 | struct ODID_service_info *si; 559 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 560 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 561 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 562 | int ret; 563 | size_t len = 0; 564 | 565 | /* IEEE 802.11 Management Header */ 566 | if (len + sizeof(*mgmt) > buf_size) 567 | return -EINVAL; 568 | mgmt = (struct ieee80211_mgmt *)(buf + len); 569 | if ((mgmt->frame_control & cpu_to_le16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) != 570 | cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION)) 571 | return -EINVAL; 572 | if (memcmp(mgmt->da, target_addr, sizeof(mgmt->da)) != 0) 573 | return -EINVAL; 574 | memcpy(mac, mgmt->sa, sizeof(mgmt->sa)); 575 | 576 | len += sizeof(*mgmt); 577 | 578 | /* NAN Service Discovery header */ 579 | if (len + sizeof(*nsd) > buf_size) 580 | return -EINVAL; 581 | nsd = (struct nan_service_discovery *)(buf + len); 582 | if (nsd->category != 0x04) 583 | return -EINVAL; 584 | if (nsd->action_code != 0x09) 585 | return -EINVAL; 586 | if (memcmp(nsd->oui, wifi_alliance_oui, sizeof(wifi_alliance_oui)) != 0) 587 | return -EINVAL; 588 | if (nsd->oui_type != 0x13) 589 | return -EINVAL; 590 | len += sizeof(*nsd); 591 | 592 | /* NAN Attribute for Service Descriptor header */ 593 | if (len + sizeof(*nsda) > buf_size) 594 | return -EINVAL; 595 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 596 | if (nsda->header.attribute_id != 0x3) 597 | return -EINVAL; 598 | if (memcmp(nsda->service_id, service_id, sizeof(service_id)) != 0) 599 | return -EINVAL; 600 | if (nsda->instance_id != 0x01) 601 | return -EINVAL; 602 | if (nsda->service_control != 0x10) 603 | return -EINVAL; 604 | len += sizeof(*nsda); 605 | 606 | si = (struct ODID_service_info *)(buf + len); 607 | ret = odid_message_process_pack(UAS_Data, buf + len + sizeof(*si), buf_size - len - sizeof(*nsdea)); 608 | if (ret < 0) 609 | return -EINVAL; 610 | if (nsda->service_info_length != (sizeof(*si) + ret)) 611 | return -EINVAL; 612 | if (nsda->header.length != (cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length))) 613 | return -EINVAL; 614 | len += sizeof(*si) + ret; 615 | 616 | /* NAN Attribute for Service Descriptor extension header */ 617 | if (len + sizeof(*nsdea) > buf_size) 618 | return -ENOMEM; 619 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 620 | if (nsdea->header.attribute_id != 0xE) 621 | return -EINVAL; 622 | if (nsdea->header.length != cpu_to_le16(0x0004)) 623 | return -EINVAL; 624 | if (nsdea->instance_id != 0x01) 625 | return -EINVAL; 626 | if (nsdea->control != cpu_to_le16(0x0200)) 627 | return -EINVAL; 628 | 629 | return 0; 630 | } -------------------------------------------------------------------------------- /node-mode-dualcore/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/remoteid-mesh-dualcore/firmware.bin -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the convention is to give header files names that end with `.h'. 29 | 30 | Read more about using header files in official GCC documentation: 31 | 32 | * Include Syntax 33 | * Include Operation 34 | * Once-Only Headers 35 | * Computed Includes 36 | 37 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 38 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into the executable file. 4 | 5 | The source code of each library should be placed in a separate directory 6 | ("lib/your_library_name/[Code]"). 7 | 8 | For example, see the structure of the following example libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Example contents of `src/main.c` using Foo and Bar: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | The PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries by scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/platformio.ini: -------------------------------------------------------------------------------- 1 | [env:seeed_xiao_esp32c6] 2 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip 3 | framework = arduino 4 | board = seeed_xiao_esp32c6 5 | monitor_speed = 115200 6 | build_flags = -std=gnu++17 7 | lib_deps = 8 | bblanchon/ArduinoJson@^6.18.5 9 | 10 | 11 | [env:seeed_xiao_esp32s3] 12 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip 13 | framework = arduino 14 | board = seeed_xiao_esp32s3 15 | monitor_speed = 115200 16 | build_flags = -std=gnu++17 17 | lib_deps = 18 | bblanchon/ArduinoJson@^6.18.5 -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/src/main.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(ARDUINO_ARCH_ESP32) 2 | #error "This program requires an ESP32S3" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "opendroneid.h" 15 | #include "odid_wifi.h" 16 | #include 17 | #include 18 | #include 19 | 20 | const int SERIAL1_RX_PIN = 6; 21 | const int SERIAL1_TX_PIN = 5; 22 | 23 | struct id_data { 24 | uint8_t mac[6]; 25 | int rssi; 26 | uint32_t last_seen; 27 | char op_id[ODID_ID_SIZE + 1]; 28 | char uav_id[ODID_ID_SIZE + 1]; 29 | double lat_d; 30 | double long_d; 31 | double base_lat_d; 32 | double base_long_d; 33 | int altitude_msl; 34 | int height_agl; 35 | int speed; 36 | int heading; 37 | int flag; 38 | }; 39 | 40 | void callback(void *, wifi_promiscuous_pkt_type_t); 41 | void send_json_fast(const id_data *UAV); 42 | void print_compact_message(const id_data *UAV); 43 | 44 | #define MAX_UAVS 8 45 | id_data uavs[MAX_UAVS] = {0}; 46 | BLEScan* pBLEScan = nullptr; 47 | ODID_UAS_Data UAS_data; 48 | unsigned long last_status = 0; 49 | 50 | static QueueHandle_t printQueue; 51 | 52 | id_data* next_uav(uint8_t* mac) { 53 | for (int i = 0; i < MAX_UAVS; i++) { 54 | if (memcmp(uavs[i].mac, mac, 6) == 0) 55 | return &uavs[i]; 56 | } 57 | for (int i = 0; i < MAX_UAVS; i++) { 58 | if (uavs[i].mac[0] == 0) 59 | return &uavs[i]; 60 | } 61 | return &uavs[0]; 62 | } 63 | 64 | class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { 65 | public: 66 | void onResult(BLEAdvertisedDevice device) override { 67 | int len = device.getPayloadLength(); 68 | if (len <= 0) return; 69 | 70 | uint8_t* payload = device.getPayload(); 71 | if (len > 5 && payload[1] == 0x16 && payload[2] == 0xFA && 72 | payload[3] == 0xFF && payload[4] == 0x0D) { 73 | uint8_t* mac = (uint8_t*) device.getAddress().getNative(); 74 | id_data* UAV = next_uav(mac); 75 | UAV->last_seen = millis(); 76 | UAV->rssi = device.getRSSI(); 77 | memcpy(UAV->mac, mac, 6); 78 | 79 | uint8_t* odid = &payload[6]; 80 | switch (odid[0] & 0xF0) { 81 | case 0x00: { 82 | ODID_BasicID_data basic; 83 | decodeBasicIDMessage(&basic, (ODID_BasicID_encoded*) odid); 84 | strncpy(UAV->uav_id, (char*) basic.UASID, ODID_ID_SIZE); 85 | break; 86 | } 87 | case 0x10: { 88 | ODID_Location_data loc; 89 | decodeLocationMessage(&loc, (ODID_Location_encoded*) odid); 90 | UAV->lat_d = loc.Latitude; 91 | UAV->long_d = loc.Longitude; 92 | UAV->altitude_msl = (int) loc.AltitudeGeo; 93 | UAV->height_agl = (int) loc.Height; 94 | UAV->speed = (int) loc.SpeedHorizontal; 95 | UAV->heading = (int) loc.Direction; 96 | break; 97 | } 98 | case 0x40: { 99 | ODID_System_data sys; 100 | decodeSystemMessage(&sys, (ODID_System_encoded*) odid); 101 | UAV->base_lat_d = sys.OperatorLatitude; 102 | UAV->base_long_d = sys.OperatorLongitude; 103 | break; 104 | } 105 | case 0x50: { 106 | ODID_OperatorID_data op; 107 | decodeOperatorIDMessage(&op, (ODID_OperatorID_encoded*) odid); 108 | strncpy(UAV->op_id, (char*) op.OperatorId, ODID_ID_SIZE); 109 | break; 110 | } 111 | } 112 | UAV->flag = 1; 113 | { 114 | id_data tmp = *UAV; 115 | BaseType_t xHigherPriorityTaskWoken = pdFALSE; 116 | xQueueSendFromISR(printQueue, &tmp, &xHigherPriorityTaskWoken); 117 | if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); 118 | } 119 | } 120 | } 121 | }; 122 | 123 | void send_json_fast(const id_data *UAV) { 124 | char mac_str[18]; 125 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 126 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 127 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 128 | char json_msg[256]; 129 | snprintf(json_msg, sizeof(json_msg), 130 | "{\"mac\":\"%s\",\"rssi\":%d,\"drone_lat\":%.6f,\"drone_long\":%.6f,\"drone_altitude\":%d,\"pilot_lat\":%.6f,\"pilot_long\":%.6f,\"basic_id\":\"%s\"}", 131 | mac_str, UAV->rssi, UAV->lat_d, UAV->long_d, UAV->altitude_msl, 132 | UAV->base_lat_d, UAV->base_long_d, UAV->uav_id); 133 | Serial.println(json_msg); 134 | } 135 | 136 | void print_compact_message(const id_data *UAV) { 137 | static unsigned long lastSendTime = 0; 138 | const unsigned long sendInterval = 5000; 139 | const int MAX_MESH_SIZE = 230; 140 | 141 | if (millis() - lastSendTime < sendInterval) return; 142 | lastSendTime = millis(); 143 | 144 | char mac_str[18]; 145 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 146 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 147 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 148 | 149 | char mesh_msg[MAX_MESH_SIZE]; 150 | int msg_len = 0; 151 | msg_len += snprintf(mesh_msg + msg_len, sizeof(mesh_msg) - msg_len, 152 | "Drone: %s RSSI:%d", mac_str, UAV->rssi); 153 | if (msg_len < MAX_MESH_SIZE && UAV->lat_d != 0.0 && UAV->long_d != 0.0) { 154 | msg_len += snprintf(mesh_msg + msg_len, sizeof(mesh_msg) - msg_len, 155 | " https://maps.google.com/?q=%.6f,%.6f", 156 | UAV->lat_d, UAV->long_d, UAV->uav_id); 157 | } 158 | if (Serial1.availableForWrite() >= msg_len) { 159 | Serial1.println(mesh_msg); 160 | } 161 | 162 | delay(1000); 163 | if (UAV->base_lat_d != 0.0 && UAV->base_long_d != 0.0) { 164 | char pilot_msg[MAX_MESH_SIZE]; 165 | int pilot_len = snprintf(pilot_msg, sizeof(pilot_msg), 166 | "Pilot: https://maps.google.com/?q=%.6f,%.6f", 167 | UAV->base_lat_d, UAV->base_long_d); 168 | if (Serial1.availableForWrite() >= pilot_len) { 169 | Serial1.println(pilot_msg); 170 | } 171 | } 172 | } 173 | 174 | void bleScanTask(void *parameter) { 175 | for (;;) { 176 | BLEScanResults* foundDevices = pBLEScan->start(1, false); 177 | pBLEScan->clearResults(); 178 | for (int i = 0; i < MAX_UAVS; i++) { 179 | if (uavs[i].flag) { 180 | // Removed send_json_fast and print_compact_message calls here 181 | uavs[i].flag = 0; 182 | } 183 | } 184 | delay(100); 185 | } 186 | } 187 | 188 | void wifiProcessTask(void *parameter) { 189 | for (;;) { 190 | // No-op: callback sets uavs[].flag and data, so nothing needed here 191 | delay(10); 192 | } 193 | } 194 | 195 | void callback(void *buffer, wifi_promiscuous_pkt_type_t type) { 196 | if (type != WIFI_PKT_MGMT) return; 197 | 198 | wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t *)buffer; 199 | uint8_t *payload = packet->payload; 200 | int length = packet->rx_ctrl.sig_len; 201 | 202 | static const uint8_t nan_dest[6] = {0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00}; 203 | if (memcmp(nan_dest, &payload[4], 6) == 0) { 204 | if (odid_wifi_receive_message_pack_nan_action_frame(&UAS_data, nullptr, payload, length) == 0) { 205 | id_data UAV; 206 | memset(&UAV, 0, sizeof(UAV)); 207 | memcpy(UAV.mac, &payload[10], 6); 208 | UAV.rssi = packet->rx_ctrl.rssi; 209 | UAV.last_seen = millis(); 210 | 211 | if (UAS_data.BasicIDValid[0]) { 212 | strncpy(UAV.uav_id, (char *)UAS_data.BasicID[0].UASID, ODID_ID_SIZE); 213 | } 214 | if (UAS_data.LocationValid) { 215 | UAV.lat_d = UAS_data.Location.Latitude; 216 | UAV.long_d = UAS_data.Location.Longitude; 217 | UAV.altitude_msl = (int)UAS_data.Location.AltitudeGeo; 218 | UAV.height_agl = (int)UAS_data.Location.Height; 219 | UAV.speed = (int)UAS_data.Location.SpeedHorizontal; 220 | UAV.heading = (int)UAS_data.Location.Direction; 221 | } 222 | if (UAS_data.SystemValid) { 223 | UAV.base_lat_d = UAS_data.System.OperatorLatitude; 224 | UAV.base_long_d = UAS_data.System.OperatorLongitude; 225 | } 226 | if (UAS_data.OperatorIDValid) { 227 | strncpy(UAV.op_id, (char *)UAS_data.OperatorID.OperatorId, ODID_ID_SIZE); 228 | } 229 | 230 | id_data* storedUAV = next_uav(UAV.mac); 231 | *storedUAV = UAV; 232 | storedUAV->flag = 1; 233 | { 234 | id_data tmp = *storedUAV; 235 | BaseType_t xHigherPriorityTaskWoken = pdFALSE; 236 | xQueueSendFromISR(printQueue, &tmp, &xHigherPriorityTaskWoken); 237 | if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); 238 | } 239 | } 240 | } 241 | else if (payload[0] == 0x80) { 242 | int offset = 36; 243 | while (offset < length) { 244 | int typ = payload[offset]; 245 | int len = payload[offset + 1]; 246 | if ((typ == 0xdd) && 247 | (((payload[offset + 2] == 0x90 && payload[offset + 3] == 0x3a && payload[offset + 4] == 0xe6)) || 248 | ((payload[offset + 2] == 0xfa && payload[offset + 3] == 0x0b && payload[offset + 4] == 0xbc)))) { 249 | int j = offset + 7; 250 | if (j < length) { 251 | memset(&UAS_data, 0, sizeof(UAS_data)); 252 | odid_message_process_pack(&UAS_data, &payload[j], length - j); 253 | 254 | id_data UAV; 255 | memset(&UAV, 0, sizeof(UAV)); 256 | memcpy(UAV.mac, &payload[10], 6); 257 | UAV.rssi = packet->rx_ctrl.rssi; 258 | UAV.last_seen = millis(); 259 | 260 | if (UAS_data.BasicIDValid[0]) { 261 | strncpy(UAV.uav_id, (char *)UAS_data.BasicID[0].UASID, ODID_ID_SIZE); 262 | } 263 | if (UAS_data.LocationValid) { 264 | UAV.lat_d = UAS_data.Location.Latitude; 265 | UAV.long_d = UAS_data.Location.Longitude; 266 | UAV.altitude_msl = (int)UAS_data.Location.AltitudeGeo; 267 | UAV.height_agl = (int)UAS_data.Location.Height; 268 | UAV.speed = (int)UAS_data.Location.SpeedHorizontal; 269 | UAV.heading = (int)UAS_data.Location.Direction; 270 | } 271 | if (UAS_data.SystemValid) { 272 | UAV.base_lat_d = UAS_data.System.OperatorLatitude; 273 | UAV.base_long_d = UAS_data.System.OperatorLongitude; 274 | } 275 | if (UAS_data.OperatorIDValid) { 276 | strncpy(UAV.op_id, (char *)UAS_data.OperatorID.OperatorId, ODID_ID_SIZE); 277 | } 278 | 279 | id_data* storedUAV = next_uav(UAV.mac); 280 | *storedUAV = UAV; 281 | storedUAV->flag = 1; 282 | { 283 | id_data tmp = *storedUAV; 284 | BaseType_t xHigherPriorityTaskWoken = pdFALSE; 285 | xQueueSendFromISR(printQueue, &tmp, &xHigherPriorityTaskWoken); 286 | if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); 287 | } 288 | } 289 | } 290 | offset += len + 2; 291 | } 292 | } 293 | } 294 | 295 | void printerTask(void *param) { 296 | id_data UAV; 297 | for (;;) { 298 | if (xQueueReceive(printQueue, &UAV, portMAX_DELAY)) { 299 | send_json_fast(&UAV); 300 | print_compact_message(&UAV); 301 | // no need to reset flag on copy 302 | } 303 | } 304 | } 305 | 306 | void initializeSerial() { 307 | Serial.begin(115200); 308 | Serial1.begin(115200, SERIAL_8N1, SERIAL1_RX_PIN, SERIAL1_TX_PIN); 309 | } 310 | 311 | void setup() { 312 | setCpuFrequencyMhz(160); 313 | initializeSerial(); 314 | nvs_flash_init(); 315 | 316 | WiFi.mode(WIFI_STA); 317 | WiFi.disconnect(); 318 | 319 | esp_wifi_set_promiscuous(true); 320 | esp_wifi_set_promiscuous_rx_cb(&callback); 321 | esp_wifi_set_channel(6, WIFI_SECOND_CHAN_NONE); 322 | 323 | BLEDevice::init("DroneID"); 324 | pBLEScan = BLEDevice::getScan(); 325 | pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); 326 | pBLEScan->setActiveScan(true); 327 | 328 | printQueue = xQueueCreate(MAX_UAVS, sizeof(id_data)); 329 | 330 | xTaskCreatePinnedToCore(bleScanTask, "BLEScanTask", 10000, NULL, 1, NULL, 1); 331 | xTaskCreatePinnedToCore(wifiProcessTask, "WiFiProcessTask", 10000, NULL, 1, NULL, 0); 332 | xTaskCreatePinnedToCore(printerTask, "PrinterTask", 10000, NULL, 1, NULL, 1); 333 | 334 | memset(uavs, 0, sizeof(uavs)); 335 | } 336 | 337 | void loop() { 338 | unsigned long current_millis = millis(); 339 | if ((current_millis - last_status) > 60000UL) { 340 | Serial.println("{\" [+] Device is active and scanning...\"}"); 341 | last_status = current_millis; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/src/odid_wifi.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2019 Intel Corporation 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | Open Drone ID C Library 7 | 8 | Maintainer: 9 | Gabriel Cox 10 | gabriel.c.cox@intel.com 11 | */ 12 | 13 | #ifndef _ODID_WIFI_H_ 14 | #define _ODID_WIFI_H_ 15 | 16 | /** 17 | * IEEE 802.11 structs to build management action frame 18 | */ 19 | struct __attribute__((__packed__)) ieee80211_mgmt { 20 | uint16_t frame_control; 21 | uint16_t duration; 22 | uint8_t da[6]; 23 | uint8_t sa[6]; 24 | uint8_t bssid[6]; 25 | uint16_t seq_ctrl; 26 | }; 27 | 28 | struct __attribute__((__packed__)) ieee80211_beacon { 29 | uint64_t timestamp; 30 | uint16_t beacon_interval; 31 | uint16_t capability; 32 | }; 33 | 34 | struct __attribute__((__packed__)) ieee80211_ssid { 35 | uint8_t element_id; 36 | uint8_t length; 37 | uint8_t ssid[]; 38 | }; 39 | 40 | struct __attribute__((__packed__)) ieee80211_supported_rates { 41 | uint8_t element_id; 42 | uint8_t length; 43 | uint8_t supported_rates; 44 | }; 45 | 46 | struct __attribute__((__packed__)) ieee80211_vendor_specific { 47 | uint8_t element_id; 48 | uint8_t length; 49 | uint8_t oui[3]; 50 | uint8_t oui_type; 51 | }; 52 | 53 | struct __attribute__((__packed__)) nan_service_discovery { 54 | uint8_t category; 55 | uint8_t action_code; 56 | uint8_t oui[3]; 57 | uint8_t oui_type; 58 | }; 59 | 60 | struct __attribute__((__packed__)) nan_attribute_header { 61 | uint8_t attribute_id; 62 | uint16_t length; 63 | }; 64 | 65 | struct __attribute__((__packed__)) nan_master_indication_attribute { 66 | struct nan_attribute_header header; 67 | uint8_t master_preference; 68 | uint8_t random_factor; 69 | }; 70 | 71 | struct __attribute__((__packed__)) nan_cluster_attribute { 72 | struct nan_attribute_header header; 73 | uint8_t device_mac[6]; 74 | uint8_t random_factor; 75 | uint8_t master_preference; 76 | uint8_t hop_count_to_anchor_master; 77 | uint8_t anchor_master_beacon_transmission_time[4]; 78 | }; 79 | 80 | struct __attribute__((__packed__)) nan_service_id_list_attribute { 81 | struct nan_attribute_header header; 82 | uint8_t service_id[6]; 83 | }; 84 | 85 | struct __attribute__((__packed__)) nan_service_descriptor_attribute { 86 | struct nan_attribute_header header; 87 | uint8_t service_id[6]; 88 | uint8_t instance_id; 89 | uint8_t requestor_instance_id; 90 | uint8_t service_control; 91 | uint8_t service_info_length; 92 | }; 93 | 94 | struct __attribute__((__packed__)) nan_service_descriptor_extension_attribute { 95 | struct nan_attribute_header header; 96 | uint8_t instance_id; 97 | uint16_t control; 98 | uint8_t service_update_indicator; 99 | }; 100 | 101 | struct __attribute__((__packed__)) ODID_service_info { 102 | uint8_t message_counter; 103 | ODID_MessagePack_encoded odid_message_pack[]; 104 | }; 105 | 106 | #endif // _ODID_WIFI_H_ 107 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/src/wifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2020 Simon Wunderlich, Marek Sobe 3 | Copyright (C) 2020 Doodle Labs 4 | 5 | SPDX-License-Identifier: Apache-2.0 6 | 7 | Open Drone ID C Library 8 | 9 | Maintainer: 10 | Simon Wunderlich 11 | sw@simonwunderlich.de 12 | */ 13 | 14 | #if defined(ARDUINO_ARCH_ESP32) 15 | #include 16 | #include 17 | #include 18 | int clock_gettime(clockid_t clk_id, struct timespec *tp); 19 | #else 20 | #include 21 | #include 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | #include "opendroneid.h" 29 | #include "odid_wifi.h" 30 | 31 | int clock_gettime(clockid_t, struct timespec *); 32 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 33 | #if defined(IDF_VER) 34 | #include 35 | #define cpu_to_be16(x) (bswap16(x)) 36 | #define cpu_to_be32(x) (bswap32(x)) 37 | #else 38 | #include 39 | #define cpu_to_be16(x) (bswap_16(x)) 40 | #define cpu_to_be32(x) (bswap_32(x)) 41 | #endif 42 | #define cpu_to_le16(x) (x) 43 | #define cpu_to_le64(x) (x) 44 | #else 45 | #define cpu_to_be16(x) (x) 46 | #define cpu_to_be32(x) (x) 47 | #define cpu_to_le16(x) (bswap_16(x)) 48 | #define cpu_to_le64(x) (bswap_64(x)) 49 | #endif 50 | 51 | #define IEEE80211_FCTL_FTYPE 0x000c 52 | #define IEEE80211_FCTL_STYPE 0x00f0 53 | 54 | #define IEEE80211_FTYPE_MGMT 0x0000 55 | #define IEEE80211_STYPE_ACTION 0x00D0 56 | #define IEEE80211_STYPE_BEACON 0x0080 57 | 58 | /* IEEE 802.11-2016 capability info */ 59 | #define IEEE80211_CAPINFO_ESS 0x0001 60 | #define IEEE80211_CAPINFO_IBSS 0x0002 61 | #define IEEE80211_CAPINFO_CF_POLLABLE 0x0004 62 | #define IEEE80211_CAPINFO_CF_POLLREQ 0x0008 63 | #define IEEE80211_CAPINFO_PRIVACY 0x0010 64 | #define IEEE80211_CAPINFO_SHORT_PREAMBLE 0x0020 65 | /* bits 6-7 reserved */ 66 | #define IEEE80211_CAPINFO_SPECTRUM_MGMT 0x0100 67 | #define IEEE80211_CAPINFO_QOS 0x0200 68 | #define IEEE80211_CAPINFO_SHORT_SLOTTIME 0x0400 69 | #define IEEE80211_CAPINFO_APSD 0x0800 70 | #define IEEE80211_CAPINFO_RADIOMEAS 0x1000 71 | /* bit 13 reserved */ 72 | #define IEEE80211_CAPINFO_DEL_BLOCK_ACK 0x4000 73 | #define IEEE80211_CAPINFO_IMM_BLOCK_ACK 0x8000 74 | 75 | /* IEEE 802.11 Element IDs */ 76 | #define IEEE80211_ELEMID_SSID 0x00 77 | #define IEEE80211_ELEMID_RATES 0x01 78 | #define IEEE80211_ELEMID_VENDOR 0xDD 79 | 80 | /* Neighbor Awareness Networking Specification v3.1 in section 2.8.2 81 | * The NAN Cluster ID is a MAC address that takes a value from 82 | * 50-6F-9A-01-00-00 to 50-6F-9A-01-FF-FF and is carried in the A3 field of 83 | * some of the NAN frames. The NAN Cluster ID is randomly chosen by the device 84 | * that initiates the NAN Cluster. 85 | * However, the ASTM Remote ID specification v1.1 specifies that the NAN 86 | * cluster ID must be fixed to the value 50-6F-9A-01-00-FF. 87 | */ 88 | static const uint8_t *get_nan_cluster_id(void) 89 | { 90 | static const uint8_t cluster_id[6] = { 0x50, 0x6F, 0x9A, 0x01, 0x00, 0xFF }; 91 | return cluster_id; 92 | } 93 | 94 | static int buf_fill_ieee80211_mgmt(uint8_t *buf, size_t *len, size_t buf_size, 95 | const uint16_t subtype, 96 | const uint8_t *dst_addr, 97 | const uint8_t *src_addr, 98 | const uint8_t *bssid) 99 | { 100 | if (*len + sizeof(struct ieee80211_mgmt) > buf_size) 101 | return -ENOMEM; 102 | 103 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)(buf + *len); 104 | mgmt->frame_control = (uint16_t) cpu_to_le16(IEEE80211_FTYPE_MGMT | subtype); 105 | mgmt->duration = cpu_to_le16(0x0000); 106 | memcpy(mgmt->da, dst_addr, sizeof(mgmt->da)); 107 | memcpy(mgmt->sa, src_addr, sizeof(mgmt->sa)); 108 | memcpy(mgmt->bssid, bssid, sizeof(mgmt->bssid)); 109 | mgmt->seq_ctrl = cpu_to_le16(0x0000); 110 | *len += sizeof(*mgmt); 111 | 112 | return 0; 113 | } 114 | 115 | static int buf_fill_ieee80211_beacon(uint8_t *buf, size_t *len, size_t buf_size, uint16_t interval_tu) 116 | { 117 | if (*len + sizeof(struct ieee80211_beacon) > buf_size) 118 | return -ENOMEM; 119 | 120 | struct ieee80211_beacon *beacon = (struct ieee80211_beacon *)(buf + *len); 121 | struct timespec ts; 122 | uint64_t mono_us = 0; 123 | 124 | #if defined(CLOCK_MONOTONIC) 125 | clock_gettime(CLOCK_MONOTONIC, &ts); 126 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 127 | #elif defined(CLOCK_REALTIME) 128 | clock_gettime(CLOCK_REALTIME, &ts); 129 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 130 | #elif defined(ARDUINO) 131 | #warning "No REALTIME or MONOTONIC clock, using micros()." 132 | mono_us = micros(); 133 | #else 134 | #warning "Unable to set wifi timestamp." 135 | #endif 136 | beacon->timestamp = cpu_to_le64(mono_us); 137 | beacon->beacon_interval = cpu_to_le16(interval_tu); 138 | beacon->capability = cpu_to_le16(IEEE80211_CAPINFO_SHORT_SLOTTIME | IEEE80211_CAPINFO_SHORT_PREAMBLE); 139 | *len += sizeof(*beacon); 140 | 141 | return 0; 142 | } 143 | 144 | void drone_export_gps_data(ODID_UAS_Data *UAS_Data, char *buf, size_t buf_size) 145 | { 146 | ptrdiff_t len = 0; 147 | 148 | #define mprintf(...) {\ 149 | len += snprintf(buf + len, buf_size - (size_t)len, __VA_ARGS__); \ 150 | if ((len < 0) || ((size_t)len >= buf_size)) \ 151 | return; \ 152 | } 153 | 154 | mprintf("{\n\t\"Version\": \"1.1\",\n\t\"Response\": {\n"); 155 | 156 | mprintf("\t\t\"BasicID\": {\n"); 157 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 158 | if (!UAS_Data->BasicIDValid[i]) 159 | continue; 160 | mprintf("\t\t\t\"UAType%d\": %d,\n", i, UAS_Data->BasicID[i].UAType); 161 | mprintf("\t\t\t\"IDType%d\": %d,\n", i, UAS_Data->BasicID[i].IDType); 162 | mprintf("\t\t\t\"UASID%d\": \"%s\",\n", i, UAS_Data->BasicID[i].UASID); 163 | } 164 | mprintf("\t\t},\n"); 165 | 166 | mprintf("\t\t\"Location\": {\n"); 167 | mprintf("\t\t\t\"Status\": %d,\n", (int)UAS_Data->Location.Status); 168 | mprintf("\t\t\t\"Direction\": %f,\n", (double) UAS_Data->Location.Direction); 169 | mprintf("\t\t\t\"SpeedHorizontal\": %f,\n", (double) UAS_Data->Location.SpeedHorizontal); 170 | mprintf("\t\t\t\"SpeedVertical\": %f,\n", (double) UAS_Data->Location.SpeedVertical); 171 | mprintf("\t\t\t\"Latitude\": %f,\n", UAS_Data->Location.Latitude); 172 | mprintf("\t\t\t\"Longitude\": %f,\n", UAS_Data->Location.Longitude); 173 | mprintf("\t\t\t\"AltitudeBaro\": %f,\n", (double) UAS_Data->Location.AltitudeBaro); 174 | mprintf("\t\t\t\"AltitudeGeo\": %f,\n", (double) UAS_Data->Location.AltitudeGeo); 175 | mprintf("\t\t\t\"HeightType\": %d,\n", UAS_Data->Location.HeightType); 176 | mprintf("\t\t\t\"Height\": %f,\n", (double) UAS_Data->Location.Height); 177 | mprintf("\t\t\t\"HorizAccuracy\": %d,\n", UAS_Data->Location.HorizAccuracy); 178 | mprintf("\t\t\t\"VertAccuracy\": %d,\n", UAS_Data->Location.VertAccuracy); 179 | mprintf("\t\t\t\"BaroAccuracy\": %d,\n", UAS_Data->Location.BaroAccuracy); 180 | mprintf("\t\t\t\"SpeedAccuracy\": %d,\n", UAS_Data->Location.SpeedAccuracy); 181 | mprintf("\t\t\t\"TSAccuracy\": %d,\n", UAS_Data->Location.TSAccuracy); 182 | mprintf("\t\t\t\"TimeStamp\": %f,\n", (double) UAS_Data->Location.TimeStamp); 183 | mprintf("\t\t},\n"); 184 | 185 | mprintf("\t\t\"Authentication\": {\n"); 186 | mprintf("\t\t\t\"AuthType\": %d,\n", UAS_Data->Auth[0].AuthType); 187 | mprintf("\t\t\t\"LastPageIndex\": %d,\n", UAS_Data->Auth[0].LastPageIndex); 188 | mprintf("\t\t\t\"Length\": %d,\n", UAS_Data->Auth[0].Length); 189 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->Auth[0].Timestamp); 190 | for (int i = 0; i <= UAS_Data->Auth[0].LastPageIndex; i++) { 191 | mprintf("\t\t\t\"AuthData Page %d,\": \"%s\"\n", i, UAS_Data->Auth[i].AuthData); 192 | } 193 | mprintf("\t\t},\n"); 194 | 195 | mprintf("\t\t\"SelfID\": {\n"); 196 | mprintf("\t\t\t\"Description Type\": %d,\n", UAS_Data->SelfID.DescType); 197 | mprintf("\t\t\t\"Description\": \"%s\",\n", UAS_Data->SelfID.Desc); 198 | mprintf("\t\t},\n"); 199 | 200 | mprintf("\t\t\"Operator\": {\n"); 201 | mprintf("\t\t\t\"OperatorLocationType\": %d,\n", UAS_Data->System.OperatorLocationType); 202 | mprintf("\t\t\t\"ClassificationType\": %d,\n", UAS_Data->System.ClassificationType); 203 | mprintf("\t\t\t\"OperatorLatitude\": %f,\n", UAS_Data->System.OperatorLatitude); 204 | mprintf("\t\t\t\"OperatorLongitude\": %f,\n", UAS_Data->System.OperatorLongitude); 205 | mprintf("\t\t\t\"AreaCount\": %d,\n", UAS_Data->System.AreaCount); 206 | mprintf("\t\t\t\"AreaRadius\": %d,\n", UAS_Data->System.AreaRadius); 207 | mprintf("\t\t\t\"AreaCeiling\": %f,\n", (double) UAS_Data->System.AreaCeiling); 208 | mprintf("\t\t\t\"AreaFloor\": %f,\n", (double) UAS_Data->System.AreaFloor); 209 | mprintf("\t\t\t\"CategoryEU\": %d,\n", UAS_Data->System.CategoryEU); 210 | mprintf("\t\t\t\"ClassEU\": %d,\n", UAS_Data->System.ClassEU); 211 | mprintf("\t\t\t\"OperatorAltitudeGeo\": %f,\n", (double) UAS_Data->System.OperatorAltitudeGeo); 212 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->System.Timestamp); 213 | mprintf("\t\t}\n"); 214 | 215 | mprintf("\t\t\"OperatorID\": {\n"); 216 | mprintf("\t\t\t\"OperatorIdType\": %d,\n", UAS_Data->OperatorID.OperatorIdType); 217 | mprintf("\t\t\t\"OperatorId\": \"%s\",\n", UAS_Data->OperatorID.OperatorId); 218 | mprintf("\t\t},\n"); 219 | 220 | mprintf("\t}\n}"); 221 | } 222 | 223 | int odid_message_build_pack(ODID_UAS_Data *UAS_Data, void *pack, size_t buflen) 224 | { 225 | ODID_MessagePack_data msg_pack; 226 | ODID_MessagePack_encoded *msg_pack_enc; 227 | size_t len; 228 | 229 | /* create a complete message pack */ 230 | msg_pack.SingleMessageSize = ODID_MESSAGE_SIZE; 231 | msg_pack.MsgPackSize = 0; 232 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 233 | if (UAS_Data->BasicIDValid[i]) { 234 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 235 | return -EINVAL; 236 | if (encodeBasicIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->BasicID[i]) == ODID_SUCCESS) 237 | msg_pack.MsgPackSize++; 238 | } 239 | } 240 | if (UAS_Data->LocationValid) { 241 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 242 | return -EINVAL; 243 | if (encodeLocationMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Location) == ODID_SUCCESS) 244 | msg_pack.MsgPackSize++; 245 | } 246 | for (int i = 0; i < ODID_AUTH_MAX_PAGES; i++) 247 | { 248 | if (UAS_Data->AuthValid[i]) { 249 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 250 | return -EINVAL; 251 | if (encodeAuthMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Auth[i]) == ODID_SUCCESS) 252 | msg_pack.MsgPackSize++; 253 | } 254 | } 255 | if (UAS_Data->SelfIDValid) { 256 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 257 | return -EINVAL; 258 | if (encodeSelfIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->SelfID) == ODID_SUCCESS) 259 | msg_pack.MsgPackSize++; 260 | } 261 | if (UAS_Data->SystemValid) { 262 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 263 | return -EINVAL; 264 | if (encodeSystemMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->System) == ODID_SUCCESS) 265 | msg_pack.MsgPackSize++; 266 | } 267 | if (UAS_Data->OperatorIDValid) { 268 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 269 | return -EINVAL; 270 | if (encodeOperatorIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->OperatorID) == ODID_SUCCESS) 271 | msg_pack.MsgPackSize++; 272 | } 273 | 274 | /* check that there is at least one message to send. */ 275 | if (msg_pack.MsgPackSize == 0) 276 | return -EINVAL; 277 | 278 | /* calculate the exact encoded message pack size. */ 279 | len = sizeof(*msg_pack_enc) - (ODID_PACK_MAX_MESSAGES - msg_pack.MsgPackSize) * ODID_MESSAGE_SIZE; 280 | 281 | /* check if there is enough space for the message pack. */ 282 | if (len > buflen) 283 | return -ENOMEM; 284 | 285 | msg_pack_enc = (ODID_MessagePack_encoded *) pack; 286 | if (encodeMessagePack(msg_pack_enc, &msg_pack) != ODID_SUCCESS) 287 | return -1; 288 | 289 | return (int) len; 290 | } 291 | 292 | int odid_wifi_build_nan_sync_beacon_frame(char *mac, uint8_t *buf, size_t buf_size) 293 | { 294 | /* Broadcast address */ 295 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 296 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 297 | /* "org.opendroneid.remoteid" hash */ 298 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 299 | const uint8_t *cluster_id = get_nan_cluster_id(); 300 | struct ieee80211_vendor_specific *vendor; 301 | struct nan_master_indication_attribute *master_indication_attr; 302 | struct nan_cluster_attribute *cluster_attr; 303 | struct nan_service_id_list_attribute *nsila; 304 | int ret; 305 | size_t len = 0; 306 | 307 | /* IEEE 802.11 Management Header */ 308 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, cluster_id); 309 | if (ret <0) 310 | return ret; 311 | 312 | /* Beacon */ 313 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, 0x0200); 314 | if (ret <0) 315 | return ret; 316 | 317 | /* Vendor Specific */ 318 | if (len + sizeof(*vendor) > buf_size) 319 | return -ENOMEM; 320 | 321 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 322 | memset(vendor, 0, sizeof(*vendor)); 323 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 324 | vendor->length = 0x22; 325 | memcpy(vendor->oui, wifi_alliance_oui, sizeof(vendor->oui)); 326 | vendor->oui_type = 0x13; 327 | len += sizeof(*vendor); 328 | 329 | /* NAN Master Indication attribute */ 330 | if (len + sizeof(*master_indication_attr) > buf_size) 331 | return -ENOMEM; 332 | 333 | master_indication_attr = (struct nan_master_indication_attribute *)(buf + len); 334 | memset(master_indication_attr, 0, sizeof(*master_indication_attr)); 335 | master_indication_attr->header.attribute_id = 0x00; 336 | master_indication_attr->header.length = cpu_to_le16(0x0002); 337 | /* Information that is used to indicate a NAN Device’s preference to serve 338 | * as the role of Master, with a larger value indicating a higher 339 | * preference. Values 1 and 255 are used for testing purposes only. 340 | */ 341 | master_indication_attr->master_preference = 0xFE; 342 | /* Random factor value 0xEA is recommended by the European Standard */ 343 | master_indication_attr->random_factor = 0xEA; 344 | len += sizeof(*master_indication_attr); 345 | 346 | /* NAN Cluster attribute */ 347 | if (len + sizeof(*cluster_attr) > buf_size) 348 | return -ENOMEM; 349 | 350 | cluster_attr = (struct nan_cluster_attribute *)(buf + len); 351 | memset(cluster_attr, 0, sizeof(*cluster_attr)); 352 | cluster_attr->header.attribute_id = 0x1; 353 | cluster_attr->header.length = cpu_to_le16(0x000D); 354 | memcpy(cluster_attr->device_mac, mac, sizeof(cluster_attr->device_mac)); 355 | cluster_attr->random_factor = 0xEA; 356 | cluster_attr->master_preference = 0xFE; 357 | cluster_attr->hop_count_to_anchor_master = 0x00; 358 | memset(cluster_attr->anchor_master_beacon_transmission_time, 0, sizeof(cluster_attr->anchor_master_beacon_transmission_time)); 359 | len += sizeof(*cluster_attr); 360 | 361 | /* NAN attributes */ 362 | if (len + sizeof(*nsila) > buf_size) 363 | return -ENOMEM; 364 | 365 | nsila = (struct nan_service_id_list_attribute *)(buf + len); 366 | memset(nsila, 0, sizeof(*nsila)); 367 | nsila->header.attribute_id = 0x02; 368 | nsila->header.length = cpu_to_le16(0x0006); 369 | memcpy(nsila->service_id, service_id, sizeof(service_id)); 370 | len += sizeof(*nsila); 371 | 372 | return (int) len; 373 | } 374 | 375 | int odid_wifi_build_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, char *mac, 376 | uint8_t send_counter, 377 | uint8_t *buf, size_t buf_size) 378 | { 379 | /* Neighbor Awareness Networking Specification v3.0 in section 2.8.1 380 | * NAN Network ID calls for the destination mac to be 51-6F-9A-01-00-00 */ 381 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 382 | /* "org.opendroneid.remoteid" hash */ 383 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 384 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 385 | const uint8_t *cluster_id = get_nan_cluster_id(); 386 | struct nan_service_discovery *nsd; 387 | struct nan_service_descriptor_attribute *nsda; 388 | struct nan_service_descriptor_extension_attribute *nsdea; 389 | struct ODID_service_info *si; 390 | int ret; 391 | size_t len = 0; 392 | 393 | /* IEEE 802.11 Management Header */ 394 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_ACTION, target_addr, (uint8_t *)mac, cluster_id); 395 | if (ret <0) 396 | return ret; 397 | 398 | /* NAN Service Discovery header */ 399 | if (len + sizeof(*nsd) > buf_size) 400 | return -ENOMEM; 401 | 402 | nsd = (struct nan_service_discovery *)(buf + len); 403 | memset(nsd, 0, sizeof(*nsd)); 404 | nsd->category = 0x04; /* IEEE 802.11 Public Action frame */ 405 | nsd->action_code = 0x09; /* IEEE 802.11 Public Action frame Vendor Specific*/ 406 | memcpy(nsd->oui, wifi_alliance_oui, sizeof(nsd->oui)); 407 | nsd->oui_type = 0x13; /* Identify Type and version of the NAN */ 408 | len += sizeof(*nsd); 409 | 410 | /* NAN Attribute for Service Descriptor header */ 411 | if (len + sizeof(*nsda) > buf_size) 412 | return -ENOMEM; 413 | 414 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 415 | nsda->header.attribute_id = 0x3; /* Service Descriptor Attribute type */ 416 | memcpy(nsda->service_id, service_id, sizeof(service_id)); 417 | /* always 1 */ 418 | nsda->instance_id = 0x01; /* always 1 */ 419 | nsda->requestor_instance_id = 0x00; /* from triggering frame */ 420 | nsda->service_control = 0x10; /* follow up */ 421 | len += sizeof(*nsda); 422 | 423 | /* ODID Service Info Attribute header */ 424 | if (len + sizeof(*si) > buf_size) 425 | return -ENOMEM; 426 | 427 | si = (struct ODID_service_info *)(buf + len); 428 | memset(si, 0, sizeof(*si)); 429 | si->message_counter = send_counter; 430 | len += sizeof(*si); 431 | 432 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 433 | if (ret < 0) 434 | return ret; 435 | len += ret; 436 | 437 | /* set the lengths according to the message pack lengths */ 438 | nsda->service_info_length = sizeof(*si) + ret; 439 | nsda->header.length = cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length); 440 | 441 | /* NAN Attribute for Service Descriptor extension header */ 442 | if (len + sizeof(*nsdea) > buf_size) 443 | return -ENOMEM; 444 | 445 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 446 | nsdea->header.attribute_id = 0xE; 447 | nsdea->header.length = cpu_to_le16(0x0004); 448 | nsdea->instance_id = 0x01; 449 | nsdea->control = cpu_to_le16(0x0200); 450 | nsdea->service_update_indicator = send_counter; 451 | len += sizeof(*nsdea); 452 | 453 | return (int) len; 454 | } 455 | 456 | int odid_wifi_build_message_pack_beacon_frame(ODID_UAS_Data *UAS_Data, char *mac, 457 | const char *SSID, size_t SSID_len, 458 | uint16_t interval_tu, uint8_t send_counter, 459 | uint8_t *buf, size_t buf_size) 460 | { 461 | /* Broadcast address */ 462 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 463 | uint8_t asd_stan_oui[3] = { 0xFA, 0x0B, 0xBC }; 464 | /* Mgmt Beacon frame mandatory fields + IE 221 */ 465 | struct ieee80211_ssid *ssid_s; 466 | struct ieee80211_supported_rates *rates; 467 | struct ieee80211_vendor_specific *vendor; 468 | 469 | /* Message Pack */ 470 | struct ODID_service_info *si; 471 | 472 | int ret; 473 | size_t len = 0; 474 | 475 | /* IEEE 802.11 Management Header */ 476 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, (uint8_t *)mac); 477 | if (ret <0) 478 | return ret; 479 | 480 | /* Mandatory Beacon as of 802.11-2016 Part 11 */ 481 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, interval_tu); 482 | if (ret <0) 483 | return ret; 484 | 485 | /* SSID: 1-32 bytes */ 486 | if (len + sizeof(*ssid_s) > buf_size) 487 | return -ENOMEM; 488 | 489 | ssid_s = (struct ieee80211_ssid *)(buf + len); 490 | if(!SSID || (SSID_len ==0) || (SSID_len > 32)) 491 | return -EINVAL; 492 | ssid_s->element_id = IEEE80211_ELEMID_SSID; 493 | ssid_s->length = (uint8_t) SSID_len; 494 | memcpy(ssid_s->ssid, SSID, ssid_s->length); 495 | len += sizeof(*ssid_s) + SSID_len; 496 | 497 | /* Supported Rates: 1 record at minimum */ 498 | if (len + sizeof(*rates) > buf_size) 499 | return -ENOMEM; 500 | 501 | rates = (struct ieee80211_supported_rates *)(buf + len); 502 | rates->element_id = IEEE80211_ELEMID_RATES; 503 | rates->length = 1; // One rate only 504 | rates->supported_rates = 0x8C; // 6 Mbps 505 | len += sizeof(*rates); 506 | 507 | /* Vendor Specific Information Element (IE 221) */ 508 | if (len + sizeof(*vendor) > buf_size) 509 | return -ENOMEM; 510 | 511 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 512 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 513 | vendor->length = 0x00; // Length updated at end of function 514 | memcpy(vendor->oui, asd_stan_oui, sizeof(vendor->oui)); 515 | vendor->oui_type = 0x0D; 516 | len += sizeof(*vendor); 517 | 518 | /* ODID Service Info Attribute header */ 519 | if (len + sizeof(*si) > buf_size) 520 | return -ENOMEM; 521 | 522 | si = (struct ODID_service_info *)(buf + len); 523 | memset(si, 0, sizeof(*si)); 524 | si->message_counter = send_counter; 525 | len += sizeof(*si); 526 | 527 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 528 | if (ret < 0) 529 | return ret; 530 | len += ret; 531 | 532 | /* set the lengths according to the message pack lengths */ 533 | vendor->length = sizeof(vendor->oui) + sizeof(vendor->oui_type) + sizeof(*si) + ret; 534 | 535 | return (int) len; 536 | } 537 | 538 | int odid_message_process_pack(ODID_UAS_Data *UAS_Data, uint8_t *pack, size_t buflen) 539 | { 540 | ODID_MessagePack_encoded *msg_pack_enc = (ODID_MessagePack_encoded *) pack; 541 | size_t size = sizeof(*msg_pack_enc) - ODID_MESSAGE_SIZE * (ODID_PACK_MAX_MESSAGES - msg_pack_enc->MsgPackSize); 542 | if (size > buflen) 543 | return -ENOMEM; 544 | 545 | odid_initUasData(UAS_Data); 546 | 547 | if (decodeMessagePack(UAS_Data, msg_pack_enc) != ODID_SUCCESS) 548 | return -1; 549 | 550 | return (int) size; 551 | } 552 | 553 | int odid_wifi_receive_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, 554 | char *mac, uint8_t *buf, size_t buf_size) 555 | { 556 | struct ieee80211_mgmt *mgmt; 557 | struct nan_service_discovery *nsd; 558 | struct nan_service_descriptor_attribute *nsda; 559 | struct nan_service_descriptor_extension_attribute *nsdea; 560 | struct ODID_service_info *si; 561 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 562 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 563 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 564 | int ret; 565 | size_t len = 0; 566 | 567 | /* IEEE 802.11 Management Header */ 568 | if (len + sizeof(*mgmt) > buf_size) 569 | return -EINVAL; 570 | mgmt = (struct ieee80211_mgmt *)(buf + len); 571 | if ((mgmt->frame_control & cpu_to_le16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) != 572 | cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION)) 573 | return -EINVAL; 574 | if (memcmp(mgmt->da, target_addr, sizeof(mgmt->da)) != 0) 575 | return -EINVAL; 576 | memcpy(mac, mgmt->sa, sizeof(mgmt->sa)); 577 | 578 | len += sizeof(*mgmt); 579 | 580 | /* NAN Service Discovery header */ 581 | if (len + sizeof(*nsd) > buf_size) 582 | return -EINVAL; 583 | nsd = (struct nan_service_discovery *)(buf + len); 584 | if (nsd->category != 0x04) 585 | return -EINVAL; 586 | if (nsd->action_code != 0x09) 587 | return -EINVAL; 588 | if (memcmp(nsd->oui, wifi_alliance_oui, sizeof(wifi_alliance_oui)) != 0) 589 | return -EINVAL; 590 | if (nsd->oui_type != 0x13) 591 | return -EINVAL; 592 | len += sizeof(*nsd); 593 | 594 | /* NAN Attribute for Service Descriptor header */ 595 | if (len + sizeof(*nsda) > buf_size) 596 | return -EINVAL; 597 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 598 | if (nsda->header.attribute_id != 0x3) 599 | return -EINVAL; 600 | if (memcmp(nsda->service_id, service_id, sizeof(service_id)) != 0) 601 | return -EINVAL; 602 | if (nsda->instance_id != 0x01) 603 | return -EINVAL; 604 | if (nsda->service_control != 0x10) 605 | return -EINVAL; 606 | len += sizeof(*nsda); 607 | 608 | si = (struct ODID_service_info *)(buf + len); 609 | ret = odid_message_process_pack(UAS_Data, buf + len + sizeof(*si), buf_size - len - sizeof(*nsdea)); 610 | if (ret < 0) 611 | return -EINVAL; 612 | if (nsda->service_info_length != (sizeof(*si) + ret)) 613 | return -EINVAL; 614 | if (nsda->header.length != (cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length))) 615 | return -EINVAL; 616 | len += sizeof(*si) + ret; 617 | 618 | /* NAN Attribute for Service Descriptor extension header */ 619 | if (len + sizeof(*nsdea) > buf_size) 620 | return -ENOMEM; 621 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 622 | if (nsdea->header.attribute_id != 0xE) 623 | return -EINVAL; 624 | if (nsdea->header.length != cpu_to_le16(0x0004)) 625 | return -EINVAL; 626 | if (nsdea->instance_id != 0x01) 627 | return -EINVAL; 628 | if (nsdea->control != cpu_to_le16(0x0200)) 629 | return -EINVAL; 630 | 631 | return 0; 632 | } 633 | -------------------------------------------------------------------------------- /remoteid-mesh-dualcore/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /remoteid-mesh/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /remoteid-mesh/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colonelpanichacks/drone-mesh-mapper/28d735218f23ea5438df952713fb6923202d4832/remoteid-mesh/firmware.bin -------------------------------------------------------------------------------- /remoteid-mesh/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the convention is to give header files names that end with `.h'. 29 | 30 | Read more about using header files in official GCC documentation: 31 | 32 | * Include Syntax 33 | * Include Operation 34 | * Once-Only Headers 35 | * Computed Includes 36 | 37 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 38 | -------------------------------------------------------------------------------- /remoteid-mesh/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into the executable file. 4 | 5 | The source code of each library should be placed in a separate directory 6 | ("lib/your_library_name/[Code]"). 7 | 8 | For example, see the structure of the following example libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Example contents of `src/main.c` using Foo and Bar: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | The PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries by scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /remoteid-mesh/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:seeed_xiao_esp32c3] 12 | platform = espressif32 13 | board = seeed_xiao_esp32c3 14 | framework = arduino 15 | 16 | [env:seeed_xiao_esp32s3] 17 | platform = espressif32 18 | board = seeed_xiao_esp32s3 19 | framework = arduino -------------------------------------------------------------------------------- /remoteid-mesh/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Minimal scanner for WiFi direct remote ID */ 4 | 5 | #if !defined(ARDUINO_ARCH_ESP32) 6 | #error "This program requires an ESP32" 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "opendroneid.h" 19 | #include "odid_wifi.h" 20 | 21 | // Custom UART pin definitions for Serial1 22 | const int SERIAL1_RX_PIN = 7; // GPIO7 23 | const int SERIAL1_TX_PIN = 6; // GPIO6 24 | 25 | ODID_UAS_Data UAS_data; 26 | 27 | // Structure to hold UAV detection data 28 | struct uav_data { 29 | uint8_t mac[6]; 30 | uint8_t padding[1]; 31 | int8_t rssi; 32 | char op_id[ODID_ID_SIZE + 1]; 33 | char uav_id[ODID_ID_SIZE + 1]; 34 | double lat_d; 35 | double long_d; 36 | double base_lat_d; 37 | double base_long_d; 38 | int altitude_msl; 39 | int height_agl; 40 | int speed; 41 | int heading; 42 | int speed_vertical; 43 | int altitude_pressure; 44 | int horizontal_accuracy; 45 | int vertical_accuracy; 46 | int baro_accuracy; 47 | int speed_accuracy; 48 | int timestamp; 49 | int status; 50 | int height_type; 51 | int operator_location_type; 52 | int classification_type; 53 | int area_count; 54 | int area_radius; 55 | int area_ceiling; 56 | int area_floor; 57 | int operator_altitude_geo; 58 | uint32_t system_timestamp; 59 | int operator_id_type; 60 | uint8_t ua_type; 61 | uint8_t auth_type; 62 | uint8_t auth_page; 63 | uint8_t auth_length; 64 | uint32_t auth_timestamp; 65 | char auth_data[ODID_AUTH_PAGE_NONZERO_DATA_SIZE + 1]; 66 | uint8_t desc_type; 67 | char description[ODID_STR_SIZE + 1]; 68 | }; 69 | 70 | // Forward declarations 71 | void event_handler(void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data); 72 | void callback(void *, wifi_promiscuous_pkt_type_t); 73 | void parse_odid(struct uav_data *, ODID_UAS_Data *); 74 | void store_mac(struct uav_data *uav, uint8_t *payload); 75 | void send_json_detection(struct uav_data *UAV); // existing function 76 | 77 | // Global packet counter 78 | static int packetCount = 0; 79 | 80 | // Variables for periodic heartbeat 81 | unsigned long last_status = 0; 82 | unsigned long current_millis = 0; 83 | 84 | void event_handler(void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data) { 85 | // No-op handler for now 86 | } 87 | 88 | // Initialize USB Serial (for JSON output) and Serial1 ( 89 | void initializeSerial() { 90 | // Initialize USB Serial for JSON payloads. 91 | Serial.begin(115200); 92 | // Initialize Serial1 for mesh detection messages. 93 | Serial1.begin(115200, SERIAL_8N1, SERIAL1_RX_PIN, SERIAL1_TX_PIN); 94 | Serial.println("USB Serial (for JSON) and UART (Serial1) initialized."); 95 | } 96 | 97 | void setup() { 98 | setCpuFrequencyMhz(160); 99 | nvs_flash_init(); 100 | esp_netif_init(); // Modern replacement for tcpip_adapter_init 101 | initializeSerial(); 102 | esp_event_loop_create_default(); // Modern replacement 103 | esp_event_handler_instance_register(ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL); 104 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 105 | esp_wifi_init(&cfg); 106 | esp_wifi_set_storage(WIFI_STORAGE_RAM); 107 | esp_wifi_set_mode(WIFI_MODE_NULL); 108 | esp_wifi_start(); 109 | esp_wifi_set_promiscuous(true); 110 | esp_wifi_set_promiscuous_rx_cb(&callback); 111 | esp_wifi_set_channel(6, WIFI_SECOND_CHAN_NONE); 112 | } 113 | 114 | void loop() { 115 | delay(10); 116 | current_millis = millis(); 117 | if ((current_millis - last_status) > 60000UL) { // Every 60 seconds 118 | // Send a heartbeat as JSON (optional) 119 | Serial.println("{\"heartbeat\":\"Device is active and running.\"}"); 120 | last_status = current_millis; 121 | } 122 | } 123 | 124 | // Sends the minimal JSON payload over USB Serial (updated to include basic_id). 125 | void send_json_detection(struct uav_data *UAV) { 126 | char mac_str[18]; 127 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 128 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 129 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 130 | char json_msg[256]; 131 | snprintf(json_msg, sizeof(json_msg), 132 | "{\"mac\":\"%s\", \"rssi\":%d, \"drone_lat\":%.6f, \"drone_long\":%.6f, \"drone_altitude\":%d, \"pilot_lat\":%.6f, \"pilot_long\":%.6f, \"basic_id\":\"%s\"}", 133 | mac_str, UAV->rssi, UAV->lat_d, UAV->long_d, UAV->altitude_msl, UAV->base_lat_d, UAV->base_long_d, UAV->uav_id); 134 | Serial.println(json_msg); 135 | } 136 | 137 | // New function: sends JSON payload as fast as possible over USB Serial (updated to include basic_id). 138 | void send_json_fast(struct uav_data *UAV) { 139 | char mac_str[18]; 140 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 141 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 142 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 143 | char json_msg[256]; 144 | snprintf(json_msg, sizeof(json_msg), 145 | "{\"mac\":\"%s\", \"rssi\":%d, \"drone_lat\":%.6f, \"drone_long\":%.6f, \"drone_altitude\":%d, \"pilot_lat\":%.6f, \"pilot_long\":%.6f, \"basic_id\":\"%s\"}", 146 | mac_str, UAV->rssi, UAV->lat_d, UAV->long_d, UAV->altitude_msl, UAV->base_lat_d, UAV->base_long_d, UAV->uav_id); 147 | Serial.println(json_msg); 148 | } 149 | 150 | // Sends UART messages over Serial1 exactly as before. 151 | void print_compact_message(struct uav_data *UAV) { 152 | static unsigned long lastSendTime = 0; 153 | const unsigned long sendInterval = 5000; // 5-second interval for UART messages 154 | const int MAX_MESH_SIZE = 230; 155 | 156 | if (millis() - lastSendTime < sendInterval) return; 157 | lastSendTime = millis(); 158 | 159 | char mac_str[18]; 160 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", 161 | UAV->mac[0], UAV->mac[1], UAV->mac[2], 162 | UAV->mac[3], UAV->mac[4], UAV->mac[5]); 163 | 164 | char mesh_msg[MAX_MESH_SIZE]; 165 | int msg_len = 0; 166 | msg_len += snprintf(mesh_msg + msg_len, sizeof(mesh_msg) - msg_len, 167 | "Drone: %s RSSI:%d", mac_str, UAV->rssi); 168 | if (msg_len < MAX_MESH_SIZE && UAV->lat_d != 0.0 && UAV->long_d != 0.0) { 169 | msg_len += snprintf(mesh_msg + msg_len, sizeof(mesh_msg) - msg_len, 170 | " https://maps.google.com/?q=%.6f,%.6f", 171 | UAV->lat_d, UAV->long_d); 172 | } 173 | if (Serial1.availableForWrite() >= msg_len) { 174 | Serial1.println(mesh_msg); 175 | } 176 | 177 | delay(1000); 178 | if (UAV->base_lat_d != 0.0 && UAV->base_long_d != 0.0) { 179 | char pilot_msg[MAX_MESH_SIZE]; 180 | int pilot_len = snprintf(pilot_msg, sizeof(pilot_msg), 181 | "Pilot: https://maps.google.com/?q=%.6f,%.6f", 182 | UAV->base_lat_d, UAV->base_long_d); 183 | if (Serial1.availableForWrite() >= pilot_len) { 184 | Serial1.println(pilot_msg); 185 | } 186 | } 187 | // Do not call send_json_detection() here; JSON is now sent separately via send_json_fast(). 188 | } 189 | 190 | // WiFi promiscuous callback: processes packets and sends both UART and fast JSON. 191 | void callback(void *buffer, wifi_promiscuous_pkt_type_t type) { 192 | if (type != WIFI_PKT_MGMT) return; 193 | 194 | wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t *)buffer; 195 | uint8_t *payload = packet->payload; 196 | int length = packet->rx_ctrl.sig_len; 197 | 198 | uav_data *currentUAV = (uav_data *)malloc(sizeof(uav_data)); 199 | if (!currentUAV) return; 200 | memset(currentUAV, 0, sizeof(uav_data)); 201 | 202 | store_mac(currentUAV, payload); 203 | currentUAV->rssi = packet->rx_ctrl.rssi; 204 | 205 | static const uint8_t nan_dest[6] = {0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00}; 206 | if (memcmp(nan_dest, &payload[4], 6) == 0) { 207 | if (odid_wifi_receive_message_pack_nan_action_frame(&UAS_data, 208 | (char *)currentUAV->op_id, 209 | payload, length) == 0) { 210 | parse_odid(currentUAV, &UAS_data); 211 | packetCount++; 212 | print_compact_message(currentUAV); // Send UART messages (throttled). 213 | send_json_fast(currentUAV); // Send JSON messages as fast as possible. 214 | } 215 | } 216 | else if (payload[0] == 0x80) { 217 | int offset = 36; 218 | bool printed = false; 219 | while (offset < length) { 220 | int typ = payload[offset]; 221 | int len = payload[offset + 1]; 222 | if (!printed) { 223 | if ((typ == 0xdd) && 224 | (((payload[offset + 2] == 0x90 && payload[offset + 3] == 0x3a && payload[offset + 4] == 0xe6)) || 225 | ((payload[offset + 2] == 0xfa && payload[offset + 3] == 0x0b && payload[offset + 4] == 0xbc)))) { 226 | int j = offset + 7; 227 | if (j < length) { 228 | memset(&UAS_data, 0, sizeof(UAS_data)); 229 | odid_message_process_pack(&UAS_data, &payload[j], length - j); 230 | parse_odid(currentUAV, &UAS_data); 231 | packetCount++; 232 | print_compact_message(currentUAV); 233 | send_json_fast(currentUAV); 234 | printed = true; 235 | } 236 | } 237 | } 238 | offset += len + 2; 239 | } 240 | } 241 | free(currentUAV); 242 | } 243 | 244 | void parse_odid(uav_data *UAV, ODID_UAS_Data *UAS_data2) { 245 | memset(UAV->op_id, 0, sizeof(UAV->op_id)); 246 | memset(UAV->uav_id, 0, sizeof(UAV->uav_id)); 247 | memset(UAV->description, 0, sizeof(UAV->description)); 248 | memset(UAV->auth_data, 0, sizeof(UAV->auth_data)); 249 | 250 | if (UAS_data2->BasicIDValid[0]) { 251 | strncpy(UAV->uav_id, (char *)UAS_data2->BasicID[0].UASID, ODID_ID_SIZE); 252 | } 253 | if (UAS_data2->LocationValid) { 254 | UAV->lat_d = UAS_data2->Location.Latitude; 255 | UAV->long_d = UAS_data2->Location.Longitude; 256 | UAV->altitude_msl = (int)UAS_data2->Location.AltitudeGeo; 257 | UAV->height_agl = (int)UAS_data2->Location.Height; 258 | UAV->speed = (int)UAS_data2->Location.SpeedHorizontal; 259 | UAV->heading = (int)UAS_data2->Location.Direction; 260 | UAV->speed_vertical = (int)UAS_data2->Location.SpeedVertical; 261 | UAV->altitude_pressure = (int)UAS_data2->Location.AltitudeBaro; 262 | UAV->height_type = UAS_data2->Location.HeightType; 263 | UAV->horizontal_accuracy = UAS_data2->Location.HorizAccuracy; 264 | UAV->vertical_accuracy = UAS_data2->Location.VertAccuracy; 265 | UAV->baro_accuracy = UAS_data2->Location.BaroAccuracy; 266 | UAV->speed_accuracy = UAS_data2->Location.SpeedAccuracy; 267 | UAV->timestamp = (int)UAS_data2->Location.TimeStamp; 268 | UAV->status = UAS_data2->Location.Status; 269 | } 270 | if (UAS_data2->SystemValid) { 271 | UAV->base_lat_d = UAS_data2->System.OperatorLatitude; 272 | UAV->base_long_d = UAS_data2->System.OperatorLongitude; 273 | UAV->operator_location_type = UAS_data2->System.OperatorLocationType; 274 | UAV->classification_type = UAS_data2->System.ClassificationType; 275 | UAV->area_count = UAS_data2->System.AreaCount; 276 | UAV->area_radius = UAS_data2->System.AreaRadius; 277 | UAV->area_ceiling = UAS_data2->System.AreaCeiling; 278 | UAV->area_floor = UAS_data2->System.AreaFloor; 279 | UAV->operator_altitude_geo = UAS_data2->System.OperatorAltitudeGeo; 280 | UAV->system_timestamp = UAS_data2->System.Timestamp; 281 | } 282 | if (UAS_data2->AuthValid[0]) { 283 | UAV->auth_type = UAS_data2->Auth[0].AuthType; 284 | UAV->auth_page = UAS_data2->Auth[0].DataPage; 285 | UAV->auth_length = UAS_data2->Auth[0].Length; 286 | UAV->auth_timestamp = UAS_data2->Auth[0].Timestamp; 287 | memcpy(UAV->auth_data, UAS_data2->Auth[0].AuthData, sizeof(UAV->auth_data) - 1); 288 | } 289 | if (UAS_data2->SelfIDValid) { 290 | UAV->desc_type = UAS_data2->SelfID.DescType; 291 | strncpy(UAV->description, UAS_data2->SelfID.Desc, ODID_STR_SIZE); 292 | } 293 | if (UAS_data2->OperatorIDValid) { 294 | UAV->operator_id_type = UAS_data2->OperatorID.OperatorIdType; 295 | strncpy(UAV->op_id, (char *)UAS_data2->OperatorID.OperatorId, ODID_ID_SIZE); 296 | } 297 | } 298 | 299 | void store_mac(uav_data *uav, uint8_t *payload) { 300 | memcpy(uav->mac, &payload[10], 6); 301 | } 302 | -------------------------------------------------------------------------------- /remoteid-mesh/src/odid_wifi.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2019 Intel Corporation 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | Open Drone ID C Library 7 | 8 | Maintainer: 9 | Gabriel Cox 10 | gabriel.c.cox@intel.com 11 | */ 12 | 13 | #ifndef _ODID_WIFI_H_ 14 | #define _ODID_WIFI_H_ 15 | 16 | /** 17 | * IEEE 802.11 structs to build management action frame 18 | */ 19 | struct __attribute__((__packed__)) ieee80211_mgmt { 20 | uint16_t frame_control; 21 | uint16_t duration; 22 | uint8_t da[6]; 23 | uint8_t sa[6]; 24 | uint8_t bssid[6]; 25 | uint16_t seq_ctrl; 26 | }; 27 | 28 | struct __attribute__((__packed__)) ieee80211_beacon { 29 | uint64_t timestamp; 30 | uint16_t beacon_interval; 31 | uint16_t capability; 32 | }; 33 | 34 | struct __attribute__((__packed__)) ieee80211_ssid { 35 | uint8_t element_id; 36 | uint8_t length; 37 | uint8_t ssid[]; 38 | }; 39 | 40 | struct __attribute__((__packed__)) ieee80211_supported_rates { 41 | uint8_t element_id; 42 | uint8_t length; 43 | uint8_t supported_rates; 44 | }; 45 | 46 | struct __attribute__((__packed__)) ieee80211_vendor_specific { 47 | uint8_t element_id; 48 | uint8_t length; 49 | uint8_t oui[3]; 50 | uint8_t oui_type; 51 | }; 52 | 53 | struct __attribute__((__packed__)) nan_service_discovery { 54 | uint8_t category; 55 | uint8_t action_code; 56 | uint8_t oui[3]; 57 | uint8_t oui_type; 58 | }; 59 | 60 | struct __attribute__((__packed__)) nan_attribute_header { 61 | uint8_t attribute_id; 62 | uint16_t length; 63 | }; 64 | 65 | struct __attribute__((__packed__)) nan_master_indication_attribute { 66 | struct nan_attribute_header header; 67 | uint8_t master_preference; 68 | uint8_t random_factor; 69 | }; 70 | 71 | struct __attribute__((__packed__)) nan_cluster_attribute { 72 | struct nan_attribute_header header; 73 | uint8_t device_mac[6]; 74 | uint8_t random_factor; 75 | uint8_t master_preference; 76 | uint8_t hop_count_to_anchor_master; 77 | uint8_t anchor_master_beacon_transmission_time[4]; 78 | }; 79 | 80 | struct __attribute__((__packed__)) nan_service_id_list_attribute { 81 | struct nan_attribute_header header; 82 | uint8_t service_id[6]; 83 | }; 84 | 85 | struct __attribute__((__packed__)) nan_service_descriptor_attribute { 86 | struct nan_attribute_header header; 87 | uint8_t service_id[6]; 88 | uint8_t instance_id; 89 | uint8_t requestor_instance_id; 90 | uint8_t service_control; 91 | uint8_t service_info_length; 92 | }; 93 | 94 | struct __attribute__((__packed__)) nan_service_descriptor_extension_attribute { 95 | struct nan_attribute_header header; 96 | uint8_t instance_id; 97 | uint16_t control; 98 | uint8_t service_update_indicator; 99 | }; 100 | 101 | struct __attribute__((__packed__)) ODID_service_info { 102 | uint8_t message_counter; 103 | ODID_MessagePack_encoded odid_message_pack[]; 104 | }; 105 | 106 | #endif // _ODID_WIFI_H_ 107 | -------------------------------------------------------------------------------- /remoteid-mesh/src/wifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2020 Simon Wunderlich, Marek Sobe 3 | Copyright (C) 2020 Doodle Labs 4 | 5 | SPDX-License-Identifier: Apache-2.0 6 | 7 | Open Drone ID C Library 8 | 9 | Maintainer: 10 | Simon Wunderlich 11 | sw@simonwunderlich.de 12 | */ 13 | 14 | #if defined(ARDUINO_ARCH_ESP32) 15 | #include 16 | #include 17 | #include 18 | int clock_gettime(clockid_t clk_id, struct timespec *tp); 19 | #else 20 | #include 21 | #include 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | #include "opendroneid.h" 29 | #include "odid_wifi.h" 30 | 31 | int clock_gettime(clockid_t, struct timespec *); 32 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 33 | #if defined(IDF_VER) 34 | #include 35 | #define cpu_to_be16(x) (bswap16(x)) 36 | #define cpu_to_be32(x) (bswap32(x)) 37 | #else 38 | #include 39 | #define cpu_to_be16(x) (bswap_16(x)) 40 | #define cpu_to_be32(x) (bswap_32(x)) 41 | #endif 42 | #define cpu_to_le16(x) (x) 43 | #define cpu_to_le64(x) (x) 44 | #else 45 | #define cpu_to_be16(x) (x) 46 | #define cpu_to_be32(x) (x) 47 | #define cpu_to_le16(x) (bswap_16(x)) 48 | #define cpu_to_le64(x) (bswap_64(x)) 49 | #endif 50 | 51 | #define IEEE80211_FCTL_FTYPE 0x000c 52 | #define IEEE80211_FCTL_STYPE 0x00f0 53 | 54 | #define IEEE80211_FTYPE_MGMT 0x0000 55 | #define IEEE80211_STYPE_ACTION 0x00D0 56 | #define IEEE80211_STYPE_BEACON 0x0080 57 | 58 | /* IEEE 802.11-2016 capability info */ 59 | #define IEEE80211_CAPINFO_ESS 0x0001 60 | #define IEEE80211_CAPINFO_IBSS 0x0002 61 | #define IEEE80211_CAPINFO_CF_POLLABLE 0x0004 62 | #define IEEE80211_CAPINFO_CF_POLLREQ 0x0008 63 | #define IEEE80211_CAPINFO_PRIVACY 0x0010 64 | #define IEEE80211_CAPINFO_SHORT_PREAMBLE 0x0020 65 | /* bits 6-7 reserved */ 66 | #define IEEE80211_CAPINFO_SPECTRUM_MGMT 0x0100 67 | #define IEEE80211_CAPINFO_QOS 0x0200 68 | #define IEEE80211_CAPINFO_SHORT_SLOTTIME 0x0400 69 | #define IEEE80211_CAPINFO_APSD 0x0800 70 | #define IEEE80211_CAPINFO_RADIOMEAS 0x1000 71 | /* bit 13 reserved */ 72 | #define IEEE80211_CAPINFO_DEL_BLOCK_ACK 0x4000 73 | #define IEEE80211_CAPINFO_IMM_BLOCK_ACK 0x8000 74 | 75 | /* IEEE 802.11 Element IDs */ 76 | #define IEEE80211_ELEMID_SSID 0x00 77 | #define IEEE80211_ELEMID_RATES 0x01 78 | #define IEEE80211_ELEMID_VENDOR 0xDD 79 | 80 | /* Neighbor Awareness Networking Specification v3.1 in section 2.8.2 81 | * The NAN Cluster ID is a MAC address that takes a value from 82 | * 50-6F-9A-01-00-00 to 50-6F-9A-01-FF-FF and is carried in the A3 field of 83 | * some of the NAN frames. The NAN Cluster ID is randomly chosen by the device 84 | * that initiates the NAN Cluster. 85 | * However, the ASTM Remote ID specification v1.1 specifies that the NAN 86 | * cluster ID must be fixed to the value 50-6F-9A-01-00-FF. 87 | */ 88 | static const uint8_t *get_nan_cluster_id(void) 89 | { 90 | static const uint8_t cluster_id[6] = { 0x50, 0x6F, 0x9A, 0x01, 0x00, 0xFF }; 91 | return cluster_id; 92 | } 93 | 94 | static int buf_fill_ieee80211_mgmt(uint8_t *buf, size_t *len, size_t buf_size, 95 | const uint16_t subtype, 96 | const uint8_t *dst_addr, 97 | const uint8_t *src_addr, 98 | const uint8_t *bssid) 99 | { 100 | if (*len + sizeof(struct ieee80211_mgmt) > buf_size) 101 | return -ENOMEM; 102 | 103 | struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)(buf + *len); 104 | mgmt->frame_control = (uint16_t) cpu_to_le16(IEEE80211_FTYPE_MGMT | subtype); 105 | mgmt->duration = cpu_to_le16(0x0000); 106 | memcpy(mgmt->da, dst_addr, sizeof(mgmt->da)); 107 | memcpy(mgmt->sa, src_addr, sizeof(mgmt->sa)); 108 | memcpy(mgmt->bssid, bssid, sizeof(mgmt->bssid)); 109 | mgmt->seq_ctrl = cpu_to_le16(0x0000); 110 | *len += sizeof(*mgmt); 111 | 112 | return 0; 113 | } 114 | 115 | static int buf_fill_ieee80211_beacon(uint8_t *buf, size_t *len, size_t buf_size, uint16_t interval_tu) 116 | { 117 | if (*len + sizeof(struct ieee80211_beacon) > buf_size) 118 | return -ENOMEM; 119 | 120 | struct ieee80211_beacon *beacon = (struct ieee80211_beacon *)(buf + *len); 121 | struct timespec ts; 122 | uint64_t mono_us = 0; 123 | 124 | #if defined(CLOCK_MONOTONIC) 125 | clock_gettime(CLOCK_MONOTONIC, &ts); 126 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 127 | #elif defined(CLOCK_REALTIME) 128 | clock_gettime(CLOCK_REALTIME, &ts); 129 | mono_us = (uint64_t)((double) ts.tv_sec * 1e6 + (double) ts.tv_nsec * 1e-3); 130 | #elif defined(ARDUINO) 131 | #warning "No REALTIME or MONOTONIC clock, using micros()." 132 | mono_us = micros(); 133 | #else 134 | #warning "Unable to set wifi timestamp." 135 | #endif 136 | beacon->timestamp = cpu_to_le64(mono_us); 137 | beacon->beacon_interval = cpu_to_le16(interval_tu); 138 | beacon->capability = cpu_to_le16(IEEE80211_CAPINFO_SHORT_SLOTTIME | IEEE80211_CAPINFO_SHORT_PREAMBLE); 139 | *len += sizeof(*beacon); 140 | 141 | return 0; 142 | } 143 | 144 | void drone_export_gps_data(ODID_UAS_Data *UAS_Data, char *buf, size_t buf_size) 145 | { 146 | ptrdiff_t len = 0; 147 | 148 | #define mprintf(...) {\ 149 | len += snprintf(buf + len, buf_size - (size_t)len, __VA_ARGS__); \ 150 | if ((len < 0) || ((size_t)len >= buf_size)) \ 151 | return; \ 152 | } 153 | 154 | mprintf("{\n\t\"Version\": \"1.1\",\n\t\"Response\": {\n"); 155 | 156 | mprintf("\t\t\"BasicID\": {\n"); 157 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 158 | if (!UAS_Data->BasicIDValid[i]) 159 | continue; 160 | mprintf("\t\t\t\"UAType%d\": %d,\n", i, UAS_Data->BasicID[i].UAType); 161 | mprintf("\t\t\t\"IDType%d\": %d,\n", i, UAS_Data->BasicID[i].IDType); 162 | mprintf("\t\t\t\"UASID%d\": \"%s\",\n", i, UAS_Data->BasicID[i].UASID); 163 | } 164 | mprintf("\t\t},\n"); 165 | 166 | mprintf("\t\t\"Location\": {\n"); 167 | mprintf("\t\t\t\"Status\": %d,\n", (int)UAS_Data->Location.Status); 168 | mprintf("\t\t\t\"Direction\": %f,\n", (double) UAS_Data->Location.Direction); 169 | mprintf("\t\t\t\"SpeedHorizontal\": %f,\n", (double) UAS_Data->Location.SpeedHorizontal); 170 | mprintf("\t\t\t\"SpeedVertical\": %f,\n", (double) UAS_Data->Location.SpeedVertical); 171 | mprintf("\t\t\t\"Latitude\": %f,\n", UAS_Data->Location.Latitude); 172 | mprintf("\t\t\t\"Longitude\": %f,\n", UAS_Data->Location.Longitude); 173 | mprintf("\t\t\t\"AltitudeBaro\": %f,\n", (double) UAS_Data->Location.AltitudeBaro); 174 | mprintf("\t\t\t\"AltitudeGeo\": %f,\n", (double) UAS_Data->Location.AltitudeGeo); 175 | mprintf("\t\t\t\"HeightType\": %d,\n", UAS_Data->Location.HeightType); 176 | mprintf("\t\t\t\"Height\": %f,\n", (double) UAS_Data->Location.Height); 177 | mprintf("\t\t\t\"HorizAccuracy\": %d,\n", UAS_Data->Location.HorizAccuracy); 178 | mprintf("\t\t\t\"VertAccuracy\": %d,\n", UAS_Data->Location.VertAccuracy); 179 | mprintf("\t\t\t\"BaroAccuracy\": %d,\n", UAS_Data->Location.BaroAccuracy); 180 | mprintf("\t\t\t\"SpeedAccuracy\": %d,\n", UAS_Data->Location.SpeedAccuracy); 181 | mprintf("\t\t\t\"TSAccuracy\": %d,\n", UAS_Data->Location.TSAccuracy); 182 | mprintf("\t\t\t\"TimeStamp\": %f,\n", (double) UAS_Data->Location.TimeStamp); 183 | mprintf("\t\t},\n"); 184 | 185 | mprintf("\t\t\"Authentication\": {\n"); 186 | mprintf("\t\t\t\"AuthType\": %d,\n", UAS_Data->Auth[0].AuthType); 187 | mprintf("\t\t\t\"LastPageIndex\": %d,\n", UAS_Data->Auth[0].LastPageIndex); 188 | mprintf("\t\t\t\"Length\": %d,\n", UAS_Data->Auth[0].Length); 189 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->Auth[0].Timestamp); 190 | for (int i = 0; i <= UAS_Data->Auth[0].LastPageIndex; i++) { 191 | mprintf("\t\t\t\"AuthData Page %d,\": \"%s\"\n", i, UAS_Data->Auth[i].AuthData); 192 | } 193 | mprintf("\t\t},\n"); 194 | 195 | mprintf("\t\t\"SelfID\": {\n"); 196 | mprintf("\t\t\t\"Description Type\": %d,\n", UAS_Data->SelfID.DescType); 197 | mprintf("\t\t\t\"Description\": \"%s\",\n", UAS_Data->SelfID.Desc); 198 | mprintf("\t\t},\n"); 199 | 200 | mprintf("\t\t\"Operator\": {\n"); 201 | mprintf("\t\t\t\"OperatorLocationType\": %d,\n", UAS_Data->System.OperatorLocationType); 202 | mprintf("\t\t\t\"ClassificationType\": %d,\n", UAS_Data->System.ClassificationType); 203 | mprintf("\t\t\t\"OperatorLatitude\": %f,\n", UAS_Data->System.OperatorLatitude); 204 | mprintf("\t\t\t\"OperatorLongitude\": %f,\n", UAS_Data->System.OperatorLongitude); 205 | mprintf("\t\t\t\"AreaCount\": %d,\n", UAS_Data->System.AreaCount); 206 | mprintf("\t\t\t\"AreaRadius\": %d,\n", UAS_Data->System.AreaRadius); 207 | mprintf("\t\t\t\"AreaCeiling\": %f,\n", (double) UAS_Data->System.AreaCeiling); 208 | mprintf("\t\t\t\"AreaFloor\": %f,\n", (double) UAS_Data->System.AreaFloor); 209 | mprintf("\t\t\t\"CategoryEU\": %d,\n", UAS_Data->System.CategoryEU); 210 | mprintf("\t\t\t\"ClassEU\": %d,\n", UAS_Data->System.ClassEU); 211 | mprintf("\t\t\t\"OperatorAltitudeGeo\": %f,\n", (double) UAS_Data->System.OperatorAltitudeGeo); 212 | mprintf("\t\t\t\"Timestamp\": %u,\n", UAS_Data->System.Timestamp); 213 | mprintf("\t\t}\n"); 214 | 215 | mprintf("\t\t\"OperatorID\": {\n"); 216 | mprintf("\t\t\t\"OperatorIdType\": %d,\n", UAS_Data->OperatorID.OperatorIdType); 217 | mprintf("\t\t\t\"OperatorId\": \"%s\",\n", UAS_Data->OperatorID.OperatorId); 218 | mprintf("\t\t},\n"); 219 | 220 | mprintf("\t}\n}"); 221 | } 222 | 223 | int odid_message_build_pack(ODID_UAS_Data *UAS_Data, void *pack, size_t buflen) 224 | { 225 | ODID_MessagePack_data msg_pack; 226 | ODID_MessagePack_encoded *msg_pack_enc; 227 | size_t len; 228 | 229 | /* create a complete message pack */ 230 | msg_pack.SingleMessageSize = ODID_MESSAGE_SIZE; 231 | msg_pack.MsgPackSize = 0; 232 | for (int i = 0; i < ODID_BASIC_ID_MAX_MESSAGES; i++) { 233 | if (UAS_Data->BasicIDValid[i]) { 234 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 235 | return -EINVAL; 236 | if (encodeBasicIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->BasicID[i]) == ODID_SUCCESS) 237 | msg_pack.MsgPackSize++; 238 | } 239 | } 240 | if (UAS_Data->LocationValid) { 241 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 242 | return -EINVAL; 243 | if (encodeLocationMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Location) == ODID_SUCCESS) 244 | msg_pack.MsgPackSize++; 245 | } 246 | for (int i = 0; i < ODID_AUTH_MAX_PAGES; i++) 247 | { 248 | if (UAS_Data->AuthValid[i]) { 249 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 250 | return -EINVAL; 251 | if (encodeAuthMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->Auth[i]) == ODID_SUCCESS) 252 | msg_pack.MsgPackSize++; 253 | } 254 | } 255 | if (UAS_Data->SelfIDValid) { 256 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 257 | return -EINVAL; 258 | if (encodeSelfIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->SelfID) == ODID_SUCCESS) 259 | msg_pack.MsgPackSize++; 260 | } 261 | if (UAS_Data->SystemValid) { 262 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 263 | return -EINVAL; 264 | if (encodeSystemMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->System) == ODID_SUCCESS) 265 | msg_pack.MsgPackSize++; 266 | } 267 | if (UAS_Data->OperatorIDValid) { 268 | if (msg_pack.MsgPackSize >= ODID_PACK_MAX_MESSAGES) 269 | return -EINVAL; 270 | if (encodeOperatorIDMessage((void *)&msg_pack.Messages[msg_pack.MsgPackSize], &UAS_Data->OperatorID) == ODID_SUCCESS) 271 | msg_pack.MsgPackSize++; 272 | } 273 | 274 | /* check that there is at least one message to send. */ 275 | if (msg_pack.MsgPackSize == 0) 276 | return -EINVAL; 277 | 278 | /* calculate the exact encoded message pack size. */ 279 | len = sizeof(*msg_pack_enc) - (ODID_PACK_MAX_MESSAGES - msg_pack.MsgPackSize) * ODID_MESSAGE_SIZE; 280 | 281 | /* check if there is enough space for the message pack. */ 282 | if (len > buflen) 283 | return -ENOMEM; 284 | 285 | msg_pack_enc = (ODID_MessagePack_encoded *) pack; 286 | if (encodeMessagePack(msg_pack_enc, &msg_pack) != ODID_SUCCESS) 287 | return -1; 288 | 289 | return (int) len; 290 | } 291 | 292 | int odid_wifi_build_nan_sync_beacon_frame(char *mac, uint8_t *buf, size_t buf_size) 293 | { 294 | /* Broadcast address */ 295 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 296 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 297 | /* "org.opendroneid.remoteid" hash */ 298 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 299 | const uint8_t *cluster_id = get_nan_cluster_id(); 300 | struct ieee80211_vendor_specific *vendor; 301 | struct nan_master_indication_attribute *master_indication_attr; 302 | struct nan_cluster_attribute *cluster_attr; 303 | struct nan_service_id_list_attribute *nsila; 304 | int ret; 305 | size_t len = 0; 306 | 307 | /* IEEE 802.11 Management Header */ 308 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, cluster_id); 309 | if (ret <0) 310 | return ret; 311 | 312 | /* Beacon */ 313 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, 0x0200); 314 | if (ret <0) 315 | return ret; 316 | 317 | /* Vendor Specific */ 318 | if (len + sizeof(*vendor) > buf_size) 319 | return -ENOMEM; 320 | 321 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 322 | memset(vendor, 0, sizeof(*vendor)); 323 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 324 | vendor->length = 0x22; 325 | memcpy(vendor->oui, wifi_alliance_oui, sizeof(vendor->oui)); 326 | vendor->oui_type = 0x13; 327 | len += sizeof(*vendor); 328 | 329 | /* NAN Master Indication attribute */ 330 | if (len + sizeof(*master_indication_attr) > buf_size) 331 | return -ENOMEM; 332 | 333 | master_indication_attr = (struct nan_master_indication_attribute *)(buf + len); 334 | memset(master_indication_attr, 0, sizeof(*master_indication_attr)); 335 | master_indication_attr->header.attribute_id = 0x00; 336 | master_indication_attr->header.length = cpu_to_le16(0x0002); 337 | /* Information that is used to indicate a NAN Device’s preference to serve 338 | * as the role of Master, with a larger value indicating a higher 339 | * preference. Values 1 and 255 are used for testing purposes only. 340 | */ 341 | master_indication_attr->master_preference = 0xFE; 342 | /* Random factor value 0xEA is recommended by the European Standard */ 343 | master_indication_attr->random_factor = 0xEA; 344 | len += sizeof(*master_indication_attr); 345 | 346 | /* NAN Cluster attribute */ 347 | if (len + sizeof(*cluster_attr) > buf_size) 348 | return -ENOMEM; 349 | 350 | cluster_attr = (struct nan_cluster_attribute *)(buf + len); 351 | memset(cluster_attr, 0, sizeof(*cluster_attr)); 352 | cluster_attr->header.attribute_id = 0x1; 353 | cluster_attr->header.length = cpu_to_le16(0x000D); 354 | memcpy(cluster_attr->device_mac, mac, sizeof(cluster_attr->device_mac)); 355 | cluster_attr->random_factor = 0xEA; 356 | cluster_attr->master_preference = 0xFE; 357 | cluster_attr->hop_count_to_anchor_master = 0x00; 358 | memset(cluster_attr->anchor_master_beacon_transmission_time, 0, sizeof(cluster_attr->anchor_master_beacon_transmission_time)); 359 | len += sizeof(*cluster_attr); 360 | 361 | /* NAN attributes */ 362 | if (len + sizeof(*nsila) > buf_size) 363 | return -ENOMEM; 364 | 365 | nsila = (struct nan_service_id_list_attribute *)(buf + len); 366 | memset(nsila, 0, sizeof(*nsila)); 367 | nsila->header.attribute_id = 0x02; 368 | nsila->header.length = cpu_to_le16(0x0006); 369 | memcpy(nsila->service_id, service_id, sizeof(service_id)); 370 | len += sizeof(*nsila); 371 | 372 | return (int) len; 373 | } 374 | 375 | int odid_wifi_build_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, char *mac, 376 | uint8_t send_counter, 377 | uint8_t *buf, size_t buf_size) 378 | { 379 | /* Neighbor Awareness Networking Specification v3.0 in section 2.8.1 380 | * NAN Network ID calls for the destination mac to be 51-6F-9A-01-00-00 */ 381 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 382 | /* "org.opendroneid.remoteid" hash */ 383 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 384 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 385 | const uint8_t *cluster_id = get_nan_cluster_id(); 386 | struct nan_service_discovery *nsd; 387 | struct nan_service_descriptor_attribute *nsda; 388 | struct nan_service_descriptor_extension_attribute *nsdea; 389 | struct ODID_service_info *si; 390 | int ret; 391 | size_t len = 0; 392 | 393 | /* IEEE 802.11 Management Header */ 394 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_ACTION, target_addr, (uint8_t *)mac, cluster_id); 395 | if (ret <0) 396 | return ret; 397 | 398 | /* NAN Service Discovery header */ 399 | if (len + sizeof(*nsd) > buf_size) 400 | return -ENOMEM; 401 | 402 | nsd = (struct nan_service_discovery *)(buf + len); 403 | memset(nsd, 0, sizeof(*nsd)); 404 | nsd->category = 0x04; /* IEEE 802.11 Public Action frame */ 405 | nsd->action_code = 0x09; /* IEEE 802.11 Public Action frame Vendor Specific*/ 406 | memcpy(nsd->oui, wifi_alliance_oui, sizeof(nsd->oui)); 407 | nsd->oui_type = 0x13; /* Identify Type and version of the NAN */ 408 | len += sizeof(*nsd); 409 | 410 | /* NAN Attribute for Service Descriptor header */ 411 | if (len + sizeof(*nsda) > buf_size) 412 | return -ENOMEM; 413 | 414 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 415 | nsda->header.attribute_id = 0x3; /* Service Descriptor Attribute type */ 416 | memcpy(nsda->service_id, service_id, sizeof(service_id)); 417 | /* always 1 */ 418 | nsda->instance_id = 0x01; /* always 1 */ 419 | nsda->requestor_instance_id = 0x00; /* from triggering frame */ 420 | nsda->service_control = 0x10; /* follow up */ 421 | len += sizeof(*nsda); 422 | 423 | /* ODID Service Info Attribute header */ 424 | if (len + sizeof(*si) > buf_size) 425 | return -ENOMEM; 426 | 427 | si = (struct ODID_service_info *)(buf + len); 428 | memset(si, 0, sizeof(*si)); 429 | si->message_counter = send_counter; 430 | len += sizeof(*si); 431 | 432 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 433 | if (ret < 0) 434 | return ret; 435 | len += ret; 436 | 437 | /* set the lengths according to the message pack lengths */ 438 | nsda->service_info_length = sizeof(*si) + ret; 439 | nsda->header.length = cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length); 440 | 441 | /* NAN Attribute for Service Descriptor extension header */ 442 | if (len + sizeof(*nsdea) > buf_size) 443 | return -ENOMEM; 444 | 445 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 446 | nsdea->header.attribute_id = 0xE; 447 | nsdea->header.length = cpu_to_le16(0x0004); 448 | nsdea->instance_id = 0x01; 449 | nsdea->control = cpu_to_le16(0x0200); 450 | nsdea->service_update_indicator = send_counter; 451 | len += sizeof(*nsdea); 452 | 453 | return (int) len; 454 | } 455 | 456 | int odid_wifi_build_message_pack_beacon_frame(ODID_UAS_Data *UAS_Data, char *mac, 457 | const char *SSID, size_t SSID_len, 458 | uint16_t interval_tu, uint8_t send_counter, 459 | uint8_t *buf, size_t buf_size) 460 | { 461 | /* Broadcast address */ 462 | uint8_t target_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 463 | uint8_t asd_stan_oui[3] = { 0xFA, 0x0B, 0xBC }; 464 | /* Mgmt Beacon frame mandatory fields + IE 221 */ 465 | struct ieee80211_ssid *ssid_s; 466 | struct ieee80211_supported_rates *rates; 467 | struct ieee80211_vendor_specific *vendor; 468 | 469 | /* Message Pack */ 470 | struct ODID_service_info *si; 471 | 472 | int ret; 473 | size_t len = 0; 474 | 475 | /* IEEE 802.11 Management Header */ 476 | ret = buf_fill_ieee80211_mgmt(buf, &len, buf_size, IEEE80211_STYPE_BEACON, target_addr, (uint8_t *)mac, (uint8_t *)mac); 477 | if (ret <0) 478 | return ret; 479 | 480 | /* Mandatory Beacon as of 802.11-2016 Part 11 */ 481 | ret = buf_fill_ieee80211_beacon(buf, &len, buf_size, interval_tu); 482 | if (ret <0) 483 | return ret; 484 | 485 | /* SSID: 1-32 bytes */ 486 | if (len + sizeof(*ssid_s) > buf_size) 487 | return -ENOMEM; 488 | 489 | ssid_s = (struct ieee80211_ssid *)(buf + len); 490 | if(!SSID || (SSID_len ==0) || (SSID_len > 32)) 491 | return -EINVAL; 492 | ssid_s->element_id = IEEE80211_ELEMID_SSID; 493 | ssid_s->length = (uint8_t) SSID_len; 494 | memcpy(ssid_s->ssid, SSID, ssid_s->length); 495 | len += sizeof(*ssid_s) + SSID_len; 496 | 497 | /* Supported Rates: 1 record at minimum */ 498 | if (len + sizeof(*rates) > buf_size) 499 | return -ENOMEM; 500 | 501 | rates = (struct ieee80211_supported_rates *)(buf + len); 502 | rates->element_id = IEEE80211_ELEMID_RATES; 503 | rates->length = 1; // One rate only 504 | rates->supported_rates = 0x8C; // 6 Mbps 505 | len += sizeof(*rates); 506 | 507 | /* Vendor Specific Information Element (IE 221) */ 508 | if (len + sizeof(*vendor) > buf_size) 509 | return -ENOMEM; 510 | 511 | vendor = (struct ieee80211_vendor_specific *)(buf + len); 512 | vendor->element_id = IEEE80211_ELEMID_VENDOR; 513 | vendor->length = 0x00; // Length updated at end of function 514 | memcpy(vendor->oui, asd_stan_oui, sizeof(vendor->oui)); 515 | vendor->oui_type = 0x0D; 516 | len += sizeof(*vendor); 517 | 518 | /* ODID Service Info Attribute header */ 519 | if (len + sizeof(*si) > buf_size) 520 | return -ENOMEM; 521 | 522 | si = (struct ODID_service_info *)(buf + len); 523 | memset(si, 0, sizeof(*si)); 524 | si->message_counter = send_counter; 525 | len += sizeof(*si); 526 | 527 | ret = odid_message_build_pack(UAS_Data, buf + len, buf_size - len); 528 | if (ret < 0) 529 | return ret; 530 | len += ret; 531 | 532 | /* set the lengths according to the message pack lengths */ 533 | vendor->length = sizeof(vendor->oui) + sizeof(vendor->oui_type) + sizeof(*si) + ret; 534 | 535 | return (int) len; 536 | } 537 | 538 | int odid_message_process_pack(ODID_UAS_Data *UAS_Data, uint8_t *pack, size_t buflen) 539 | { 540 | ODID_MessagePack_encoded *msg_pack_enc = (ODID_MessagePack_encoded *) pack; 541 | size_t size = sizeof(*msg_pack_enc) - ODID_MESSAGE_SIZE * (ODID_PACK_MAX_MESSAGES - msg_pack_enc->MsgPackSize); 542 | if (size > buflen) 543 | return -ENOMEM; 544 | 545 | odid_initUasData(UAS_Data); 546 | 547 | if (decodeMessagePack(UAS_Data, msg_pack_enc) != ODID_SUCCESS) 548 | return -1; 549 | 550 | return (int) size; 551 | } 552 | 553 | int odid_wifi_receive_message_pack_nan_action_frame(ODID_UAS_Data *UAS_Data, 554 | char *mac, uint8_t *buf, size_t buf_size) 555 | { 556 | struct ieee80211_mgmt *mgmt; 557 | struct nan_service_discovery *nsd; 558 | struct nan_service_descriptor_attribute *nsda; 559 | struct nan_service_descriptor_extension_attribute *nsdea; 560 | struct ODID_service_info *si; 561 | uint8_t target_addr[6] = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; 562 | uint8_t wifi_alliance_oui[3] = { 0x50, 0x6F, 0x9A }; 563 | uint8_t service_id[6] = { 0x88, 0x69, 0x19, 0x9D, 0x92, 0x09 }; 564 | int ret; 565 | size_t len = 0; 566 | 567 | /* IEEE 802.11 Management Header */ 568 | if (len + sizeof(*mgmt) > buf_size) 569 | return -EINVAL; 570 | mgmt = (struct ieee80211_mgmt *)(buf + len); 571 | if ((mgmt->frame_control & cpu_to_le16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) != 572 | cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION)) 573 | return -EINVAL; 574 | if (memcmp(mgmt->da, target_addr, sizeof(mgmt->da)) != 0) 575 | return -EINVAL; 576 | memcpy(mac, mgmt->sa, sizeof(mgmt->sa)); 577 | 578 | len += sizeof(*mgmt); 579 | 580 | /* NAN Service Discovery header */ 581 | if (len + sizeof(*nsd) > buf_size) 582 | return -EINVAL; 583 | nsd = (struct nan_service_discovery *)(buf + len); 584 | if (nsd->category != 0x04) 585 | return -EINVAL; 586 | if (nsd->action_code != 0x09) 587 | return -EINVAL; 588 | if (memcmp(nsd->oui, wifi_alliance_oui, sizeof(wifi_alliance_oui)) != 0) 589 | return -EINVAL; 590 | if (nsd->oui_type != 0x13) 591 | return -EINVAL; 592 | len += sizeof(*nsd); 593 | 594 | /* NAN Attribute for Service Descriptor header */ 595 | if (len + sizeof(*nsda) > buf_size) 596 | return -EINVAL; 597 | nsda = (struct nan_service_descriptor_attribute *)(buf + len); 598 | if (nsda->header.attribute_id != 0x3) 599 | return -EINVAL; 600 | if (memcmp(nsda->service_id, service_id, sizeof(service_id)) != 0) 601 | return -EINVAL; 602 | if (nsda->instance_id != 0x01) 603 | return -EINVAL; 604 | if (nsda->service_control != 0x10) 605 | return -EINVAL; 606 | len += sizeof(*nsda); 607 | 608 | si = (struct ODID_service_info *)(buf + len); 609 | ret = odid_message_process_pack(UAS_Data, buf + len + sizeof(*si), buf_size - len - sizeof(*nsdea)); 610 | if (ret < 0) 611 | return -EINVAL; 612 | if (nsda->service_info_length != (sizeof(*si) + ret)) 613 | return -EINVAL; 614 | if (nsda->header.length != (cpu_to_le16(sizeof(*nsda) - sizeof(struct nan_attribute_header) + nsda->service_info_length))) 615 | return -EINVAL; 616 | len += sizeof(*si) + ret; 617 | 618 | /* NAN Attribute for Service Descriptor extension header */ 619 | if (len + sizeof(*nsdea) > buf_size) 620 | return -ENOMEM; 621 | nsdea = (struct nan_service_descriptor_extension_attribute *)(buf + len); 622 | if (nsdea->header.attribute_id != 0xE) 623 | return -EINVAL; 624 | if (nsdea->header.length != cpu_to_le16(0x0004)) 625 | return -EINVAL; 626 | if (nsdea->instance_id != 0x01) 627 | return -EINVAL; 628 | if (nsdea->control != cpu_to_le16(0x0200)) 629 | return -EINVAL; 630 | 631 | return 0; 632 | } 633 | -------------------------------------------------------------------------------- /remoteid-mesh/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask>=2.0.0 2 | flask-socketio>=5.0.0 3 | requests>=2.25.0 4 | urllib3>=1.26.0 5 | pyserial>=3.5 6 | --------------------------------------------------------------------------------