
348 |
349 |
350 | ---
351 |
352 | ## 🛒 **Hardware Store**
353 |
354 | Get professional PCBs and complete kits:
355 |
356 |
357 |
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 |
--------------------------------------------------------------------------------