feat(simulator): virtual drone fleet data generator
One process per drone: seeded patrol kinematics, sensor streams at realistic rates, detection events, hot current/ blocks sealed into Hive-partitioned Parquet, and the compact UDP state broadcast with peer RSSI derivation. Scales via docker compose --scale.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Virtual drone fleet
|
||||
|
||||
A data generator that exercises the **exact on-board pipeline** described in [03 — Data platform](../docs/03-data-platform.md): seeded kinematics along a patrol route, sensor streams at realistic rates, detection events, the hot `current/` → sealed Parquet write path, and the compact UDP state broadcast between drones.
|
||||
|
||||
One process = one drone. Scaling the swarm is a Compose flag.
|
||||
|
||||
## Run a swarm
|
||||
|
||||
```bash
|
||||
# 5 drones (default), 2-minute flight, shared flight id
|
||||
FLIGHT_ID=$(date -u +%Y%m%dT%H%MZ)-sim docker compose up --build --scale drone=5
|
||||
|
||||
# 10 drones, 5-minute flight, 4x accelerated
|
||||
DRONE_COUNT=10 DURATION_S=300 SPEEDUP=4 docker compose up --build --scale drone=10
|
||||
```
|
||||
|
||||
Output lands in `./data/` with the standard layout:
|
||||
|
||||
```
|
||||
data/dataset=telemetry/flight=…/drone=…/sensor=imu/year=…/…/hour=…/data.parquet
|
||||
data/dataset=detections/flight=…/drone=…/…
|
||||
data/dataset=state/flight=…/drone=…/… ← sent + received broadcasts
|
||||
```
|
||||
|
||||
## Run a single drone without Docker
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
DRONE_ID=dr-01 DURATION_S=30 SPEEDUP=10 DATA_DIR=./data python -m virtual_drone.main
|
||||
```
|
||||
|
||||
## Query the results
|
||||
|
||||
```bash
|
||||
python - <<'EOF'
|
||||
import duckdb
|
||||
con = duckdb.connect()
|
||||
print(con.sql("""
|
||||
SELECT drone, sensor, count(*) AS rows,
|
||||
to_timestamp(min(ts_ns)/1e9) AS first_row,
|
||||
to_timestamp(max(ts_ns)/1e9) AS last_row
|
||||
FROM read_parquet('data/dataset=telemetry/**/*.parquet', hive_partitioning=true)
|
||||
GROUP BY drone, sensor ORDER BY drone, sensor
|
||||
"""))
|
||||
print(con.sql("""
|
||||
SELECT drone, direction, count(*) AS frames, count(DISTINCT peer_id) AS peers
|
||||
FROM read_parquet('data/dataset=state/**/*.parquet', hive_partitioning=true)
|
||||
GROUP BY drone, direction ORDER BY drone, direction
|
||||
"""))
|
||||
EOF
|
||||
```
|
||||
|
||||
## Reproducibility
|
||||
|
||||
Every run is deterministic per `(SEED, DRONE_ID)`: same route jitter, same sensor noise, same detection sequence. A bug report is a seed and a config, not a description.
|
||||
|
||||
## Knobs
|
||||
|
||||
| Env | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `DRONE_ID` | derived from hostname | Unique per container automatically under `--scale` |
|
||||
| `FLIGHT_ID` | generated | Set explicitly so all drones share one flight partition |
|
||||
| `SEED` | `42` | Determinism root |
|
||||
| `DURATION_S` | `120` | Simulated flight length |
|
||||
| `SPEEDUP` | `1.0` | Wall-clock acceleration |
|
||||
| `IMU_HZ` | `50` | High-rate sensor load |
|
||||
| `STATE_HZ` | `5` | Broadcast frequency |
|
||||
| `AREA_SIZE_M` | `400` | Patrol square side |
|
||||
Reference in New Issue
Block a user