@h2loop/uav 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: uav
3
+ description: >-
4
+ UAV domain expertise for ArduPilot / PX4 work — flight logs, parameters, tuning, and
5
+ telemetry. Load this at the START of any UAV task in whatever mode you are in (Code,
6
+ Plan, Debug, Ask): deciding which uav_* tool answers a question, decoding/triaging a
7
+ .bin/.log/.ulog/.tlog, auditing params, recommending tuning, interpreting a check
8
+ (battery, attitude, altitude, hover, rcou, rcin, vibration, mag, ekf), explaining WHY a
9
+ guardrail bound is what it is, or narrating a cross-flight regression. It carries the
10
+ tool map, the grounding discipline (guardrails judge, you narrate), signal semantics,
11
+ per-check interpretation, the tuning playbook, and the fan-out rules. This is a domain
12
+ LAYER, not a mode — you stay in your current mode and use it.
13
+ ---
14
+ # UAV skill — ArduPilot/PX4 flight-log domain layer
15
+
16
+ You are doing UAV work inside a normal Hydron mode (Code / Plan / Debug / Ask). This skill
17
+ is the domain layer that rides across that mode — it does not replace it. The deterministic
18
+ harness (the `uav_*` tools) produces findings (severity + verdict); your job is to drive the
19
+ right tool, then **explain** the results grounded in guardrail bounds and their sources —
20
+ never to author a verdict from raw numbers.
21
+
22
+ ## Tooling — which tool for which job (call these directly; no mode switch)
23
+
24
+ - **Triage one log** → `uav_analyze` (.bin/.log/.tlog/.ulog): decode + the vehicle-aware
25
+ system checks + param audit + persist the episode. It already isolates the decode, the
26
+ full param load, and per-table SQL and returns only the rendered report — call it directly,
27
+ do not wrap it in a subagent.
28
+ - **Ad-hoc read** → `uav_query`: read-only SQL over ONE decoded flight, for a single quick
29
+ peek (one aggregate / a few rows).
30
+ - **Real computation** → `uav_python`: pandas/numpy over the flight's decoded tables
31
+ (`load("VIBE")`, `load("ATT")`, …; `TimeUS` is µs since boot; `print()` results). Use this
32
+ the moment an analysis needs binning, correlation, scoring/ranking windows, regression, FFT,
33
+ or multi-signal joins. Rule of thumb: **if you're about to run a 2nd `uav_query` to assemble
34
+ an answer, write `uav_python` instead.** Numbers must come from code you actually ran.
35
+ - **Chart** → `uav_plot` (`timeseries` | `xy` | `fft`): the FINAL picture, not a substitute
36
+ for reasoning. Never hand-write SQL for a visualisation.
37
+ - **Parameters** → `uav_param_audit` (safety / range / consistency; ranges grounded in
38
+ `apm.pdef.xml`, never invented).
39
+ - **Tuning** → `uav_tune`: FFT over gyro/VIBE → dominant peak → recommended `INS_HNTCH_*`
40
+ notch params (recommend, then push via the gated MAVLink link — never auto-write).
41
+ - **Run-tracking** → `uav_tag`: label a flight (test profile / campaign) — the param-history
42
+ record ArduPilot lacks.
43
+ - **Handoff** → `uav_export`: one CSV per message type for PlotJuggler.
44
+ - **Record a conclusion** → `uav_finding`: at the END of an investigation, `op:"list"` first,
45
+ judge whether it's already represented (same root cause counts), then `op:"add"` only if
46
+ genuinely new (`supersedes_id` to replace a stale one). Only ABSOLUTE-bound findings the
47
+ deterministic checks did not already cover — never a relative-only one.
48
+ - **Bench / live vehicle** → `serial_*` (watch/drive the FC console) and the `mavlink` MCP
49
+ (read params/telemetry; gated param writes) when a link is configured.
50
+
51
+ ## Load the reference that matches the task
52
+
53
+ Read each by its explicit path under `references/`:
54
+
55
+ - **`references/grounding.md`** — how thresholds/guardrails are sourced and cited. The core
56
+ principle (the LLM narrates, the guardrails judge), the three guardrail sources (OEM
57
+ datasheet / rule-of-thumb / per-airframe tuned) and their precedence, and the work-style
58
+ rules (chain-of-thought, ask-don't-assume, cite the bound, surface-don't-over-verdict).
59
+ **Read this before you state why a bound is what it is, or you are tempted to judge a raw
60
+ number yourself. A flag must clear an ABSOLUTE grounded bound — not merely stand out
61
+ relative to peers or its own baseline.**
62
+ - **`references/signals.md`** — signal/column dictionary + analysis method. Which column
63
+ actually answers a question (e.g. throttle = `RCOU` motor-mean / `CTUN.ThO`, **not** the
64
+ near-constant `CTUN.ThH`), EKF innovation names, plus the reason-and-explore-before-plotting
65
+ discipline. **Read this before any correlation / "does X relate to Y" / visualisation
66
+ question.**
67
+ - **`references/checks.md`** — per-check interpretation (battery, attitude, altitude, hover,
68
+ rcou, rcin, vibration, mag, ekf): the table/fields it reads, the rule, what an anomaly means
69
+ physically, and how to talk about it without over-verdicting. Read this when interpreting
70
+ any finding.
71
+ - **`references/tuning-methodology.md`** — the ORDERED tuning playbook: pre-tune gates →
72
+ harmonic notch → rate PID → attitude → position/altitude → sensor source (`EK3_SRC`) → drift
73
+ over the sortie. Read this for ANY "how do I tune / what params / why is it oscillating /
74
+ altitude is noisy" question. `references/tuning.md` is the notch-recommender detail.
75
+ - **`references/compare.md`** — how to narrate a cross-flight diff (params, findings, regime;
76
+ tuning-phase vs production reference; direction/magnitude; ranking without closing the
77
+ verdict). Read this for `/uav-compare` or the `uav-compare` subagent's output.
78
+ - **`references/project-config.md`** — the optional per-project override files under
79
+ `<project>/.hydron/` (thresholds, param rules, param metadata, format converters): what each
80
+ controls, merge-vs-replace semantics, and the exact path to WRITE when a user asks to change
81
+ a bound, a param range, or add a custom log format.
82
+
83
+ If the right reference is not obvious, read `references/grounding.md` first — the grounding
84
+ discipline governs almost every UAV answer.
85
+
86
+ ## Orchestration — split unrelated work, fan it out
87
+
88
+ - When ONE message asks MULTIPLE INDEPENDENT investigation-class questions — each needing its
89
+ own multi-step drill-down, none depending on another's answer — spawn ONE **`uav-investigate`**
90
+ subagent PER question and issue those task calls TOGETHER so they run in parallel, then
91
+ synthesize the briefs. Do NOT work them serially in this thread, and do NOT merge unrelated
92
+ questions into one investigation. (E.g. "what caused the EKF spike at 800s?", "walk the
93
+ altitude-excursion cause-chain", and "cross-check the IMUs" → three parallel investigations.)
94
+ - Split only when the questions are genuinely independent. If one's answer feeds the next, do
95
+ them in order. If each is a single one-shot tool call (several `uav_analyze`/`uav_plot`
96
+ requests), just call the tools directly — in parallel where independent — with no subagent.
97
+ - Other specialist subagents: **`uav-compare`** (cross-flight regression over an airframe's
98
+ history) and **`uav-verify`** (adversarially confirm a proposed flag clears an ABSOLUTE
99
+ bound — use before reporting any anomaly the deterministic checks did NOT produce). Each runs
100
+ the same grounded discipline and returns only its brief; the heavy chatter stays out of the
101
+ main thread.
@@ -0,0 +1,174 @@
1
+ # Per-check interpretation
2
+
3
+ **21 system checks** run as SQL over the per-flight in-memory tables, plus a **parameter
4
+ audit** over the PARM set (`param.*` findings). Each yields a `severity`
5
+ (`OK` / `HIGH` / `CRITICAL`) and a `verdict` (`PASS` / `FAIL` / `SKIP` — `SKIP` = required
6
+ table/param absent). These are deterministic. Your job is to explain a FAIL physically and
7
+ rank it — never to manufacture a verdict. Always cite the bound and its source (see
8
+ `grounding.md`). Column names follow the sample log; the decoder is FMT-driven, so verify
9
+ per-firmware at decode time. The detailed write-ups below cover the core checks; the full
10
+ catalog (by subsystem) and the param audit follow at the end.
11
+
12
+ ## battery (CRITICAL)
13
+
14
+ - **Tables / fields:** `ERR` (`Subsys`, `ECode`), correlated with `MSG`, `MODE`, `BAT`.
15
+ - **Rule:** `ERR.Subsys=6 AND ECode=1` with count > 0 → CRITICAL. A correlation check —
16
+ join on `TimeUS` to assemble the "battery failsafe → RTL" story (`ERR` + `MSG`
17
+ "Battery critical" + `MODE`→RTL + `BAT` sag).
18
+ - **Physically:** the flight controller declared a battery failsafe — voltage or
19
+ capacity crossed the configured floor and the vehicle acted on it (commonly RTL/Land).
20
+ - **How to talk about it:** narrate the timeline (when the failsafe fired, what the mode
21
+ did after), cite the `err_subsys:6 / err_ecode:1` signature as the ArduPilot battery-failsafe
22
+ firmware constant (rule-of-thumb), and point
23
+ at the `BAT` sag if present. Don't speculate on root cause beyond what the timeline
24
+ shows; surface it as the top-ranked finding when present.
25
+
26
+ ## attitude (HIGH)
27
+
28
+ - **Tables / fields:** `ATT` — `DesRoll-Roll`, `DesPitch-Pitch`.
29
+ - **Rule:** sustained `MAX(ABS(desired - actual)) > max_deg` (default 5°, over a sample
30
+ window) → HIGH.
31
+ - **Physically:** the vehicle could not hold the commanded attitude — control authority,
32
+ tuning, or a disturbance (wind, payload, a failing motor) is preventing it from
33
+ tracking the setpoint.
34
+ - **How to talk about it:** report the peak divergence in degrees vs the 5° bound (a seed
35
+ default), note whether it was sustained or a transient, and correlate with rcou
36
+ saturation or vibration before suggesting a cause. Rank, don't conclude.
37
+
38
+ ## altitude (HIGH)
39
+
40
+ - **Tables / fields:** `CTUN` — `DAlt-Alt` (desired vs achieved altitude).
41
+ - **Rule:** `MAX(ABS(DAlt-Alt)) > max_m` (default 2 m) → HIGH.
42
+ - **Physically:** altitude tracking error — the vehicle is not holding commanded height,
43
+ often tied to thrust margin, baro/EKF altitude estimate, or aggressive descent.
44
+ - **How to talk about it:** state the peak error in metres vs the 2 m seed bound; tie it
45
+ to hover-throttle or EKF findings if they co-occur. A few-metre transient in an
46
+ aggressive manoeuvre is different from sustained error in a hold — say which.
47
+
48
+ ## hover (HIGH)
49
+
50
+ - **Tables / fields:** `CTUN.ThH` (hover throttle).
51
+ - **Rule:** `AVG(ThH)` outside the band `[0.25, 0.75]` → HIGH.
52
+ - **Physically:** hover throttle near the band edges signals a thrust-to-weight problem —
53
+ low end = overpowered/twitchy or light; high end = underpowered/overweight, little
54
+ margin for manoeuvre or wind.
55
+ - **How to talk about it:** report the average hover throttle vs the `[0.25, 0.75]`
56
+ rule-of-thumb band, and frame it as a thrust-margin observation. Relate to the ≥ 2:1
57
+ thrust-to-weight rule-of-thumb. Surface; the engineer judges acceptability.
58
+
59
+ ## rcou — motor outputs (HIGH)
60
+
61
+ - **Tables / fields:** `RCOU` — `C1..C8`.
62
+ - **Rule:** any channel pegged at max → HIGH (saturation / imbalance).
63
+ - **Physically:** a motor output pinned at its ceiling means that motor has no headroom —
64
+ the mixer is asking for more than it can give, indicating imbalance, a weak/failing
65
+ motor, CG offset, or undersized propulsion.
66
+ - **How to talk about it:** name which channels saturated and for how long, and correlate
67
+ with attitude divergence (a saturated motor often explains an attitude FAIL). Describe
68
+ the imbalance pattern; leave the mechanical diagnosis to the engineer.
69
+
70
+ ## rcin — pilot inputs (OK / context)
71
+
72
+ - **Tables / fields:** `RCIN` — `C3` (throttle), other channels for range sanity.
73
+ - **Rule:** values outside `[1000, 2000]` µs → HIGH; otherwise context.
74
+ - **Physically:** this is what the pilot commanded. Out-of-range values can mean RC
75
+ calibration, signal loss, or a failsafe trigger; in-range values are context that helps
76
+ explain other findings (e.g. an attitude divergence during an aggressive stick input).
77
+ - **How to talk about it:** mostly use rcin as supporting context — "the divergence
78
+ coincided with a full-throttle climb the pilot commanded." Only flag it as a finding
79
+ when the ranges are genuinely out of bounds.
80
+
81
+ ## vibration (HIGH)
82
+
83
+ - **Tables / fields:** `VIBE` — `VibeX`, `VibeY`, `VibeZ`; plus `VIBE.Clip`.
84
+ - **Rule:** any axis peak ≥ 30 m/s² → HIGH (FC max is 105). Clipping:
85
+ `MAX(Clip) > 0` → anomaly (any clip event).
86
+ - **Physically:** excessive vibration corrupts the IMU/accelerometer feed, which then
87
+ poisons the EKF state estimate — a root cause that propagates into altitude and EKF
88
+ findings. Clipping means the accelerometer railed and lost data outright.
89
+ - **How to talk about it:** report peak per-axis vs the 30 m/s² bound (rule-of-thumb)
90
+ and note any clip events against the `max:0` bound. Frame vibration as a likely
91
+ upstream cause when EKF/altitude also FAIL. Tuning (notch filtering) is the usual
92
+ remedy, but that analysis lives in a separate path — surface, don't prescribe here.
93
+
94
+ ## mag — magnetometer (HIGH)
95
+
96
+ - **Tables / fields:** `MAG.Health`; `MSG` for yaw-alignment timing.
97
+ - **Rule:** `MIN(Health) < 1` → HIGH. Late yaw-align: first `MSG` matching
98
+ `/EKF.*yaw align/i` minus arm time > `late_align_s` (default 30 s); no such MSG → SKIP
99
+ that sub-check.
100
+ - **Physically:** an unhealthy magnetometer or a late yaw alignment means the heading
101
+ estimate was unreliable early in the flight — interference, a bad compass calibration,
102
+ or magnetic disturbance near the airframe.
103
+ - **How to talk about it:** state whether health dropped below 1 and/or how long yaw
104
+ alignment took vs the 30 s bound. Connect to any EKF/heading anomalies. If the
105
+ yaw-align MSG is absent, say the sub-check was skipped — never imply a clean pass over
106
+ missing data.
107
+
108
+ ## ekf — EKF innovations (HIGH)
109
+
110
+ - **Tables / fields:** `XKF3` — innovations `IVN`, `IVE`, `IVD`.
111
+ - **Rule:** innovation ratio > `max_ratio` (default 0.5) → HIGH. This is a correlation /
112
+ trend check, not a single scalar — regime-dependent.
113
+ - **Physically:** large EKF innovations mean the filter's prediction and its sensor
114
+ measurements disagree — the state estimate is being pulled around. Common upstream
115
+ causes: vibration corrupting the IMU, mag/GPS disagreement, or sensor dropout.
116
+ - **How to talk about it:** report the peak innovation ratio vs the 0.5 seed bound (from
117
+ ArduPilot health criteria), and explicitly correlate with vibration, mag, and GPS
118
+ findings — EKF FAILs are usually downstream of one of those. This is exactly where a
119
+ model is tempted to hand-wave; resolve it to the explicit criterion and the correlated
120
+ evidence, not a vibe. Surface the chain; let the engineer rule on root cause.
121
+
122
+ ## gps — GPS health (HIGH)
123
+
124
+ - **Tables / fields:** `GPS`/`GPA` — `NSats`, `HDop`.
125
+ - **Rule:** `NSats < 6` or `HDop > 2.0` → HIGH (fix loss / poor geometry).
126
+ - **Physically:** too few satellites or high HDOP means a weak position fix — the EKF
127
+ position/velocity estimate degrades, which can cascade into altitude and EKF findings,
128
+ especially outside GPS-denied regimes.
129
+ - **How to talk about it:** report satellite count and HDOP vs the `min:6` / `hdop_max:2.0`
130
+ bounds, and note when in the flight the fix degraded. Correlate with EKF innovations.
131
+ Surface and rank; the engineer decides whether the regime made it acceptable.
132
+
133
+ ---
134
+
135
+ ## Full catalog (added checks — boundary + consistency tier)
136
+
137
+ Beyond the core checks above, these run when their data is present (else SKIP). Each cites
138
+ its bound's source; bounds live in `uav-thresholds.yaml`.
139
+
140
+ **Power & battery** (`BAT`, `POWR`)
141
+ - `min_cell_voltage` (CRITICAL) — `MIN(BAT.Volt)/cells` (cells≈`ROUND(MAX(Volt)/4.2)`) below per-cell floor. Cite datasheet min; per-cell sag under load is the real health signal.
142
+ - `voltage_sag` (HIGH) — `MAX(BAT.VoltR-Volt)` large → weak pack/connector/high IR under load.
143
+ - `over_current` (HIGH) — `MAX(BAT.Curr)` above ESC/motor rating (airframe-specific; ground from datasheet).
144
+ - `internal_resistance` (HIGH) — `MAX(BAT.Res)` high → ageing/damaged pack (seed-default; pack-specific, override per airframe).
145
+ - `board_brownout` (CRITICAL) — `MIN(POWR.Vcc)` dipped near/below 4.5V → flight-controller power-rail brownout; correlate with any reboots/errors.
146
+
147
+ **Control & propulsion** (`RATE`, `MOTB`, `ESC`)
148
+ - `rate_tracking` (HIGH) — desired-vs-actual body rates diverge (`RDes-R`,`PDes-P`,`YDes-Y`) → tune/saturation; pairs with vibration & oscillation.
149
+ - `thrust_margin` (HIGH) — `MOTB.FailFlags`>0 (thrust loss) or low `ThrOut`/`LiftMax` headroom → undersized/▼T:W (rule-of-thumb ~2:1).
150
+ - `esc_health` (HIGH) — ESC over-temp (`Temp`/`MotTemp`) or `Err`>0 → desync/overheat risk.
151
+
152
+ **Estimation & sensors** (`XKF4`, `GPA`, `MAG`, `IMU`)
153
+ - `ekf_variance` (HIGH) — `XKF4` SV/SP/SH/SM spikes (or `FS` set) → estimator stress/failsafe.
154
+ - `gps_accuracy` (HIGH) — `GPA.HAcc` above bound → poor fix quality even with sats.
155
+ - `mag_offsets` (HIGH) — large `MAG.Ofs*` → bad calibration / interference; correlate with current draw.
156
+ - `imu_health` (HIGH) — `IMU.EG`/`EA` error counts > 0 → IMU dropouts; check across instances.
157
+
158
+ ## Parameter audit (`param.*`)
159
+
160
+ A separate deterministic pass over the PARM set (range/safety/consistency). Findings are
161
+ `tier:system`, `check` ids prefixed `param.`. Bounds in `<project>/.hydron/param-rules.yaml`
162
+ (create/edit this file to add or change a rule; merged over the built-in ruleset).
163
+
164
+ - **`param.safety.*`** — arming checks not disabled (`ARMING_CHECK≠0`, CRITICAL), battery
165
+ failsafe configured (`BATT_LOW/CRT_VOLT`, CRITICAL), radio failsafe (`FS_THR_ENABLE`),
166
+ geofence (`FENCE_ENABLE`). A disabled safety is a config risk, not a flight symptom — say so.
167
+ - **`param.consistency.*`** — cell-count vs failsafe voltages agree; frame class/type set;
168
+ notch filter enabled when expected.
169
+ - **`param.range.*`** — tuning/safety params within sane ranges (`INS_GYRO_FILTER`,
170
+ `ATC_RAT_*_P`, `MOT_SPIN_*`, `RTL_ALT`, …). Out-of-range = likely misconfig; cite the range.
171
+
172
+ When narrating a param finding, frame it as *configuration* (what's set vs recommended),
173
+ distinct from a *telemetry* anomaly (what the vehicle did) — and never invent a range; cite
174
+ `param-rules.yaml` / the datasheet.
@@ -0,0 +1,49 @@
1
+ # Narrating a cross-flight diff
2
+
3
+ Comparison is the tuning-phase path: track how a single airframe's behaviour shifts
4
+ flight-to-flight. The harness produces the diff (param changes + finding changes); your
5
+ job is to narrate what moved, by how much, and in which direction — grounded in the
6
+ guardrails, ranked, and without closing the verdict.
7
+
8
+ ## What to compare
9
+
10
+ - **Params** — what changed in the ~1,117-param set between the two flights. Highlight
11
+ the deltas that plausibly explain a finding shift (e.g. a notch-filter or gain change
12
+ alongside a vibration or attitude change).
13
+ - **Findings** — which checks improved, regressed, appeared, or disappeared between the
14
+ two flights, with each check's measured value on both sides and the bound it relates
15
+ to.
16
+ - **Regime** — note if the two flights were flown differently (aggressive vs hover,
17
+ GPS-denied vs open). A finding shift driven by a regime change is not the same as a
18
+ shift at constant regime. If the regime is ambiguous, ask before attributing the shift.
19
+
20
+ ## Reference: tuning vs production
21
+
22
+ - **Tuning phase** → the reference is the *last* flight. You are tracking pattern shifts
23
+ over the tuning cycle; the question is "did this change move the airframe in the right
24
+ direction?"
25
+ - **Production vehicle** → the reference is the *fixed* guardrail bound, for consistency.
26
+ The question is "is this flight within bounds?"
27
+
28
+ Same machinery, different reference. State which one you are using.
29
+
30
+ ## How to describe a shift
31
+
32
+ For each shifted finding, give:
33
+ - the check and its measured value on flight A vs flight B;
34
+ - the **direction** (improved toward / regressed away from the bound) and the
35
+ **magnitude** of the change;
36
+ - the guardrail bound it relates to and that bound's source (datasheet / rule-of-thumb /
37
+ tuned — see `grounding.md`);
38
+ - the plausible param change that correlates with it, if any.
39
+
40
+ Example shape: "Peak Z vibration fell from 41 to 28 m/s² (now under the 30 m/s²
41
+ customer-stated bound), coinciding with INS_HNTCH params being enabled — the EKF
42
+ innovation peak also dropped from 0.7 to 0.4."
43
+
44
+ ## Rank, don't close the verdict
45
+
46
+ Order the shifts by significance (a CRITICAL battery change or a regression that crosses
47
+ a bound outranks a small in-bounds wobble). Present the ranked narrative and the
48
+ correlated evidence; leave the call — "is this tuning step good enough to keep?" — to the
49
+ engineer. Surface the chain; don't decide it.
@@ -0,0 +1,94 @@
1
+ # Grounding — how thresholds and guardrails are sourced and cited
2
+
3
+ This is the customer's #1 concern: a generic model asked "is this flight OK?" guesses,
4
+ because it does not know *this* airframe's actual limits. UAV anomaly judgments must be
5
+ grounded in the vehicle's real constraints, not the model's priors.
6
+
7
+ ## The principle: the LLM narrates, the guardrails judge
8
+
9
+ The verdict is kept out of your hands. The deterministic harness compares the measured
10
+ value against an **explicit guardrail** and produces the `Finding` (its `severity` and
11
+ its `verdict`). You only **explain** confirmed findings and answer questions — you never
12
+ decide "anomalous vs fine" from raw numbers. A hallucinated threshold therefore cannot
13
+ become a verdict: the threshold is data, not a guess.
14
+
15
+ ```mermaid
16
+ flowchart LR
17
+ M["measured value (decoded)"] --> G{"guardrail check (deterministic)"}
18
+ GR["guardrails: OEM datasheet + rules-of-thumb + per-airframe bounds"] --> G
19
+ G -->|in bounds| OK["verdict: PASS"]
20
+ G -->|out of bounds| FAIL["verdict: FAIL (severity)"]
21
+ OK --> N["LLM narrates / answers (grounded in the finding, not the raw data)"]
22
+ FAIL --> N
23
+ ```
24
+
25
+ ## Where guardrails come from (the three sources)
26
+
27
+ Every bound lives in `uav-thresholds.yaml` and carries its source, so a finding can cite
28
+ *why* the bound is what it is — grounding the explanation, not just the verdict.
29
+
30
+ 1. **OEM datasheet** — component-specific limits (battery min voltage, max current,
31
+ internal-resistance ceiling, ESC/motor ratings). The authoritative bounds for a given
32
+ airframe's parts. Hydron already RAG-indexes datasheets, so these can be retrieved per
33
+ component rather than hand-typed: **call `query_hydron_project`** with the component +
34
+ the limit you need (e.g. *"min cell voltage and max continuous current for the Tattu
35
+ 6S 16000mAh on airframe X"*) and cite the returned value + its citation as the finding's
36
+ source. Only do this when a project with the datasheet ingested is selected; otherwise
37
+ fall back to `uav-thresholds.yaml`.
38
+ 2. **Rule-of-thumb** — industry constants that hold across vehicles (e.g. ≥ 2:1
39
+ thrust-to-weight for a multicopter, a healthy hover-throttle band, vibration < 30 m/s²).
40
+ A small curated rules-of-thumb library in the pack.
41
+ 3. **Per-airframe tuned** — values the lab sets for a specific vehicle in
42
+ `uav-thresholds.yaml`, refined over its tuning cycle.
43
+
44
+ **Precedence:** per-airframe tuned > OEM datasheet > rule-of-thumb default.
45
+
46
+ ## Retrieval ladder — how to source a bound that isn't already in `uav-thresholds.yaml`
47
+
48
+ The verdict is ALWAYS deterministic (the harness compares the measured value to a bound).
49
+ These steps only supply *which bound to use and where it came from* — its citation. Walk
50
+ the ladder in order and stop at the first that yields a sourced bound; record which tier +
51
+ the citation on the finding:
52
+
53
+ 1. **Local config** — `<project>/.hydron/uav-thresholds.yaml` (tuned / customer-stated).
54
+ Authoritative; no lookup needed. Create/edit this file to set or change a bound.
55
+ 2. **KG / RAG** — `query_hydron_project` for an **ingested** OEM datasheet limit (cite the
56
+ returned citation). Only when a project with that datasheet is selected.
57
+ 3. **Web** — a **web-search MCP (Exa by default)** when the bound is **not in the KG**:
58
+ `exa_web_search_exa` / `exa_web_fetch_exa` to find the OEM datasheet spec or the
59
+ published rule-of-thumb, then **cite the URL**. This is the "knowledge grounding outside
60
+ the KG" path — datasheets the lab never ingested, community tuning guides, manufacturer
61
+ PDFs.
62
+
63
+ If no tier yields a defensible bound, **say so and surface the raw value for the engineer**
64
+ — never invent a threshold. A web/KG result is a *source for a bound*, not a verdict; the
65
+ harness still decides PASS/FAIL. Treat web results with appropriate skepticism (prefer the
66
+ manufacturer's own datasheet over a forum post) and always attach the link so the engineer
67
+ can verify.
68
+
69
+ ## Grounding applies to the system tier only
70
+
71
+ Only **system** findings (the built-in inspection-order checks) are guardrail-grounded
72
+ and enter the regression record / comparison. **User** findings (ad-hoc `uav_query`) are
73
+ exploratory, tagged `tier:'user'` with their query provenance, and are **not** part of
74
+ the canonical grounded comparison. They answer a question; they do not assert a grounded
75
+ verdict. Never fold a user query's result into the regression record or present it as a
76
+ grounded verdict.
77
+
78
+ ## The hard tier — correlation checks
79
+
80
+ Simple bound checks (battery V/I/resistance) are deterministic and easy. IMU/EKF drift
81
+ needs multi-step analysis: trends, cross-signal correlation, regime awareness — not a
82
+ single scalar threshold. Grounding matters most here, precisely because it is where a
83
+ model is tempted to hand-wave. Even these resolve to explicit, explainable criteria, not
84
+ vibes.
85
+
86
+ ## Work-style rules (cite this when narrating)
87
+
88
+ - **Chain-of-thought** — reason step-by-step over the findings before concluding.
89
+ - **Ask clarifying questions over assuming** — when vehicle type, configuration, regime,
90
+ or intent is ambiguous, ask; don't guess.
91
+ - **Cite the guardrail** — every anomaly states the bound it breached and that bound's
92
+ source (datasheet / rule-of-thumb / tuned), never an unsourced number.
93
+ - **Surface, don't over-verdict** — rank anomalies; leave the final call to the engineer.
94
+ Investigation is case-by-case.
@@ -0,0 +1,27 @@
1
+ # Project config files (optional per-project overrides)
2
+
3
+ These tune the pack per project/lab. They live under `.hydron/` at the project root. All are
4
+ OPTIONAL: the tools ship built-in defaults and work without them. To customise, **WRITE the
5
+ file directly at the exact path below** — the tools read only these paths.
6
+
7
+ Merge semantics differ:
8
+ - The two YAML files are **MERGED** (deep-merged) over the built-in defaults (which are baked
9
+ into the pack, not shipped as files). Author them from scratch listing only the keys you are
10
+ overriding — everything else keeps its default.
11
+ - `apm-params.json` is different: it **REPLACES** the bundled snapshot wholesale. A partial
12
+ file drops every param you omit, so include the full set you need. The baseline snapshot
13
+ ships at the plugin's own `data/apm-params.json`.
14
+
15
+ Files:
16
+
17
+ - `<project>/.hydron/uav-thresholds.yaml` — per-airframe anomaly guardrail bounds. Deep-merged
18
+ over the built-in defaults; this is the authoritative first rung when sourcing a bound.
19
+ - `<project>/.hydron/param-rules.yaml` — curated `{param: {min, max, severity}}` range/safety
20
+ rules for `uav_param_audit`. Merged over the built-in ruleset.
21
+ - `<project>/.hydron/apm-params.json` — ArduPilot param metadata (name → `{min, max, units}`).
22
+ REPLACES the snapshot bundled with the plugin wholesale (not merged) when present.
23
+ - `<project>/.hydron/uav-format-converters.json` — registry of custom log-format converters
24
+ (proprietary formats → a known format, applied before decode). No default; add only if needed.
25
+
26
+ When a user asks to change a bound, a param range, or add a custom log format, **write to the
27
+ matching file above — do not hard-code the value** anywhere else.
@@ -0,0 +1,60 @@
1
+ # Signal dictionary + analysis method (pick the RIGHT column before you plot)
2
+
3
+ Choosing the wrong column silently produces wrong conclusions (e.g. correlating vibration
4
+ against `CTUN.ThH` — a near-constant hover estimate — falsely shows "no relationship").
5
+ Look the signal up here first. Columns follow the sample log; the decoder is FMT-driven, so
6
+ verify per-firmware.
7
+
8
+ ## Throttle (the classic trap)
9
+ - **`RCOU.C1…C8`** — actual **motor PWM output** (~1000–2000 µs). Use the **mean of the active
10
+ motors** as the true instantaneous throttle/effort. ← use this for "throttle".
11
+ - **`CTUN.ThI`** — throttle **input** (pilot demand, 0–1). **`CTUN.ThO`** — throttle **output**
12
+ (0–1, post-mix). Either is a valid throttle proxy.
13
+ - **`CTUN.ThH`** — throttle **hover**: the *slowly-learned* hover-throttle estimate
14
+ (`MOT_THST_HOVER`), **≈ constant over a flight**. **NOT instantaneous throttle** — never use
15
+ it as the throttle axis.
16
+
17
+ ## Vibration / IMU
18
+ - `VIBE.VibeX/Y/Z` (m/s²) is **whole-airframe vibration sensed at the IMU**, reported **per IMU
19
+ instance** (`VIBE.IMU` = which IMU). It is **NOT per-motor** vibration — the log does not
20
+ attribute vibration to a specific motor. Label it "airframe vibration (IMU N)", never
21
+ "motor N vibration". Per-**motor** signals are `RCOU.Cn` (output PWM) and `ESC` (per-ESC
22
+ RPM/temp); to *attribute* a vibration peak to a motor you must correlate the FFT peak Hz with a
23
+ motor's RPM (`ESC.RPM`/`RPM` × poles), not read it off VIBE.
24
+ - `VIBE.Clip0/1/2` = cumulative clip counts (per IMU).
25
+ - `IMU.GyrX/Y/Z`, `AccX/Y/Z`; `IMU.I` = instance (interleaved — de-interleave for FFT);
26
+ `EG/EA` = gyro/accel error counts; `T` = temp; `GHz/AHz` = sample rates.
27
+
28
+ ## Attitude / rates (tracking error = desired − actual)
29
+ - `ATT.DesRoll/Roll`, `DesPitch/Pitch`, `DesYaw/Yaw` (deg).
30
+ - `RATE.RDes/R`, `PDes/P`, `YDes/Y` (deg/s) + `*Out` (controller output).
31
+
32
+ ## Altitude / position
33
+ - `CTUN.DAlt/Alt` (desired/achieved), `BAlt` (baro), `DCRt/CRt` (climb rate).
34
+
35
+ ## Battery / power / ESC
36
+ - `BAT.Volt`, `VoltR` (resting), `Curr`, `CurrTot`, `Res` (internal resistance), `Temp`, `RemPct`.
37
+ - `POWR.Vcc` (board 5V rail). `ESC.RPM/Volt/Curr/Temp/MotTemp/Err` (per-ESC). `MOTB.LiftMax/ThrOut/ThLimit/FailFlags`.
38
+
39
+ ## EKF (XKF* = EKF3, NKF* = EKF2)
40
+ - Innovations `XKF3`/`NKF3`: **velocity** `IVN/IVE/IVD` (+`IVT`), **position** `IPN/IPE/IPD`.
41
+ Vibration-driven stress shows in velocity innov; GPS issues in position innov.
42
+ - Variances `XKF4`/`NKF4`: `SV/SP/SH/SM`; `FS` = failsafe flags.
43
+
44
+ ## Mag / GPS
45
+ - `MAG.MagX/Y/Z` (field), `OfsX/Y/Z` (offsets), `MOX/Y/Z` (motor compensation), `Health`.
46
+ - `GPS.NSats/HDop/Status`; `GPA.HAcc/VAcc/SAcc` (accuracy estimates).
47
+
48
+ ## Method (general — fit the analysis to the data)
49
+ - **Explore first** with `uav_query` (SQL) and/or **Python** (`bash`: `python3` + pandas/numpy
50
+ over `uav_export` CSVs, or pymavlink on the log) — check columns, units, ranges, sample
51
+ counts, distribution, outliers. Reason before you plot.
52
+ - **Don't force one statistic.** Inspect the *shape* of a relationship (linear / threshold /
53
+ none / time-varying) and pick a summary that fits it. A single correlation can mislead —
54
+ clustered/narrow ranges, nonlinearity, and outliers all distort it — so per-bucket trends,
55
+ medians, and robust/aggregate measures often tell the real story. Report the evidence and
56
+ the caveats, not just a coefficient.
57
+ - Use a **representative** signal (aggregate noisy or biased channels rather than one arbitrary
58
+ sample). Confirm the column means what you think (above) before trusting the result.
59
+ - `uav_plot`: derived exprs for error signals (`ATT.DesRoll - ATT.Roll`), `xy` for correlations,
60
+ `fft` for spectra — the final picture after the reasoning, not a substitute for it.
@@ -0,0 +1,79 @@
1
+ # Methodical tuning — the ordered playbook (generic, airframe-agnostic)
2
+
3
+ How to turn a log into **reasoned, ordered parameter suggestions** — the loop a good tuner
4
+ runs by hand, and what the Autopilot Methodical Configurator (AMC) automates. This is
5
+ METHODOLOGY, not a value table: you read the evidence from the log (`uav_query`/`uav_python`),
6
+ you take every parameter's valid **range/default from `apm.pdef.xml`** (via `uav_param_audit` —
7
+ never invent a range), and you suggest a *direction and magnitude* with justification. Actual
8
+ per-airframe target values are the lab's tuned profile, not baked in here.
9
+
10
+ Two hard rules, always:
11
+ - **Order matters — do not skip ahead.** Each stage assumes the ones above it are clean. Tuning
12
+ a PID loop on a vibrating airframe, or on top of a bad sensor source, chases ghosts.
13
+ - **Recommend only; change ONE thing; then re-fly and re-check.** You never write params (that's
14
+ the gated MAVLink link). Every suggestion cites the signal it's based on, the param + its
15
+ `apm.pdef.xml` range, the direction, and how to confirm it on the next log.
16
+
17
+ ## Stage 0 — Pre-tune gates (must pass before ANY gain tuning)
18
+ If these fail, fix them first; do not touch PIDs.
19
+ - **Vibration / clipping**: `VIBE.VibeX/Y/Z` within bound, `VIBE.Clip*` not accumulating.
20
+ Clipping or high vibe corrupts the gyro the PIDs act on. → go to Stage 1.
21
+ - **Mechanical**: props balanced, frame rigid, IMU isolation intact (shows as broadband vibe).
22
+ - **Sensors healthy**: `MAG.Health`, GPS `NSats`/`HAcc`, `POWR.Vcc` rail, EKF not failsafing
23
+ (`XKF4.FS`). A bad sensor source is Stage 5 — but a *failing* one blocks everything.
24
+
25
+ ## Stage 1 — Vibration & harmonic notch (FIRST)
26
+ Clean the gyro before touching gains. Use `uav_tune` (FFT → `INS_HNTCH_*`; see
27
+ `references/tuning.md`). Confirm the motor/prop peak, set the notch, re-fly, confirm the peak
28
+ dropped. Only then proceed.
29
+
30
+ ## Stage 2 — Rate loop (inner PID: the core of the tune)
31
+ Signals: `RATE.RDes/R`, `PDes/P`, `YDes/Y` (deg/s) and `*Out` (controller output).
32
+ Read from the log with `uav_python`:
33
+ - **Tracking error** = desired − actual per axis. Large steady error → P too low.
34
+ - **Oscillation** on the *actual* trace (and on `*Out`) → gain too high (usually D, then P).
35
+ - **Lag / mushy response** → P/D too low, or `FLTD`/`FLTT` filters too aggressive.
36
+
37
+ Params (ranges from `apm.pdef.xml`): `ATC_RAT_RLL_P/I/D`, `ATC_RAT_PIT_P/I/D`,
38
+ `ATC_RAT_YAW_P/I/D`, plus `_FLTD` (D-term filter), `_FLTT` (target filter), `_FLTE`. Standard
39
+ heuristics: raise D until high-freq oscillation appears then back off ~25%; set P near D×10 as a
40
+ start; keep I so that I/P stays in the usual band. **One axis, one gain, at a time.** Justify each
41
+ from the tracking-error/oscillation evidence you measured.
42
+
43
+ ## Stage 3 — Attitude (angle) loop (outer)
44
+ Only after the rate loop is crisp. Signals: `ATT.DesRoll/Roll`, `DesPitch/Pitch`, `DesYaw/Yaw`.
45
+ Sluggish angle response with a clean rate loop → raise `ATC_ANG_RLL_P` / `ATC_ANG_PIT_P` /
46
+ `ATC_ANG_YAW_P`. Overshoot → lower. These are usually a light touch.
47
+
48
+ ## Stage 4 — Position / altitude / velocity loops
49
+ Signals: `CTUN.DAlt/Alt`, `DCRt/CRt` (climb-rate track), horizontal pos/vel from `XKF1`/`POS`.
50
+ Params: `PSC_POSZ_P`, `PSC_VELZ_P`, `PSC_ACCZ_P/I/D` (vertical), `PSC_POSXY_P`, `PSC_VELXY_*`
51
+ (horizontal). Poor altitude hold that is NOT a sensor problem → adjust the vertical PSC chain.
52
+ But first rule out the sensor source (Stage 5) — noisy altitude is usually source, not gain.
53
+
54
+ ## Stage 5 — Sensor source & fusion (EK3_SRC) — esp. altitude
55
+ The EKF blends sources; picking the wrong primary shows as noise the PIDs can't fix.
56
+ - **Barometer is noisy near the ground** (prop-wash, ground effect). For low-altitude precision,
57
+ prefer **GPS** or a **rangefinder / 1-D lidar** for height. Params: `EK3_SRC1_POSZ`
58
+ (1=baro, 2=GPS, 3=rangefinder, …), `RNGFND*`, `EK3_RNG_USE_HGT`.
59
+ - Horizontal source: `EK3_SRC1_POSXY` / `_VELXY` (GPS vs beacon vs optical flow); yaw:
60
+ `_YAW` (compass vs GPS-yaw vs external).
61
+ - Evidence: compare `CTUN.BAlt` (baro) noise vs GPS/rangefinder height over the same window with
62
+ `uav_python`; if baro is the noisy one at low altitude, recommend switching `POSZ` source (or
63
+ blending) — with the range/enum from `apm.pdef.xml`.
64
+
65
+ ## Stage 6 — Drift over the sortie (endurance / large airframes)
66
+ Gyro/estimator drift matters more on **long sorties** and **large airframes** (heavy airframes
67
+ lean on the estimator; small ones are GPS-compensated at ~5 Hz). Method:
68
+ - Trend the EKF innovation/variance over time, not just the peak: `XKF4.SV/SP/SH/SM` and
69
+ `XKF3` innovations (`uav_python`, binned over the flight). A steady upward drift in a variance
70
+ or a growing bias signals estimator/gyro drift.
71
+ - The **acceptable drift bound scales with the airframe** — so it is a **tunable per-airframe
72
+ threshold** (the lab's profile), not a fixed number here. Report the observed drift rate and
73
+ compare it against the airframe's bound if one is configured; otherwise surface the trend and
74
+ say no absolute bound is set.
75
+
76
+ ## Closing the loop (every stage)
77
+ Suggest → (human) gate-write over the MAVLink param link → re-fly → re-run the relevant
78
+ analysis on the new log → confirm the metric moved the right way. Cite the before/after. Tag the
79
+ retest (`uav_tag`) so `uav-compare` can track the tuning trajectory across flights.