#!/usr/bin/env python3 """ Mock Intercept Agent for development and testing. This provides a simulated agent that generates fake data for testing the controller without needing actual SDR hardware. Usage: python tests/mock_agent.py [--port 8021] [--name mock-agent-1] """ from __future__ import annotations import argparse import random import string import threading import time from datetime import datetime, timezone from flask import Flask, jsonify, request app = Flask(__name__) # State running_modes: set[str] = set() start_time = time.time() agent_name = "mock-agent-1" # Simulated data generators def generate_aircraft() -> list[dict]: """Generate fake ADS-B aircraft data.""" aircraft = [] for _ in range(random.randint(3, 10)): icao = "".join(random.choices(string.hexdigits.upper()[:6], k=6)) callsign = random.choice(["UAL", "DAL", "AAL", "SWA", "JBU"]) + str(random.randint(100, 9999)) aircraft.append( { "icao": icao, "callsign": callsign, "altitude": random.randint(5000, 45000), "speed": random.randint(200, 550), "heading": random.randint(0, 359), "lat": round(40.0 + random.uniform(-2, 2), 4), "lon": round(-74.0 + random.uniform(-2, 2), 4), "vertical_rate": random.randint(-2000, 2000), "squawk": str(random.randint(1000, 7777)), "last_seen": datetime.now(timezone.utc).isoformat(), } ) return aircraft def generate_sensors() -> list[dict]: """Generate fake 433MHz sensor data.""" sensors = [] models = ["Acurite-Tower", "Oregon-THGR122N", "LaCrosse-TX141W", "Ambient-F007TH"] for _i in range(random.randint(2, 5)): sensors.append( { "time": datetime.now(timezone.utc).isoformat(), "model": random.choice(models), "id": random.randint(1, 255), "channel": random.randint(1, 3), "temperature_C": round(random.uniform(-10, 35), 1), "humidity": random.randint(20, 95), "battery_ok": random.choice([0, 1]), } ) return sensors def generate_wifi_networks() -> list[dict]: """Generate fake WiFi network data.""" networks = [] ssids = ["HomeNetwork", "Linksys", "NETGEAR", "xfinitywifi", "ATT-WIFI", "CoffeeShop-Guest"] for ssid in random.sample(ssids, random.randint(3, 6)): bssid = ":".join([f"{random.randint(0, 255):02X}" for _ in range(6)]) networks.append( { "ssid": ssid, "bssid": bssid, "channel": random.choice([1, 6, 11, 36, 40, 44, 48]), "signal": random.randint(-80, -30), "encryption": random.choice(["WPA2", "WPA3", "WEP", "Open"]), "clients": random.randint(0, 10), "last_seen": datetime.now(timezone.utc).isoformat(), } ) return networks def generate_bluetooth_devices() -> list[dict]: """Generate fake Bluetooth device data.""" devices = [] names = ["iPhone", "Galaxy S21", "AirPods", "Tile Tracker", "Fitbit", "Unknown"] for _ in range(random.randint(2, 8)): mac = ":".join([f"{random.randint(0, 255):02X}" for _ in range(6)]) devices.append( { "address": mac, "name": random.choice(names), "rssi": random.randint(-90, -40), "type": random.choice(["LE", "Classic", "Dual"]), "manufacturer": random.choice(["Apple", "Samsung", "Unknown"]), "last_seen": datetime.now(timezone.utc).isoformat(), } ) return devices def generate_vessels() -> list[dict]: """Generate fake AIS vessel data.""" vessels = [] vessel_names = ["EVERGREEN", "MAERSK WINNER", "OOCL HONG KONG", "MSC GULSUN", "CMA CGM MARCO POLO"] for name in random.sample(vessel_names, random.randint(2, 4)): mmsi = str(random.randint(200000000, 800000000)) vessels.append( { "mmsi": mmsi, "name": name, "callsign": "".join(random.choices(string.ascii_uppercase, k=5)), "ship_type": random.choice(["Cargo", "Tanker", "Passenger", "Fishing"]), "lat": round(40.5 + random.uniform(-0.5, 0.5), 4), "lon": round(-73.9 + random.uniform(-0.5, 0.5), 4), "speed": round(random.uniform(0, 25), 1), "course": random.randint(0, 359), "destination": random.choice(["NEW YORK", "NEWARK", "BALTIMORE", "BOSTON"]), "last_seen": datetime.now(timezone.utc).isoformat(), } ) return vessels # Data snapshot storage data_snapshots: dict[str, list] = {} def update_data_snapshot(mode: str): """Update data snapshot for a mode.""" if mode == "adsb": data_snapshots[mode] = generate_aircraft() elif mode == "sensor": data_snapshots[mode] = generate_sensors() elif mode == "wifi": data_snapshots[mode] = generate_wifi_networks() elif mode == "bluetooth": data_snapshots[mode] = generate_bluetooth_devices() elif mode == "ais": data_snapshots[mode] = generate_vessels() else: data_snapshots[mode] = [] # Background data generation threads data_threads: dict[str, threading.Event] = {} def data_generator_loop(mode: str, stop_event: threading.Event): """Background loop to generate data periodically.""" while not stop_event.is_set(): update_data_snapshot(mode) stop_event.wait(random.uniform(2, 5)) # ============================================================================= # Routes # ============================================================================= @app.route("/capabilities") def capabilities(): """Return mock capabilities.""" return jsonify( { "modes": { "pager": True, "sensor": True, "adsb": True, "ais": True, "acars": True, "aprs": True, "wifi": True, "bluetooth": True, "dsc": True, "rtlamr": True, "tscm": True, "satellite": True, "listening_post": True, }, "devices": [{"index": 0, "name": "Mock RTL-SDR", "type": "rtlsdr", "serial": "MOCK001"}], "agent_version": "1.0.0-mock", } ) @app.route("/status") def status(): """Return agent status.""" return jsonify( { "running_modes": list(running_modes), "uptime": time.time() - start_time, "push_enabled": False, "push_connected": False, } ) @app.route("/health") def health(): """Health check.""" return jsonify({"status": "healthy", "version": "1.0.0-mock"}) @app.route("/config", methods=["GET", "POST"]) def config(): """Config endpoint.""" if request.method == "POST": return jsonify({"status": "updated", "config": {}}) return jsonify( { "name": agent_name, "port": request.environ.get("SERVER_PORT", 8021), "push_enabled": False, "modes_enabled": dict.fromkeys(["pager", "sensor", "adsb", "ais", "wifi", "bluetooth"], True), } ) @app.route("//start", methods=["POST"]) def start_mode(mode: str): """Start a mode.""" if mode in running_modes: return jsonify({"status": "error", "message": f"{mode} already running"}), 409 running_modes.add(mode) # Start data generation thread stop_event = threading.Event() data_threads[mode] = stop_event thread = threading.Thread(target=data_generator_loop, args=(mode, stop_event)) thread.daemon = True thread.start() # Generate initial data update_data_snapshot(mode) return jsonify({"status": "started", "mode": mode}) @app.route("//stop", methods=["POST"]) def stop_mode(mode: str): """Stop a mode.""" if mode not in running_modes: return jsonify({"status": "not_running"}) running_modes.discard(mode) # Stop data generation thread if mode in data_threads: data_threads[mode].set() del data_threads[mode] # Clear data if mode in data_snapshots: del data_snapshots[mode] return jsonify({"status": "stopped", "mode": mode}) @app.route("//status") def mode_status(mode: str): """Get mode status.""" return jsonify({"running": mode in running_modes, "data_count": len(data_snapshots.get(mode, []))}) @app.route("//data") def mode_data(mode: str): """Get current data snapshot.""" # Generate fresh data if mode is running but no snapshot exists if mode in running_modes and mode not in data_snapshots: update_data_snapshot(mode) return jsonify( { "mode": mode, "data": data_snapshots.get(mode, []), "timestamp": datetime.now(timezone.utc).isoformat(), "agent_name": agent_name, } ) # ============================================================================= # Main # ============================================================================= def main(): global agent_name, start_time parser = argparse.ArgumentParser(description="Mock Intercept Agent") parser.add_argument("--port", "-p", type=int, default=8021, help="Port (default: 8021)") parser.add_argument("--name", "-n", default="mock-agent-1", help="Agent name") parser.add_argument("--debug", action="store_true", help="Enable debug mode") args = parser.parse_args() agent_name = args.name start_time = time.time() print("=" * 60) print(" MOCK INTERCEPT AGENT") print(" For development and testing") print("=" * 60) print() print(f" Agent Name: {agent_name}") print(f" Port: {args.port}") print() print(" Available modes: all (simulated data)") print() print(f" Listening on http://0.0.0.0:{args.port}") print() print(" Press Ctrl+C to stop") print() app.run(host="0.0.0.0", port=args.port, debug=args.debug) if __name__ == "__main__": main()