From 61c86aa8bcda7755a4592a52b94ec2f57690527f Mon Sep 17 00:00:00 2001 From: Aaron George Date: Fri, 2 Oct 2026 16:51:44 -0700 Subject: [PATCH 01/22] Add Flameout Alarm draft spec input Pre-spec draft for a new in-flight app (AG-FlmOt.lua) that alarms when turbine RPM telemetry shows a flameout. Covers per-model enable and RPM sensor assignment, arming only at idle RPM or above (so start-up and ECU auto-restart never arm or clear the alarm), and the approved alarm sound: "Flameout!" x3 in the Speed Gauge Piper Amy voice, urgent delivery, every 5 s with a missile-lock tone in between. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UXZ3gE435jMKRzKW9FRfiy --- docs/drafts/flameout-alarm.md | 315 ++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 docs/drafts/flameout-alarm.md diff --git a/docs/drafts/flameout-alarm.md b/docs/drafts/flameout-alarm.md new file mode 100644 index 0000000..09971c6 --- /dev/null +++ b/docs/drafts/flameout-alarm.md @@ -0,0 +1,315 @@ +# Draft spec input: Flameout Alarm + +**Status:** Pre-spec draft (2026-10-02). Not yet run through Spec Kit. +Alarm sound approved by Aaron (2026-10-02). +**Next step:** run `/speckit-specify` with the *Feature description* below, +then bring in the *Success criteria* and *Edge cases* sections, and feed the +*Open questions / research* items to `/speckit-clarify`. + +Proposed script name: `AG-FlmOt.lua` (menu name "Flameout Alarm"). +Confirm the name before release; constitution V makes filenames permanent. + +--- + +## Feature description (paste into `/speckit-specify`) + +Flameout Alarm is an in-flight telemetry app for the Jeti DS-24 II that warns +the pilot, by sound and on screen, when the turbine appears to have flamed out. + +**Problem.** A turbine flameout in flight is easy to miss. The model keeps +flying, the engine noise is already distant, and the pilot may not notice the +loss of thrust until the model is slow, low or out of glide range. Many ECUs +send RPM over telemetry, so the transmitter can tell the moment the engine +spools down while the pilot still expects it to be running. + +**Goal.** Give an immediate, unmistakable alarm when the RPM falls below a +critical threshold while the engine is supposed to be running, and stay +silent during normal start-up, idle, throttle chops and intentional shutdown. + +**Why an app and not a native alarm.** Jeti's native telemetry alarms can fire +on "RPM below X", optionally gated by a switch. But they can't tell "the +engine has not started yet" from "the engine was running and stopped", so a +low-RPM alarm also sounds during the whole start sequence and after every +shutdown. The app adds arming: it only alarms after the engine has +demonstrably been running at idle or above. + +**Per-model enable and RPM sensor assignment.** Not every turbine sends an +RPM telemetry sensor. The app is therefore configured per model: + +- Monitoring is **off by default** on every model. +- The user turns it on for a model only if that model's turbine provides an + RPM sensor, and assigns that sensor from the model's telemetry sensors + (`system.getSensors()`). +- Monitoring cannot be turned on until an RPM sensor is assigned. If the list + has no sensors, the app explains that the turbine/ECU in this model must + send RPM telemetry and that telemetry must have been received at least once + (transmitter and receiver powered on). +- When monitoring is off or no sensor is assigned, the app is fully inactive: + no sounds, no warnings, and its telemetry window shows "OFF". +- The enable flag and sensor assignment are saved per model + (`system.pSave` / `system.pLoad`), so each model keeps its own setting. + +**Startup and auto-restart RPM.** During a start, and during an ECU +auto-restart after a flameout, the starter and ignition spin the engine +through a range of RPM below idle. That startup range must never arm the +app and must never clear an active flameout alarm. Only reaching idle RPM +(or above) counts as "engine running". + +**Alarm sound (approved).** The alarm repeats a 5-second cycle until it is +acknowledged, cleared by a relight, or stopped by the Cut switch: + +1. **Voice callout:** "Flameout! Flameout! Flameout!" spoken quickly and + imperatively (about 2 s), in the same Piper "Amy" voice that Speed Gauge + uses for all its speech (Speed Gauge spec, User Story 6 and + FR-030–FR-037), so all AG- apps sound consistent. +2. **Lock tone:** a rapid, high-pitched beeping like a missile-lock warning + fills the rest of the cycle. + +A new callout starts every 5 seconds (callout start to callout start), so +the pilot hears the voice again, then the beeping resumes. + +Both sounds are audio files in the app's own `AG-FlmOt/` folder, not +references to Speed Gauge's files, so the install stays standalone. The +voice file follows Speed Gauge's voice rules (FR-034–FR-036): + +- Generated by the repo's voice script (`tools/voice/`) from the same Piper + Amy model. The script's phrase list gains "Flameout!" with output to + `AG-FlmOt/`. +- Delivered urgently, unlike Speed Gauge's normal-paced callouts: Piper + length scale 0.70 (about 30% faster), noise scale 0.5, pitch raised about + 1 semitone without changing speed (ffmpeg `asetrate` ×1.07 + + `atempo` 0.9346), high-pass at 150 Hz, +4 dB presence boost at 3 kHz and + light compression (threshold −18 dB, ratio 4, attack 3 ms, release + 60 ms, makeup 4 dB) so it cuts through wind and engine noise. Repeats are + separated by 0.12 s. +- Mono, 16-bit, 16 or 22.05 kHz; leading/trailing silence trimmed; + normalized to the same peak loudness as Speed Gauge's files. +- Not committed to git (`.gitignore`); each developer generates it locally. + A credits note for Piper (MIT) and the Amy model (CC BY-SA 4.0) goes in + the folder and in `CREDITS.md`. + +The lock tone is a plain synthesized tone (no voice licensing), so it can be +committed or generated by the same script. + +Approved samples (2026-10-02, kept in the claude.ai "LUA Apps" project chat, +not committed): lock tone `AG-FlmOt_lock_loop.wav` (seamless 2 s loop, +1800 Hz with a 3600 Hz overtone, 45 ms on / 35 ms off with 3 ms fades, +12.5 beeps/s, 22.05 kHz mono 16-bit); urgent voice +`AG-FlmOt_flameout_x3.wav`; and the full 20 s demo +`AG-FlmOt_alarm_demo.wav` (4 cycles). The voice samples were made with +Piper Amy **low** (16 kHz); the release file should use the same Amy model +quality as Speed Gauge, with the settings above. + +**Setup inputs.** + +- Enable monitoring for this model (off / on). On only if an RPM sensor is + assigned. +- RPM telemetry sensor for this model. +- Throttle safety switch and its Cut (OFF) position. Same 2-position switch + used by the Turbine Throttle Setup app, but selected independently here. +- Idle RPM (the arming threshold). Entered by hand or captured with a + "learn idle" helper (record the steady idle RPM with the engine running). + It must sit at or above idle and above any RPM the engine reaches during + start-up or auto-restart. +- Flameout threshold in RPM, below idle. Suggested as a set percentage of the + idle RPM (editable), low enough that a throttle chop to idle doesn't cross + it. +- Arming time: how long RPM must stay at or above idle before the app arms, + or before an alarm clears after a relight. +- Detection delay: how long RPM must stay below the flameout threshold before + alarming, to ride out telemetry glitches. +- Optional: ECU status sensor, if the ECU reports one. + +**Behavior.** + +0. **Off.** When monitoring is off for the model or no RPM sensor is + assigned, the app does nothing at flight time except show "OFF" in its + telemetry window. +1. **Disarmed** at model load when monitoring is on. No alarm in this state. + Startup-range RPM (below idle) keeps it Disarmed. +2. **Arming.** When the switch is not in Cut and RPM stays at or above the + idle RPM for the arming time, the app moves to **Armed** and plays a short + "armed" confirmation (optional). +3. **Flameout.** While Armed, if RPM stays below the flameout threshold for + the detection delay and the switch is not in Cut, the app raises the + alarm: the 5-second cycle of "Flameout!" ×3 then lock tone, repeating, + plus stick vibration if available and a large on-screen "FLAMEOUT" in its + telemetry window. +4. **Auto-restart.** While in Flameout, RPM rising through the startup range + (starter cranking, ignition, fuel ramp) does not clear the alarm, and + wobbles in that range never re-trigger it as a new event. The alarm + clears only when RPM is back at or above idle for the arming time; the + app then returns to Armed and can announce "relit" (optional). If the + restart fails, the flameout state persists. +5. **Intentional shutdown.** Moving the switch to Cut disarms immediately and + silently, so a normal shutdown never alarms. +6. **Acknowledge.** The pilot can silence the alarm with a key press. The + app stays in an acknowledged Flameout state (still shown on screen) until + RPM is at or above idle for the arming time, or the switch goes to Cut. + It does not re-alarm for the same event. +7. **Telemetry lost.** If the RPM sensor stops reporting valid data while + Armed, the app gives a distinct "telemetry lost" warning, not the flameout + alarm, so the pilot can tell the two apart. +8. **Telemetry window.** Shows current RPM, the state (OFF / DISARMED / + ARMED / FLAMEOUT / NO TELEMETRY) and the active thresholds. + +**Constraints.** + +- Advisory only. The app never drives any output: it never calls + `system.registerControl`, `system.setControl` or `system.setProperty` + (constitution I). It only reads telemetry and switches and plays sound. +- It does not replace the ECU's own failsafe, shutdown or auto-restart logic, + nor the pilot's own monitoring. The README must say so, and must say the + app only works on turbines that send RPM telemetry. +- Low CPU: the loop does minimal work per call and never blocks; when Off it + returns immediately. +- Standalone install: only `AG-FlmOt.lua` and its own `AG-FlmOt/` folder + (alarm audio). No dependency on other apps in this repo. Any shared module + from `src/Apps/lib/` must be listed in the plan, the install instructions + and any release package. +- All settings persist per model with `system.pSave` / `system.pLoad`. + +--- + +## Success criteria + +- **SC-001.** In replayed or emulated flameout profiles, the alarm starts + within (detection delay + 0.5 s) of RPM crossing below the threshold in + 100% of runs. +- **SC-002.** Zero alarms across a full normal flight profile: power-on, + start sequence, idle, run-ups, throttle chops to idle, landing, and + shutdown via the Cut switch. +- **SC-003.** Zero flameout alarms before the engine first reaches idle + (start sequence, failed starts, cool-down). +- **SC-004.** Telemetry loss produces the "telemetry lost" warning, never the + flameout alarm, in 100% of tests. +- **SC-005.** A pilot can enable the app for a model and configure sensor, + switch and thresholds in under 3 minutes without any other manual. +- **SC-006.** The app never changes any transmitter output or setting + (servo outputs and model file unchanged after a session with no user edits). +- **SC-007.** Copying only the app's files (plus any listed lib modules) to + `/Apps` on a transmitter with no other AG- apps is enough to load and run. +- **SC-008.** Loop execution stays within Jeti's CPU budget with no "CPU + limit" errors over a 30-minute emulator session. +- **SC-009.** On a model where monitoring is off (the default) or no RPM + sensor is assigned, the app produces zero sounds or warnings in any + scenario, including telemetry loss. +- **SC-010.** Enable state and sensor assignment are kept per model: enabling + it on model A does not enable it on model B, and both settings survive a + transmitter restart. +- **SC-011.** In replayed auto-restart profiles, the flameout alarm never + clears while RPM is in the startup range, clears within (arming time + + 0.5 s) of RPM holding at or above idle, and fires no second alarm for the + same flameout, in 100% of runs. A failed restart leaves the alarm active. +- **SC-012.** While the alarm is active, a "Flameout!" ×3 callout starts + every 5 s (±0.3 s), the lock tone plays between callouts with no audible + gap or overlap, and the cycle continues until acknowledge, relight or Cut, + in 100% of tests. + +## Edge cases + +- **Turbine has no RPM sensor.** User leaves monitoring off; the app stays + silent and shows OFF. Adding the app to such a model must never cause + alarms. +- **User tries to enable with no sensor assigned.** Blocked, with an + explanation. +- **No telemetry sensors listed at setup** (receiver off, or ECU not sending). + Setup explains that telemetry must be received once before a sensor can be + assigned. +- **Throttle chop from full to idle.** RPM falls fast but stays above the + flameout threshold. No alarm. (Drives how low the threshold sits relative + to idle.) +- **Brief telemetry dropout or a single bad sample.** Detection delay rides it + out; no alarm. +- **Sensor reports 0 or invalid at power-on.** Stays Disarmed. +- **Failed start or hot start abort.** RPM never holds at idle for the arming + time, so no alarm. +- **ECU auto-restart after flameout.** Starter cranking and ignition push RPM + up through the startup range. The alarm keeps going (or stays + acknowledged) until RPM holds at or above idle for the arming time, then + clears and the app re-arms. +- **Auto-restart fails partway.** RPM rises into the startup range and falls + back. No clear, no second alarm; the original flameout stays active. +- **Startup RPM peaks close to idle.** If the arming threshold is set below + the highest startup/restart RPM, a restart could clear the alarm early. + Setup warns when the idle value looks too low (e.g. below a learned idle) + and the README explains the rule. +- **Idle RPM drifts slightly below the learned value** (temperature, + altitude, fuel). The engine may not arm at idle and only arms on the first + run-up. Acceptable for v1; a small tolerance setting is an open question. +- **Relight, Cut or acknowledge mid-callout or mid-beep.** All alarm sound + stops at once; no further callouts. +- **Another app (e.g. Speed Gauge) is mid-announcement when the alarm + fires, or when a callout is due.** The alarm must interrupt or pre-empt it, + not queue behind it. +- **Audio file missing from `AG-FlmOt/`** (e.g. voice file not generated). + The app falls back to built-in beeps (`system.playBeep`) plus the on-screen + FLAMEOUT, and settings shows "voice files missing". A missing file must + never silence the alarm. +- **Pilot flips to Cut during the alarm.** Alarm stops; state Disarmed. +- **Wrong sensor selected** (e.g. EGT instead of RPM). Setup warns if the + selected sensor's unit isn't RPM-like, or if the value never changes. +- **Assigned sensor disappears** after an ECU swap, receiver rebind or model + copy. App shows "sensor not found" and stays Disarmed instead of erroring; + the user can re-assign or turn monitoring off. +- **Model copied from another model** (settings carried over). The copied + sensor assignment may not exist on the new model's receiver; same handling + as above. +- **Model loaded with no telemetry ever received.** App shows NO TELEMETRY, + never alarms. +- **Thresholds set nonsensically** (flameout ≥ idle). Setup blocks it and + explains. +- **Two engines.** Out of scope for v1; note as a possible extension (one + instance per RPM sensor). + +--- + +## Open questions / research (feed to `/speckit-clarify` and the plan) + +1. **ECU RPM telemetry.** Which of Aaron's turbines/ECUs send RPM over Jeti EX + (via Jeti Expander / ECU converter), and for those: units (rpm vs. x1000), + decimals, update rate, and what the value does on flameout (falls smoothly, + drops to 0, or goes invalid). +2. **Auto-restart profile.** Which of Aaron's ECUs auto-restart after a + flameout, and what RPM they reach during cranking, ignition and fuel ramp + before settling at idle. Use logs to confirm the startup range always + stays clearly below idle, and to build the SC-011 replay tests. +3. **ECU status sensor.** Do Aaron's ECUs send a status / state sensor (e.g. + "Running", "Flameout", "Restart", "Failsafe")? If so, could a state change + confirm or trigger the alarm faster than RPM, or confirm a relight? Status + codes differ per brand. +4. **Default thresholds.** Typical idle and max RPM for Aaron's engines, and + what percentage of idle is safely below a throttle-chop dip. Gather RPM + logs from real flights (Jeti log files) to set defaults and to build the + replay tests for SC-001/SC-002. Decide whether the idle/arming threshold + needs a small tolerance below the learned idle. +5. **Rate-of-drop detection.** Would RPM decay rate catch a flameout earlier + than a fixed threshold without false alarms on chops? Evaluate on logs; + keep threshold-only for v1 unless clearly better. +6. **Speed Gauge voice (resolved: Piper Amy).** Confirm which Amy model + quality (low or medium) `tools/voice/` uses, and add per-phrase overrides + (speed, pitch, EQ, compression) to the script so the urgent Flameout + delivery doesn't change Speed Gauge's normal callouts. +7. **Audio playback API.** Confirm on firmware 6.x: `system.playFile` + (priority / immediate mode so the alarm interrupts other announcements, + including Speed Gauge), how to time the 5 s callout cycle from `loop()` + with the lock tone in between (WAV loop stopped at callout time vs. + `system.playBeep` bursts), how to stop playback instantly, and the + supported WAV format. Also confirm `system.vibration` on the DS-24 II. +8. **Sensor validity.** Confirm the `valid` field returned by + `system.getSensorByID` and how quickly it goes false after telemetry loss. +9. **Sensor identity across sessions.** Confirm that the sensor `id` + `param` + pair from `system.getSensors()` is stable across power cycles so a saved + assignment keeps working, and how it behaves after a receiver rebind or + ECU/converter swap. +10. **Interaction with native alarms and voice output.** Avoid clashing with + any existing native RPM alarm or the transmitter's own telemetry-loss + announcement. +11. **Emulator testing.** Can JETI Studio replay a log or let us drive a fake + RPM sensor to script the flameout, chop and auto-restart profiles? If + not, plan a small test harness in `types/` or a debug mode that feeds + synthetic RPM. +12. **Stub check.** Confirm `types/jeti.lua` declares `getSensors`, + `getSensorByID`, `playBeep`, `playFile`, `vibration` and + `registerTelemetry`; add any missing ones and note them in + `docs/jeti-api-notes.md`. From 71576a54c7e0ec49d8c448e808a7c933221dc256 Mon Sep 17 00:00:00 2001 From: Aaron George Date: Sun, 4 Oct 2026 20:55:58 -0600 Subject: [PATCH 02/22] Spec 003: Flameout Alarm specification Spec and quality checklist for AG-FlmOt.lua, built from the draft in docs/drafts/flameout-alarm.md. Decisions made while specifying: - A Cut switch is required to enable monitoring, and Cut is the only way to silence the alarm (no separate acknowledge). - Idle RPM is typed in from the ECU setup; "learn idle" is dropped. A separate arming threshold (90% of idle) handles the start overshoot and its slow decay back to idle. - Double-size telemetry window with a segmented sweep RPM bar, idle marker and large RPM number in Speed Gauge's colors; straight bar at single size and as the fallback. Co-Authored-By: Claude Opus 5.5 --- .../checklists/requirements.md | 52 ++ specs/003-flameout-alarm/spec.md | 712 ++++++++++++++++++ 2 files changed, 764 insertions(+) create mode 100644 specs/003-flameout-alarm/checklists/requirements.md create mode 100644 specs/003-flameout-alarm/spec.md diff --git a/specs/003-flameout-alarm/checklists/requirements.md b/specs/003-flameout-alarm/checklists/requirements.md new file mode 100644 index 0000000..59b7585 --- /dev/null +++ b/specs/003-flameout-alarm/checklists/requirements.md @@ -0,0 +1,52 @@ +# Specification Quality Checklist: Flameout Alarm + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-10-04 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Both clarification markers resolved 2026-10-04: a Cut switch is required + to enable monitoring (FR-003, FR-005); Cut is the only way to silence the + alarm, with no separate acknowledge (FR-019). Recorded under + Clarifications in the spec. +- Revised 2026-10-04 for start overshoot: idle RPM is typed in only; a + separate arming threshold (90% of idle) replaces "at or above idle"; "learn + idle" removed. Re-validated: all items still pass. +- Revised 2026-10-04 for the RPM bar display: User Story 7 (now P2), + FR-031–FR-031c, SC-013. Screen sizes and colors in FR-031/FR-031a are + deliberate product constraints (target hardware, consistency with Speed + Gauge). Re-validated: all items still pass. +- Revised 2026-10-04: double-size layout is now a segmented sweep bar after + the user's dash reference; straight bar is the single-size layout and the + double-size fallback. Re-validated: all items still pass. +- The approved audio parameters (FR-026, FR-027) and the asset folder name + (FR-028) are deliberate: the sound was approved as a product decision on + 2026-10-02. API names stay out of the spec; the draft's open questions go + to `/speckit-clarify` and `/speckit-plan`. +- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan` diff --git a/specs/003-flameout-alarm/spec.md b/specs/003-flameout-alarm/spec.md new file mode 100644 index 0000000..b6abf41 --- /dev/null +++ b/specs/003-flameout-alarm/spec.md @@ -0,0 +1,712 @@ +# Feature Specification: Flameout Alarm + +**Feature Branch**: `003-flameout-alarm` + +**Created**: 2026-10-04 + +**Status**: Draft + +**Input**: User description: "Flameout Alarm, an in-flight telemetry app that +warns the pilot by sound and on screen when the turbine appears to have flamed +out" (source: `docs/drafts/flameout-alarm.md`, 2026-10-02, alarm sound +approved by Aaron the same day). + +## Overview + +**Flameout Alarm** is a transmitter app that watches a turbine's RPM telemetry +and raises an immediate, unmistakable alarm when the engine spools down while +the pilot expects it to be running. + +**Problem.** A turbine flameout in flight is easy to miss. The model keeps +flying, the engine noise is already distant, and the pilot may not notice the +loss of thrust until the model is slow, low or out of glide range. Many ECUs +send RPM over telemetry, so the transmitter can tell the moment the engine +spools down. + +**Why an app and not a native alarm.** The transmitter's own telemetry alarms +can fire on "RPM below X", optionally gated by a switch, but they can't tell +"the engine has not started yet" from "the engine was running and stopped". +A native low-RPM alarm therefore also sounds through the whole start sequence +and after every shutdown. This app adds **arming**: it alarms only after the +engine has demonstrably been running at idle or above. + +The app only listens and informs. It never controls the model or changes any +transmitter setting (constitution principle I), and it does not replace the +ECU's own failsafe, shutdown or auto-restart logic. + +Proposed script name: `AG-FlmOt.lua`, menu name "Flameout Alarm". The +filename becomes permanent at first release (constitution V). + +### Terms used in this spec + +- **Idle RPM**: the idle speed specified in the engine's ECU setup, typed in + by the pilot. The engine settles to it after a start. Both thresholds + below are set as percentages of it. +- **Start overshoot**: at the end of a start or relight, RPM typically rises + above idle RPM and then decays slowly back down to it. By then the engine + is running on its own. +- **Arming threshold**: an RPM somewhat below idle RPM (default 90% of it). + RPM at or above it counts as "engine running". It sits below the settled + idle so normal idle wander doesn't keep crossing it, and above any RPM the + engine reaches before it is running on its own. +- **Startup range**: RPM below the arming threshold, reached while the + starter and ignition spin the engine during a start or an ECU auto-restart. +- **Flameout threshold**: an RPM below the arming threshold (default 70% of + idle RPM). Falling below it while armed is treated as a flameout. +- **Arming time**: how long RPM must stay at or above the arming threshold + before the app arms, or before an active flameout clears after a relight. + +In order, from low to high: startup range < flameout threshold < arming +threshold < idle RPM < start overshoot. +- **Detection delay**: how long RPM must stay below the flameout threshold + before the alarm starts. +- **Cut switch**: the throttle safety switch and its Cut (engine off) + position. Selected in this app independently of any other app. +- **Alarm cycle**: one 5-second repetition of the alarm sound: the voice + callout "Flameout! Flameout! Flameout!" followed by the lock tone. + +## Clarifications + +### Session 2026-10-04 + +- Q: Is a Cut switch required before monitoring can be turned on? → A: Yes. + Monitoring needs both an RPM sensor and a Cut switch. Without one, every + normal shutdown of an armed engine would sound the alarm (FR-003, FR-005). +- Q: How does the pilot silence an active alarm? → A: With the Cut switch + only. There is no separate acknowledge action; the draft's "acknowledged + Flameout" state is dropped. Cut stops the alarm and disarms (FR-016, + FR-019). Context: the transmitter passes key presses to an app only while + its settings screen is open, so a key on the main screen was not an option. + Moving to Cut also commands the ECU to shut down, so it ends any + auto-restart attempt; a pilot who wants the auto-restart to finish hears the + alarm until the engine relights. +- Q: Turbine starts overshoot idle and then decay slowly back to the ECU's + idle (raised by the user). How should idle be determined and used? → A: + For this release, idle RPM is **typed in only**: the pilot enters the idle + specified in the ECU setup. The draft's "learn idle" helper is dropped, + since learning during the decay would record too high a value. Arming and + relight use a separate **arming threshold** set as a percentage below idle + (default 90%), so the settled idle and its normal wander sit safely above + it, while the overshoot simply counts as running (FR-006, FR-007). This + replaces the earlier assumption of no idle tolerance in v1. +- Q: What does the app show on screen? → A: A double-size telemetry window + showing RPM with a marker at idle RPM and the RPM as a number, in Speed + Gauge's color scheme. The earlier plan to list the threshold values in the + window is dropped: they don't fit at 150 × 68 and are already shown in + settings. +- Q: What should the double-size layout look like? → A: Like a motorsport + dash tachometer (the user supplied a Haltech iC-7 dash layout as the + reference): a segmented sweep bar that curves upward and grows toward the + high end, with the large RPM number under it. A plain straight bar with + the number above it is the fallback if the sweep doesn't work on the + transmitter, and is the layout for single size (User Story 7, + FR-031–FR-031c). + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Hear a flameout the moment it happens (Priority: P1) + +A pilot flying a turbine model has the app enabled and configured. In flight, +the engine flames out. Within the detection delay the transmitter starts the +alarm: "Flameout! Flameout! Flameout!" in the app voice, then a rapid +missile-lock beeping, repeating every 5 seconds. The app's telemetry window +shows a large "FLAMEOUT" and the stick vibrates where the transmitter supports +it. The alarm continues until the engine relights and holds at or above the +arming threshold, or the +pilot moves the Cut switch to Cut. + +**Why this priority**: This is the reason the app exists. Everything else +supports a timely, correct alarm. + +**Independent Test**: In the emulator, with a simulated RPM sensor and the +app enabled, raise RPM above the arming threshold for longer than the arming +time, then drop +it to zero. The alarm must start within (detection delay + 0.5 s) and repeat +its 5-second cycle until stopped. + +**Acceptance Scenarios**: + +1. **Given** the app is Armed and the Cut switch is not in Cut, **When** RPM + stays below the flameout threshold for the detection delay, **Then** the + alarm starts, the telemetry window shows FLAMEOUT, and the stick vibrates + if supported. +2. **Given** the alarm is active, **When** 5 seconds pass from the start of a + callout, **Then** the next callout starts, with the lock tone filling the + time between callouts. +3. **Given** the app is Armed, **When** RPM dips below the flameout threshold + for less than the detection delay (a glitch or single bad sample), **Then** + no alarm sounds. +4. **Given** another app is speaking when the alarm starts or a callout is + due, **Then** the alarm interrupts or pre-empts it rather than waiting + behind it. + +--- + +### User Story 2 - Silence during normal operation (Priority: P1) + +A pilot powers on, starts the turbine, idles, taxis, flies with run-ups and +throttle chops to idle, lands, and shuts down with the Cut switch. The app +makes no flameout alarm at any point. It arms quietly once RPM has held at or +above the arming threshold for the arming time, typically during the start +overshoot or at the settled idle (with an optional short "armed" +confirmation), and disarms silently when the Cut switch goes to Cut. + +**Why this priority**: An alarm that cries wolf gets ignored or turned off. +False alarms make the app worse than no app, so this is as important as the +alarm itself. + +**Independent Test**: Replay or simulate a full normal flight profile +(power-on, start sequence with RPM rising through the startup range, +overshooting idle and decaying back to it, idle, run-ups, chops to idle, +landing, Cut). Zero flameout alarms. + +**Acceptance Scenarios**: + +1. **Given** the model has just loaded with monitoring on, **When** RPM is + zero, invalid or anywhere in the startup range, **Then** the app stays + Disarmed and silent. +2. **Given** the app is Disarmed and the Cut switch is not in Cut, **When** + RPM holds at or above the arming threshold for the arming time, **Then** + the app becomes Armed (with the optional "armed" confirmation if enabled). +3. **Given** the start overshoots idle and RPM then decays slowly to the + settled idle, **Then** RPM never falls below the arming threshold, the app + arms and stays Armed, and no alarm sounds. +4. **Given** the app is Armed, **When** the pilot chops throttle from full to + idle, **Then** RPM stays above the flameout threshold and no alarm sounds. +5. **Given** the app is Armed, **When** the pilot moves the Cut switch to Cut, + **Then** the app disarms immediately and silently, and the following + spool-down never alarms. +6. **Given** a start fails or is aborted before RPM holds at or above the + arming threshold for the arming time, **Then** the app never arms and + never alarms. + +--- + +### User Story 3 - Per-model setup with an assigned RPM sensor (Priority: P1) + +Not every turbine sends RPM telemetry. The pilot opens the app's settings on a +turbine model whose ECU sends RPM, picks that sensor from the model's +telemetry sensors, selects the Cut switch and its Cut position, types in the +idle RPM (and, optionally, the max RPM for the bar display) from the ECU +setup, checks the arming and flameout thresholds +(percentages of idle), arming time and detection delay, and turns monitoring +on. Each model keeps its +own settings; on every other model the app stays off. + +**Why this priority**: The alarm cannot work without a correct sensor and +thresholds, and a model without RPM telemetry must never alarm. + +**Independent Test**: On a fresh model, open settings: monitoring is off and +cannot be turned on until a sensor and a Cut switch are assigned. Assign both +and enable. +Switch to a second model: monitoring is off there. Restart the transmitter: +both models keep their settings. + +**Acceptance Scenarios**: + +1. **Given** a model where the app has never been configured, **Then** + monitoring is off and the telemetry window shows OFF. +2. **Given** no RPM sensor or no Cut switch is assigned, **When** the pilot + tries to turn monitoring on, **Then** it stays off and the settings screen + says which one must be assigned first. +3. **Given** the model's sensor list is empty, **When** the pilot opens + settings, **Then** the screen explains that the turbine's ECU must send RPM + telemetry and that telemetry must have been received at least once + (transmitter and receiver powered on). +4. **Given** the pilot types in idle RPM, **Then** settings show the arming + and flameout thresholds in RPM, computed from their percentages, next to + the percentages. +5. **Given** the pilot sets a flameout threshold at or above the arming + threshold, or an arming threshold at or above idle RPM, **Then** settings + refuse the value and explain why. +6. **Given** monitoring is enabled on model A, **When** the pilot loads model + B, **Then** monitoring is off on model B unless it was enabled there. + +--- + +### User Story 4 - Auto-restart and relight (Priority: P2) + +The engine flames out and the ECU tries an auto-restart. While the starter and +ignition push RPM up through the startup range, the alarm keeps going. If the +engine relights and RPM holds at or above the arming threshold for the arming +time (usually while it overshoots idle), the alarm clears, the app returns to +Armed, and it can announce "relit" (optional). As RPM then decays to the +settled idle it stays above the arming threshold, so the app stays Armed. If the restart fails and RPM falls back, the original flameout +stays active and no second alarm event starts. + +**Why this priority**: Without this, an auto-restart attempt could falsely +clear the alarm while the engine is still not producing thrust, or produce a +confusing stream of new alarms. + +**Independent Test**: Simulate a flameout, then an RPM profile that climbs +through the startup range, falls back, climbs again, overshoots idle and +decays to it. The alarm must not clear in the startup range, must clear +within (arming time + 0.5 s) of RPM holding at or above the arming +threshold, must stay clear through the decay, and must never start a second +event. + +**Acceptance Scenarios**: + +1. **Given** the alarm is active, **When** RPM rises anywhere within the + startup range, **Then** the alarm continues. +2. **Given** the alarm is active, **When** RPM holds at or above the arming + threshold for the arming time, **Then** the alarm stops, the app returns + to Armed, and the optional "relit" callout plays. +3. **Given** the alarm has just cleared on a relight overshoot, **When** RPM + decays to the settled idle, **Then** the app stays Armed and silent. +4. **Given** the alarm is active, **When** RPM wobbles within the startup + range and falls back, **Then** the same alarm continues and no second + alarm event starts. + +--- + +### User Story 5 - Stop the alarm with the Cut switch (Priority: P2) + +The pilot has heard the alarm and is setting up the dead-stick landing. They +move the Cut switch to Cut, as they would to secure a dead engine. All alarm +sound stops at once and the app goes to Disarmed. It does not alarm again +until the engine has been restarted and RPM has held at or above the arming +threshold for the arming time with the switch out of Cut. + +**Why this priority**: A continuous alarm during a dead-stick approach is a +distraction once the pilot knows. Using the switch the pilot already reaches +for avoids a second control. + +**Independent Test**: Trigger the alarm in the emulator, move the Cut switch +to Cut: sound stops at once, the window shows DISARMED, and no further +callouts follow while RPM stays low. + +**Acceptance Scenarios**: + +1. **Given** the alarm is active, **When** the pilot moves the Cut switch to + Cut, **Then** all alarm sound stops at once, mid-callout or mid-beep, and + the app is Disarmed. +2. **Given** the app was disarmed by Cut after a flameout, **When** RPM stays + low or moves within the startup range, **Then** no alarm sounds. +3. **Given** the app was disarmed by Cut, **When** the switch leaves Cut and + RPM holds at or above the arming threshold for the arming time, **Then** + the app is Armed again and a + later flameout alarms normally. + +--- + +### User Story 6 - Tell telemetry loss apart from a flameout (Priority: P2) + +While the app is Armed, the RPM sensor stops reporting valid data (receiver +out of range, ECU converter unplugged). The app gives a distinct "telemetry +lost" warning, never the flameout alarm, and shows NO TELEMETRY. When valid +data returns, the app continues from the RPM it then sees. + +**Why this priority**: The pilot's response to a dead engine and to lost +telemetry is different; confusing them is dangerous. + +**Independent Test**: With the app Armed in the emulator, stop the simulated +RPM sensor. The "telemetry lost" warning plays, not the flameout alarm, and +the window shows NO TELEMETRY. + +**Acceptance Scenarios**: + +1. **Given** the app is Armed, **When** RPM data stops being valid, **Then** + the app gives the "telemetry lost" warning and shows NO TELEMETRY, and the + flameout alarm does not sound. +2. **Given** the model loads and telemetry has never been received, **Then** + the window shows NO TELEMETRY and the app never alarms. +3. **Given** NO TELEMETRY while Armed, **When** valid data returns with RPM at + or above the arming threshold, **Then** the app is Armed again with no + alarm. + +--- + +### User Story 7 - RPM bar display (Priority: P2) + +The pilot places the app's telemetry window on the main screen at double +size. It looks like a motorsport dash tachometer (design reference: a +Haltech iC-7 dash layout, kept locally as `docs/vendor/rpm-bar-reference.png`, +not committed). The RPM is a **segmented sweep bar**: a row of blocks running +left to right that curves upward and grows taller toward the high-RPM end. +Segments light up from the left as RPM rises; unlit segments stay visible as +dim outlines. A marker on the sweep shows idle RPM. The RPM is a large number +in the open space under the sweep, labeled "RPM", and the app's state (OFF, +DISARMED, ARMED, FLAMEOUT, NO TELEMETRY) is shown in the window. The pilot +can see at a glance that the engine is running, how far above idle it is, +and that the app is armed before take-off. On a flameout, FLAMEOUT takes +over the window. Colors follow Speed Gauge (dark face, bright colors), not +the reference's amber. + +At single size the window falls back to a simple straight bar with an idle +marker beside the RPM number. + +**Why this priority**: It's the pilot's everyday view of the engine and +confirms the app is armed before take-off. The audio alarm still carries the +safety value, so it ranks below the P1 stories. + +**Independent Test**: Place the window at double size in the emulator, drive +the simulated RPM from 0 through idle to max and back, and step through each +state. The bar tracks RPM, the idle marker sits at idle RPM, the number +matches the sensor, and everything fits and reads clearly in the 150 × 68 +transmitter window. + +**Acceptance Scenarios**: + +1. **Given** valid RPM, **Then** the lit segments of the sweep are in + proportion to RPM between zero and the bar's full-scale RPM, and the + number shows the current RPM. +2. **Given** idle RPM is entered, **Then** a marker on the sweep shows where + idle RPM sits, and the lit segments reach the marker when RPM equals idle + RPM. +3. **Given** RPM above full scale, **Then** the bar shows full and the number + still shows the true RPM. +4. **Given** each app state, **Then** the window shows the matching state + label. +5. **Given** NO TELEMETRY or OFF, **Then** no segments are lit, the number + shows no value (e.g. "---"), and the state label says why. +6. **Given** the alarm is active, **Then** FLAMEOUT is the dominant element + of the window, in the alarm color. +7. **Given** the window is placed at single size, **Then** it shows a + straight bar with the idle marker, the RPM number and the state. + +### Edge Cases + +- **Turbine has no RPM sensor.** The user leaves monitoring off; the app stays + silent and shows OFF. Adding the app to such a model never causes alarms. +- **User tries to enable with no sensor assigned.** Blocked, with an + explanation. +- **No telemetry sensors listed at setup** (receiver off, or ECU not sending). + Setup explains that telemetry must be received once before a sensor can be + assigned. +- **Throttle chop from full to idle.** RPM falls fast but stays above the + flameout threshold. No alarm. This drives how far below idle the default + threshold sits. +- **Brief telemetry dropout or a single bad sample.** The detection delay + rides it out; no alarm. +- **Sensor reports 0 or invalid at power-on.** Stays Disarmed. +- **Failed start or hot-start abort.** RPM never holds at or above the + arming threshold for the arming time, so no arming and no alarm. +- **Start overshoot and slow decay to idle.** RPM rises above idle RPM, then + decays back to it over several seconds. The app arms during the overshoot + or at the settled idle, and the decay never crosses the arming threshold, + so it stays Armed. No alarm. +- **ECU auto-restart after flameout.** The alarm keeps going until RPM holds + at or above the arming threshold for the arming time (usually during the + relight overshoot), then clears and the app re-arms. The decay to settled + idle that follows does not disturb it. +- **Auto-restart fails partway.** RPM rises into the startup range and falls + back. No clear, no second alarm; the original flameout stays active. +- **Startup RPM peaks close to the arming threshold.** If the engine reaches + the arming threshold before it is running on its own, a failed restart + could clear the alarm early. The README explains the rule, and the arming + percentage can be raised for such an engine. +- **Idle RPM typed in wrong.** + - Too high (e.g. the pilot entered a run-up RPM): the settled idle sits + below the arming threshold. The app arms only on a run-up, and after a + relight the alarm keeps sounding at idle until the pilot opens the + throttle. + - Too low: the thresholds sit lower than intended, so the alarm comes + later in a spool-down and a restart can clear it sooner. + - Settings show the live RPM next to the typed idle so the pilot can + compare them with the engine idling, and the README explains both cases. +- **Settled idle wanders** (temperature, altitude, fuel). The arming + threshold sits a margin below idle RPM (default 10%), so normal wander + doesn't cross it. +- **Relight or Cut mid-callout or mid-beep.** All alarm sound + stops at once; no further callouts. +- **Another app (e.g. Speed Gauge) is speaking when the alarm fires or a + callout is due.** The alarm interrupts or pre-empts it. +- **Alarm audio file missing** (for example, the voice file was not + generated). The app falls back to the transmitter's built-in beeps plus the + on-screen FLAMEOUT, and settings shows "voice files missing". A missing file + never silences the alarm. +- **Pilot flips to Cut during the alarm.** The alarm stops; the state becomes + Disarmed. This is the only way to silence the alarm without a relight. +- **Pilot wants the ECU auto-restart to finish.** Cut would end the restart, + so the alarm keeps sounding until the engine relights and holds at or above + the arming threshold, or the pilot gives up and moves to Cut. +- **Cut switch unassigned or missing** (e.g. a model copied from one with a + different switch layout). Monitoring cannot be enabled without it; if it + disappears after enabling, the app treats monitoring as off and settings say + why. +- **Wrong sensor selected** (e.g. EGT instead of RPM). Setup warns if the + sensor's unit isn't RPM-like, or if its value never changes. +- **Assigned sensor disappears** after an ECU swap, receiver rebind or model + copy. The app shows "sensor not found" and stays Disarmed instead of + erroring; the user can re-assign it or turn monitoring off. +- **Model copied from another model.** The copied sensor assignment may not + exist on the new model's receiver; handled as above. +- **Thresholds set nonsensically** (flameout threshold at or above the + arming threshold, or arming threshold at or above idle RPM). Setup blocks + it and explains. +- **RPM above the bar's full scale** (max RPM entered too low, or not + entered and the default multiple is too small). The bar shows full; the + number still shows the true RPM. +- **Max RPM entered at or below idle RPM.** Settings refuse it and explain. +- **Idle RPM never entered.** There is no sensible default (idle differs + widely between engines), so monitoring can't be turned on until it is. +- **Two engines.** Out of scope for v1 (see Assumptions). + +## Requirements *(mandatory)* + +### Functional Requirements + +**Configuration and per-model state** + +- **FR-001**: Monitoring MUST be off by default on every model. +- **FR-002**: Users MUST be able to assign the RPM sensor from the model's + telemetry sensors. +- **FR-003**: Monitoring MUST NOT be able to be turned on until an RPM + sensor and a Cut switch are assigned and idle RPM is entered. The settings + screen MUST say which is missing when the user tries. +- **FR-004**: When the model has no telemetry sensors listed, the settings + screen MUST explain that the ECU must send RPM telemetry and that telemetry + must have been received at least once. +- **FR-005**: Users MUST be able to select the Cut switch and its Cut + position. A Cut switch is required for monitoring (FR-003). +- **FR-006**: Users MUST set idle RPM by typing in the idle specified in the + ECU setup. It has no default. There is no automatic or learned idle in this + release. +- **FR-007**: Users MUST be able to set the arming threshold and the + flameout threshold as editable percentages of idle RPM, defaulting to 90% + and 70% (see Assumptions). Settings MUST show each one's RPM value next to + its percentage. Settings MUST refuse an arming threshold at or above idle + RPM, and a flameout threshold at or above the arming threshold. +- **FR-008**: Users MUST be able to set the arming time (default 3 s) and the + detection delay (default 1.0 s). +- **FR-009**: Users MUST be able to turn the optional "armed" and "relit" + confirmations on or off (both default off). +- **FR-010**: Setup MUST warn when the assigned sensor's unit isn't RPM-like, + and MUST show the live RPM next to the typed idle RPM so the pilot can check + it against the idling engine. +- **FR-011**: All settings MUST be saved per model and survive a transmitter + restart. Enabling the app on one model MUST NOT enable it on another. + +**Off state** + +- **FR-012**: When monitoring is off, or the RPM sensor, Cut switch or idle + RPM is missing, the app MUST produce no sounds, vibration or warnings in any scenario, including + telemetry loss, and its telemetry window MUST show OFF. + +**Arming and detection** + +- **FR-013**: When monitoring is on, the app MUST start Disarmed at model + load, and RPM that is zero, invalid or within the startup range MUST keep + it Disarmed. +- **FR-014**: The app MUST arm only when the Cut switch is not in Cut and RPM + has stayed at or above the arming threshold for the arming time. RPM above + idle RPM (the start overshoot) counts toward arming like any RPM at or + above the arming threshold. +- **FR-015**: While Armed, the app MUST raise the flameout alarm when RPM has + stayed below the flameout threshold for the detection delay and the Cut + switch is not in Cut. +- **FR-016**: Moving the Cut switch to Cut MUST disarm the app immediately and + silently from any state, stopping any alarm sound at once. +- **FR-017**: While in Flameout, RPM within the startup range MUST NOT clear + the flameout and MUST NOT start a new alarm event. +- **FR-018**: A flameout MUST clear only when RPM has stayed at or above the + arming threshold for the arming time (or when the Cut switch goes to Cut). On clearing by + relight the app MUST return to Armed and play the "relit" confirmation if + enabled. +- **FR-019**: The Cut switch MUST be the only way for the pilot to silence an + active alarm. There is no separate acknowledge action. + +**Telemetry loss** + +- **FR-020**: When the RPM sensor stops reporting valid data while Armed or in + Flameout, the app MUST give a "telemetry lost" warning that is clearly + different from the flameout alarm, and MUST show NO TELEMETRY. Telemetry + loss MUST never trigger the flameout alarm. +- **FR-021**: When valid data returns, the app MUST resume from the RPM it + then sees: Armed if RPM is at or above the arming threshold, otherwise following the + normal detection rules (the detection delay restarts). +- **FR-022**: If the assigned sensor is not found among the model's sensors, + the app MUST show "sensor not found", stay Disarmed and not error. + +**Alarm sound** + +- **FR-023**: The alarm MUST repeat a 5-second cycle: a voice callout + "Flameout! Flameout! Flameout!" (about 2 s), then a rapid, high-pitched + lock tone filling the rest of the cycle. A new callout MUST start every + 5 s (±0.3 s), callout start to callout start, with no audible gap or + overlap between callout and tone. +- **FR-024**: The alarm MUST continue until cleared by relight or stopped by + Cut. +- **FR-025**: The alarm MUST interrupt or pre-empt speech from other apps + rather than queue behind it. +- **FR-026**: The callout MUST use the same Piper "Amy" voice as Speed Gauge + (Speed Gauge spec, User Story 6 and FR-030–FR-037), so all AG- apps sound + consistent, but delivered urgently: about 30% faster, pitch raised about one + semitone without changing speed, with presence boost and light compression + so it cuts through wind and engine noise. The approved processing settings + are recorded in `docs/drafts/flameout-alarm.md`. +- **FR-027**: The lock tone MUST match the approved sample: about 1800 Hz with + a 3600 Hz overtone, 45 ms on / 35 ms off with short fades (12.5 beeps per + second). +- **FR-028**: Both alarm sounds MUST be audio files in the app's own asset + folder (`AG-FlmOt/`), not references to another app's files. The voice file + MUST follow Speed Gauge's voice rules (generated locally by the repo's voice + script from the same Amy model, not committed to git, credited for Piper + and the Amy model in the folder and in `CREDITS.md`). The lock tone, being + plain synthesized sound, MAY be committed. +- **FR-029**: If an alarm audio file is missing, the app MUST fall back to the + transmitter's built-in beeps with the on-screen FLAMEOUT, and the settings + screen MUST show "voice files missing". A missing file MUST never silence + the alarm. +- **FR-030**: While the alarm is active, the app MUST vibrate the stick where + the transmitter supports it. + +**Display** + +- **FR-031**: The app MUST provide a telemetry window designed for double + size (150 × 68 on the transmitter, title drawn above the window). It MUST + show: + - the RPM as a segmented sweep bar in the style of the design reference + (User Story 7): segments run left to right from zero to the full-scale + RPM (FR-031b), the sweep curves upward and grows taller toward the high + end, lit segments show the current RPM and unlit ones stay visible as + dim outlines; + - a marker on the sweep at idle RPM; + - the current RPM as a large number in the space under the sweep, labeled + "RPM"; + - the state: OFF, DISARMED, ARMED, FLAMEOUT or NO TELEMETRY. + FLAMEOUT MUST be the dominant element while the alarm is active. If the + sweep proves unreadable or too costly to draw on the transmitter, the + layout MAY fall back to the straight-bar layout of FR-031c scaled up to + double size, with the number above the bar. +- **FR-031a**: The window MUST follow Speed Gauge's look (Speed Gauge + research R8): an always-dark face, white numbers, light grey labels, cyan + lit segments, dim grey unlit segment outlines and a yellow idle marker by + default. Red or orange is used only + for the flameout alarm, so nothing else on the display can be mistaken for + it. +- **FR-031b**: Users MUST be able to enter the engine's maximum RPM from the + ECU setup as the bar's full-scale RPM. If it isn't entered, the bar scales + to a default multiple of idle RPM (see Assumptions). Max RPM affects only + the display, never arming or detection, and MUST be above idle RPM. +- **FR-031c**: If the window is placed at single size (150 × 23), it MUST + show a straight horizontal bar filling left to right with the idle marker, + the RPM number beside it, and the state. Full screen is out of scope. + +**Safety and footprint** + +- **FR-032**: The app MUST NOT drive any output or change any transmitter or + model setting. It only reads telemetry and switches, plays sound, vibrates + and draws (constitution I). +- **FR-033**: The app MUST install standalone: its script and its own asset + folder, plus any shared lib modules, which the plan, install instructions + and any release package MUST list. It MUST NOT depend on any other app. +- **FR-034**: When monitoring is off, the app's per-cycle work MUST be + negligible. +- **FR-035**: The README MUST state that the app is advisory, does not replace + the ECU's failsafe, shutdown or auto-restart logic nor the pilot's own + monitoring, and only works on turbines that send RPM telemetry. It MUST + also explain: entering idle RPM from the ECU setup; the start overshoot and + why the arming threshold sits below idle; the arming threshold having to + stay above any RPM the engine reaches before it runs on its own; and the + effects of an idle typed too high or too low. + +### Key Entities + +- **Model configuration** (one per model): monitoring enabled, assigned RPM + sensor, Cut switch and position, idle RPM (typed in), max RPM for the bar + display (optional), arming threshold and + flameout threshold (as percentages of idle RPM), arming time, detection + delay, confirmation toggles. +- **Engine monitor state** (in memory, reset on model load): one of Off, + Disarmed, Armed, Flameout, No telemetry; + plus the timers for arming, detection and the alarm cycle. +- **Alarm assets**: the urgent voice callout and the lock tone, in the app's + own folder, with their credits. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: In replayed or emulated flameout profiles, the alarm starts + within (detection delay + 0.5 s) of RPM crossing below the threshold in + 100% of runs. +- **SC-002**: Zero alarms across a full normal flight profile: power-on, + start sequence (including the overshoot above idle and the slow decay back + to it), idle, run-ups, throttle chops to idle, landing, and shutdown + via the Cut switch. +- **SC-003**: Zero flameout alarms before the app first arms (start + sequence, failed starts, cool-down). +- **SC-004**: Telemetry loss produces the "telemetry lost" warning, never the + flameout alarm, in 100% of tests. +- **SC-005**: A pilot can enable the app for a model and configure sensor, + switch and thresholds in under 3 minutes without any other manual. +- **SC-006**: The app never changes any transmitter output or setting (servo + outputs and model file unchanged after a session with no user edits). +- **SC-007**: Copying only the app's files (plus any listed lib modules) to + `/Apps` on a transmitter with no other AG- apps is enough to load and run. +- **SC-008**: No "CPU limit" errors over a 30-minute emulator session with the + app enabled and alarming at least once. +- **SC-009**: On a model where monitoring is off (the default), or the RPM + sensor, Cut switch or idle RPM is missing, the app produces zero sounds or + warnings in any scenario, including telemetry loss. +- **SC-010**: Enable state and sensor assignment are kept per model: enabling + it on model A does not enable it on model B, and both settings survive a + transmitter restart. +- **SC-011**: In replayed auto-restart profiles, the flameout alarm never + clears while RPM is in the startup range, clears within (arming time + + 0.5 s) of RPM holding at or above the arming threshold, stays clear while + RPM decays from the relight overshoot to idle, and fires no second alarm + for the same flameout, in 100% of runs. A failed restart leaves the alarm + active. +- **SC-012**: While the alarm is active, a callout starts every 5 s (±0.3 s), + the lock tone plays between callouts with no audible gap or overlap, and the + cycle continues until relight or Cut, in 100% of tests. +- **SC-013**: At double size on the transmitter, a pilot can read the RPM + number, the bar's position relative to the idle marker, and the state at a + glance (under 1 second) with nothing clipped. The display reflects an RPM + change within 1 second. + +## Assumptions + +- **Script name** `AG-FlmOt.lua` ("Flameout Alarm") is used unless changed + before first release. +- **Default thresholds** are placeholders until real RPM logs are reviewed + (draft open question 4): arming threshold 90% of idle RPM, flameout + threshold 70% of idle RPM, arming time 3 s, detection delay 1.0 s. Settled + idle is assumed to wander by less than 10%, and a throttle chop is assumed + to settle at idle without dipping far below it, so 70% leaves margin. +- **The startup range stays clearly below 90% of idle** on the user's ECUs + during starts and auto-restarts, i.e. the engine is running on its own + before it reaches the arming threshold (draft open question 2, to confirm + from logs). +- **Start overshoot** decays to idle without dipping below the arming + threshold. Logs should confirm the overshoot height, decay time and how + much settled idle wanders; they set the 90% default. +- **Idle RPM is typed in only** in this release, from the ECU setup. A + "learn idle" helper or an idle derived automatically each session is a + possible later extension; either would need to wait for the overshoot to + decay before recording. +- **Bar full scale** defaults to 4 × idle RPM when max RPM isn't entered. + Typical model turbines reach roughly 3–4.5 times their idle speed at full + throttle, so + this puts full throttle near the right end of the bar. +- **Display layout** in FR-031 is a starting point for design iteration in + the plan: the number of segments (the reference has about 16; around 10–14 + is likely at 150 × 68), the curve and taper of the sweep, where the state + label and idle marker go, and whether threshold marks join the idle + marker. It is checked on the transmitter's 150 × 68 window, not the + emulator's larger one. Drawing cost is checked too (constitution VI): the + sweep is built from plain filled shapes without transparency, which Speed + Gauge's measurements show are cheap, or from a pre-drawn image of the + unlit sweep. +- **Detection uses a fixed RPM threshold only** in v1. Rate-of-drop + detection and ECU status sensors are possible later extensions (draft open + questions 3 and 5). +- **"Telemetry lost" warning** is a short spoken "Engine telemetry lost" in + the app voice, given once per loss event, with NO TELEMETRY shown until data + returns. It does not repeat, because the transmitter already announces + signal loss itself. +- **One engine per model** in v1. Two-engine models are out of scope; a later + version could monitor one RPM sensor per engine. +- **Target hardware** is the DS-24 II / DC-24 II on firmware 6.x. The + emulator plays no Lua audio, so the alarm sound and its timing can only be + verified on the transmitter. +- **Turbine Throttle Setup** (spec 002, separate app) uses the same physical + safety switch, but this app selects it independently and does not read the + other app's settings. +- API availability (sensor validity, audio playback priority and stopping, + vibration, sensor identity across power cycles) is confirmed during + planning; the draft's open questions 1–12 feed `/speckit-clarify` and + `/speckit-plan`. From af9b1fd7b5a94e346c8ac0fe9a858241c7f081b7 Mon Sep 17 00:00:00 2001 From: Aaron George Date: Sun, 4 Oct 2026 21:08:18 -0600 Subject: [PATCH 03/22] Spec 003: clarifications and RPM window mockup Clarify session 2026-10-04: - Telemetry lost during the flameout alarm keeps the alarm going, with a "no telemetry" note and no callout (FR-020a). - Telemetry-loss delay is a setting, default 2 s (FR-008, FR-020). - Plain RPM by default, with a manual sensor scale of x1/x10/x100/x1000 (FR-006a, FR-010). Adds an original SVG mockup of the double-size sweep bar (armed and flameout) and the single-size bar, with its generator script. The third-party dash screenshot that inspired it stays local in docs/vendor/. Co-Authored-By: Claude Opus 5.5 --- specs/003-flameout-alarm/design/mockup.py | 105 ++++++++++++++++++ .../design/rpm-window-mockup.svg | 48 ++++++++ specs/003-flameout-alarm/spec.md | 78 ++++++++++--- 3 files changed, 213 insertions(+), 18 deletions(-) create mode 100644 specs/003-flameout-alarm/design/mockup.py create mode 100644 specs/003-flameout-alarm/design/rpm-window-mockup.svg diff --git a/specs/003-flameout-alarm/design/mockup.py b/specs/003-flameout-alarm/design/mockup.py new file mode 100644 index 0000000..637b7b6 --- /dev/null +++ b/specs/003-flameout-alarm/design/mockup.py @@ -0,0 +1,105 @@ +"""Generate the Flameout Alarm RPM window mockup (SVG, Lua coordinates).""" +import sys + +FACE = "rgb(18,20,24)" +CYAN = "rgb(0,190,255)" +UNLIT_FILL = "rgb(34,38,44)" +UNLIT_EDGE = "rgb(70,74,80)" +YELLOW = "rgb(255,225,0)" +WHITE = "rgb(255,255,255)" +GREY = "rgb(170,170,170)" +RED = "rgb(255,45,30)" +FONT = "font-family='Arial, Helvetica, sans-serif' font-weight='bold'" + +N = 12 # segments +X0, X1 = 4, 146 # sweep extent +GAP = 2 + + +def yb(t): # bottom edge: rises steeply on the left, flattens on the right + return 22 + 40 * (1 - t) ** 2.2 + + +def h(t): # segment height grows toward the high end + return 6 + 12 * t + + +def sweep(ox, oy, lit, lit_col, idle_frac): + w = (X1 - X0 - GAP * (N - 1)) / N + out = [] + for i in range(N): + xa = X0 + i * (w + GAP) + xb = xa + w + ta, tb = (xa - X0) / (X1 - X0), (xb - X0) / (X1 - X0) + pts = [(xa, yb(ta)), (xb, yb(tb)), (xb, yb(tb) - h(tb)), (xa, yb(ta) - h(ta))] + p = " ".join(f"{ox + x:.1f},{oy + y:.1f}" for x, y in pts) + if i < lit: + out.append(f"") + else: + out.append(f"") + # idle marker: a yellow bar across the sweep, poking out above and below + xi = X0 + idle_frac * (X1 - X0) + ti = idle_frac + out.append(f"") + return "\n".join(out) + + +def window(ox, oy, title, body, wh=68): + return (f"{title}\n" + f"\n{body}") + + +def double_armed(ox, oy): + rpm, full, idle = 112400, 140000, 35000 + lit = round(N * rpm / full) + b = sweep(ox, oy, lit, CYAN, idle / full) + b += (f"\nARMED" + f"\n112,400" + f"\nRPM") + return window(ox, oy, "Double size: armed", b) + + +def double_flameout(ox, oy): + full, idle = 140000, 35000 + b = sweep(ox, oy, 0, CYAN, idle / full) + b += (f"\n" + f"\nFLAMEOUT" + f"\n4,800" + f"\nRPM") + return window(ox, oy, "Double size: flameout", b) + + +def single(ox, oy): + rpm, full, idle = 112400, 140000, 35000 + bx, bw, by, bh = 4, 84, 8, 8 + b = (f"" + f"\n" + f"\n" + f"\n112,400" + f"\nARMED") + return window(ox, oy, "Single size: armed", b, 23) + + +W, H = 330, 200 +parts = [double_armed(10, 20), double_flameout(170, 20), single(10, 120)] +note = (f"" + "Mockup in Lua coordinates (150 x 68 / 150 x 23)." + f"" + "Fonts approximate FONT_BIG / FONT_MINI." + f"" + "Yellow bar = idle RPM. Full scale = 4 x idle.") +svg = (f"\n\n" + + "\n".join(parts) + "\n" + note + "\n\n") +with open(sys.argv[1], "w", encoding="utf-8", newline="\n") as f: + f.write(svg) diff --git a/specs/003-flameout-alarm/design/rpm-window-mockup.svg b/specs/003-flameout-alarm/design/rpm-window-mockup.svg new file mode 100644 index 0000000..f5a1260 --- /dev/null +++ b/specs/003-flameout-alarm/design/rpm-window-mockup.svg @@ -0,0 +1,48 @@ + + +Double size: armed + + + + + + + + + + + + + + +ARMED +112,400 +RPM +Double size: flameout + + + + + + + + + + + + + + + +FLAMEOUT +4,800 +RPM +Single size: armed + + + + +112,400 +ARMED +Mockup in Lua coordinates (150 x 68 / 150 x 23).Fonts approximate FONT_BIG / FONT_MINI.Yellow bar = idle RPM. Full scale = 4 x idle. + diff --git a/specs/003-flameout-alarm/spec.md b/specs/003-flameout-alarm/spec.md index b6abf41..3ec1bef 100644 --- a/specs/003-flameout-alarm/spec.md +++ b/specs/003-flameout-alarm/spec.md @@ -60,6 +60,9 @@ In order, from low to high: startup range < flameout threshold < arming threshold < idle RPM < start overshoot. - **Detection delay**: how long RPM must stay below the flameout threshold before the alarm starts. +- **Telemetry-loss delay**: how long the RPM sensor must go without valid + data before the app treats telemetry as lost. Shorter dropouts are + ignored. - **Cut switch**: the throttle safety switch and its Cut (engine off) position. Selected in this app independently of any other app. - **Alarm cycle**: one 5-second repetition of the alarm sound: the voice @@ -101,6 +104,17 @@ threshold < idle RPM < start overshoot. the number above it is the fallback if the sweep doesn't work on the transmitter, and is the layout for single size (User Story 7, FR-031–FR-031c). +- Q: If telemetry is lost while the flameout alarm is already sounding, + what should the alarm do? → A: Keep going. No "telemetry lost" callout + interrupts it; the window shows FLAMEOUT with a "no telemetry" note. It + still stops only on Cut or a relight seen in valid data (FR-020a). +- Q: How long must the RPM sensor go without valid data before the app + treats it as telemetry lost? → A: Adjustable in settings as the + telemetry-loss delay, default 2 s (FR-008, FR-020). +- Q: How should the app handle ECUs that send RPM in a scaled unit, such as + thousands of RPM? → A: Plain RPM by default, with a manual sensor scale + setting (×1, ×10, ×100, ×1000, default ×1). No automatic unit conversion + (FR-006a, FR-010). ## User Scenarios & Testing *(mandatory)* @@ -314,15 +328,21 @@ the window shows NO TELEMETRY. 3. **Given** NO TELEMETRY while Armed, **When** valid data returns with RPM at or above the arming threshold, **Then** the app is Armed again with no alarm. +4. **Given** the flameout alarm is active, **When** RPM data stops being + valid, **Then** the alarm continues, no "telemetry lost" callout plays, + and the window shows FLAMEOUT with a "no telemetry" note. --- ### User Story 7 - RPM bar display (Priority: P2) The pilot places the app's telemetry window on the main screen at double -size. It looks like a motorsport dash tachometer (design reference: a -Haltech iC-7 dash layout, kept locally as `docs/vendor/rpm-bar-reference.png`, -not committed). The RPM is a **segmented sweep bar**: a row of blocks running +size. It looks like a motorsport dash tachometer (inspiration: a Haltech +iC-7 dash layout the user supplied, kept locally as +`docs/vendor/rpm-bar-reference.png` and not committed because it is a +third-party image). The working design is the original mockup +`design/rpm-window-mockup.svg` in this spec folder, generated by +`design/mockup.py`. The RPM is a **segmented sweep bar**: a row of blocks running left to right that curves upward and grows taller toward the high-RPM end. Segments light up from the left as RPM rises; unlit segments stay visible as dim outlines. A marker on the sweep shows idle RPM. The RPM is a large number @@ -377,8 +397,10 @@ transmitter window. - **Throttle chop from full to idle.** RPM falls fast but stays above the flameout threshold. No alarm. This drives how far below idle the default threshold sits. -- **Brief telemetry dropout or a single bad sample.** The detection delay - rides it out; no alarm. +- **Brief telemetry dropout or a single bad sample.** A bad RPM sample is + ridden out by the detection delay, and a dropout shorter than the + telemetry-loss delay by that delay; no alarm and no "telemetry lost" + callout. - **Sensor reports 0 or invalid at power-on.** Stays Disarmed. - **Failed start or hot-start abort.** RPM never holds at or above the arming threshold for the arming time, so no arming and no alarm. @@ -426,12 +448,19 @@ transmitter window. disappears after enabling, the app treats monitoring as off and settings say why. - **Wrong sensor selected** (e.g. EGT instead of RPM). Setup warns if the - sensor's unit isn't RPM-like, or if its value never changes. + sensor's unit isn't plain RPM, or if its value never changes. +- **ECU sends scaled RPM** (e.g. 112 meaning 112,000). With the default ×1 + scale the live RPM in settings reads far below the typed idle, and the + unit warning shows; the pilot sets the sensor scale to match (×1000 here). + Until then the app never arms, so it never false-alarms. - **Assigned sensor disappears** after an ECU swap, receiver rebind or model copy. The app shows "sensor not found" and stays Disarmed instead of erroring; the user can re-assign it or turn monitoring off. - **Model copied from another model.** The copied sensor assignment may not exist on the new model's receiver; handled as above. +- **Telemetry lost during the flameout alarm.** The engine is most likely + still out, so the alarm keeps going; only Cut or a relight seen in valid + data stops it. - **Thresholds set nonsensically** (flameout threshold at or above the arming threshold, or arming threshold at or above idle RPM). Setup blocks it and explains. @@ -463,18 +492,25 @@ transmitter window. - **FR-006**: Users MUST set idle RPM by typing in the idle specified in the ECU setup. It has no default. There is no automatic or learned idle in this release. +- **FR-006a**: Users MUST be able to set a sensor scale of ×1, ×10, ×100 or + ×1000, default ×1 (the sensor sends plain RPM). The app multiplies the + sensor's value by the scale before every use: display, arming and + detection. Idle RPM and max RPM are always typed in plain RPM. The app + does not convert units automatically. - **FR-007**: Users MUST be able to set the arming threshold and the flameout threshold as editable percentages of idle RPM, defaulting to 90% and 70% (see Assumptions). Settings MUST show each one's RPM value next to its percentage. Settings MUST refuse an arming threshold at or above idle RPM, and a flameout threshold at or above the arming threshold. -- **FR-008**: Users MUST be able to set the arming time (default 3 s) and the - detection delay (default 1.0 s). +- **FR-008**: Users MUST be able to set the arming time (default 3 s), the + detection delay (default 1.0 s) and the telemetry-loss delay (default + 2 s). - **FR-009**: Users MUST be able to turn the optional "armed" and "relit" confirmations on or off (both default off). -- **FR-010**: Setup MUST warn when the assigned sensor's unit isn't RPM-like, - and MUST show the live RPM next to the typed idle RPM so the pilot can check - it against the idling engine. +- **FR-010**: Setup MUST warn when the assigned sensor's unit isn't plain RPM + (pointing the pilot to the sensor scale), and MUST show the live RPM, after + scaling, next to the typed idle RPM so the pilot can check both against the + idling engine. - **FR-011**: All settings MUST be saved per model and survive a transmitter restart. Enabling the app on one model MUST NOT enable it on another. @@ -509,10 +545,15 @@ transmitter window. **Telemetry loss** -- **FR-020**: When the RPM sensor stops reporting valid data while Armed or in - Flameout, the app MUST give a "telemetry lost" warning that is clearly - different from the flameout alarm, and MUST show NO TELEMETRY. Telemetry - loss MUST never trigger the flameout alarm. +- **FR-020**: When the RPM sensor has reported no valid data for the + telemetry-loss delay while Armed, the app MUST give a "telemetry lost" warning that is clearly different from the + flameout alarm, and MUST show NO TELEMETRY. Telemetry loss MUST never + trigger the flameout alarm. +- **FR-020a**: When the RPM sensor stops reporting valid data while the + flameout alarm is active, the alarm MUST continue unchanged, with no + "telemetry lost" callout, and the window MUST show FLAMEOUT with a "no + telemetry" note. The alarm still stops only on Cut, or on a relight seen + in valid data (FR-018). - **FR-021**: When valid data returns, the app MUST resume from the RPM it then sees: Armed if RPM is at or above the arming threshold, otherwise following the normal detection rules (the detection delay restarts). @@ -557,7 +598,7 @@ transmitter window. - **FR-031**: The app MUST provide a telemetry window designed for double size (150 × 68 on the transmitter, title drawn above the window). It MUST show: - - the RPM as a segmented sweep bar in the style of the design reference + - the RPM as a segmented sweep bar in the style of the mockup (User Story 7): segments run left to right from zero to the full-scale RPM (FR-031b), the sweep curves upward and grows taller toward the high end, lit segments show the current RPM and unlit ones stay visible as @@ -605,10 +646,11 @@ transmitter window. ### Key Entities - **Model configuration** (one per model): monitoring enabled, assigned RPM - sensor, Cut switch and position, idle RPM (typed in), max RPM for the bar + sensor and its scale, Cut switch and position, idle RPM (typed in), max RPM + for the bar display (optional), arming threshold and flameout threshold (as percentages of idle RPM), arming time, detection - delay, confirmation toggles. + delay, telemetry-loss delay, confirmation toggles. - **Engine monitor state** (in memory, reset on model load): one of Off, Disarmed, Armed, Flameout, No telemetry; plus the timers for arming, detection and the alarm cycle. From 617c785c83b9c7b18fa88efb19f21d9b5b5d8114 Mon Sep 17 00:00:00 2001 From: Aaron George Date: Sun, 4 Oct 2026 21:18:58 -0600 Subject: [PATCH 04/22] Spec 003: ECU telemetry research, telemetry-lost callout - research.md R1: how JetCat, Xicoy, KingTech, Swiwin, JetCentral and Enjet ECUs deliver RPM to a Jeti transmitter. Where RPM arrives as a normal telemetry sensor it is plain RPM, so the default x1 scale fits; JetCentral, Enjet and Swiwin direct are unconfirmed. - The "telemetry lost" callout is now also spoken once between alarm cycles when telemetry is lost during the flameout alarm (FR-020a). - Idle and max RPM are entered to the nearest 100 RPM, shown in thousands with one decimal, since form.addIntbox stops at 32767 (FR-006). - Setup's "no sensors" message mentions the up-to-a-minute delay some adapters need (FR-004); the README lists support per ECU brand (FR-035). Co-Authored-By: Claude Opus 5.5 --- specs/003-flameout-alarm/research.md | 94 ++++++++++++++++++++++++++++ specs/003-flameout-alarm/spec.md | 62 ++++++++++++------ 2 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 specs/003-flameout-alarm/research.md diff --git a/specs/003-flameout-alarm/research.md b/specs/003-flameout-alarm/research.md new file mode 100644 index 0000000..b1d26b3 --- /dev/null +++ b/specs/003-flameout-alarm/research.md @@ -0,0 +1,94 @@ +# Research: Flameout Alarm + +Research notes for the plan. Started during specification (2026-10-04); +`/speckit-plan` extends this file. + +## R1. How ECUs deliver RPM to a Jeti transmitter + +**Question:** In what form does each turbine ECU send RPM over Jeti +telemetry, so the app can read it (spec FR-002, FR-006a, FR-010)? + +**Finding: RPM arrives as a plain integer RPM value on a standard Jeti EX +sensor.** Where the data reaches the transmitter as an EX sensor, the +value is the shaft speed in RPM (e.g. 112400), not thousands. The best +evidence is the open-source Jeti ECU telemetry app by Thomas Ekdahl +([thomasekdahlN/jeti](https://github.com/thomasekdahlN/jeti)), which +supports JetCat, KingTech, Xicoy, VSpeak, Digitech and CB-Electroniks +converters. Its display divides the RPM value by 1000 to show "112K" for +every converter (`ecu/lib/window4.lua`), and its per-converter configs +(`ecu/converter///config.jsn`) map RPM to a sensor +parameter with no scaling. + +The Jeti Lua API already applies the sensor's decimals: a sensor entry has +`value`, `decimals`, `unit` and `valid` (`types/jeti.lua`). So "raw numeric +value" is right in practice: the app reads a number and compares it with +RPM thresholds. The sensor scale setting (FR-006a, default ×1) stays as a +safety valve for any converter found to send a scaled value. + +### Per manufacturer + +| Manufacturer | Path to Jeti | RPM as EX sensor? | RPM param* | Status sensor? | Confidence | +| --- | --- | --- | --- | --- | --- | +| JetCat | JetCat's own Jeti converter, VSpeak, Digitech or CB-Electroniks converter | Yes | 2 | Yes, param 3: signed code, 13 = Run, negatives = faults | High | +| Xicoy (V6/V10 FADEC, also JetsMunt) | Xicoy telemetry adapter v5 (factory-set for Jeti) | Yes: sensor group "Turbine" with 6 measures (RPM, EGT, ECU battery, throttle %, pump V, fuel %) | 2 | Not as an EX sensor; status shows in the JetiBox screen emulation | High (Xicoy manual) | +| KingTech (G1–G5) | KingTech telemetry unit (lists Jeti support), VSpeak or Digitech converter | Yes | 2 | Not in the KingTech converter config | Medium | +| Swiwin (ECU V3) | VSpeak converter for Swiwin (EX-Sensor / EX-Bus); or the ECU's own telemetry port wired to a receiver's Ext port set to "Jetibox" | VSpeak: yes. Direct: unclear, may be JetiBox text only | — | VSpeak: yes (coded as a numeric value) | Low for direct | +| JetCentral | JetCentral Jeti Telemetry Adapter V2 | Unclear: the adapter ships with its own Lua app (`JCHDT.lc`, firmware ≥ 4.27); its values may not appear as ordinary sensors | — | Shown by its app | Low | +| Enjet Power | Retailers list "telemetry: Jeti, Futaba, FrSky (external module)" | Unknown: no Jeti documentation found | — | Unknown | Low | + +\* Parameter number within the converter's sensor, from the Ekdahl configs. +The app doesn't depend on it: the pilot picks the sensor by its label. + +### Other observations + +- **Update rate** isn't documented by any manufacturer. Jeti EX sensors + typically refresh several times a second; the 1.0 s detection delay and + 2 s telemetry-loss delay assume at least about 2 updates per second. + Check on the transmitter with a real converter (plan / test). +- **Sensors appear 30–60 s after power-up** with the Xicoy adapter (its + manual). Setup's "no sensors yet" message (FR-004) should say to wait. +- **The startup false-alarm problem is real.** Xicoy's adapter disables its + own low-RPM alarm until the tank drops below 98%, "to avoid the alarms of + Low RPM ... to be triggered during startup phase". That is the problem + the app's arming solves. +- **Swiwin and VSpeak:** users report the Swiwin ECU misbehaving with the + transmitter's "Auto" output period; 11–13 ms fixes it. Not our concern, + but worth a README note for Swiwin users. +- **ECU status codes** (JetCat: 13 Run, −7/−29/−30 Low rpm, −14 Failsafe; + Xicoy: 8 Running, 15 Flameout) could confirm a flameout faster than RPM, + but codes differ per brand and Xicoy doesn't send status as a sensor. + Stays out of v1 (spec Assumptions). + +### Decision + +- Read RPM from a pilot-selected EX sensor as plain RPM; keep the scale + setting at default ×1. +- Supported: any ECU or converter that shows RPM as a normal telemetry + sensor (JetCat, Xicoy, KingTech via its unit or a converter, Swiwin via + VSpeak). Not supported: setups whose RPM is only visible in a JetiBox + screen or inside another vendor's Lua app. The README says so and lists + what is known per brand. +- Open for testing: JetCentral adapter V2, Enjet, Swiwin direct. If any of + these turns out to publish RPM as a normal sensor, it just works. + +### Related limit found: number entry range + +`form.addIntbox` takes values from −32768 to 32767 (`types/jeti.lua`). +Idle RPM (typically 30,000–40,000) and max RPM (100,000–160,000) don't fit +as plain integers. **Decision (2026-10-04):** settings enter them in +steps of 100 RPM, shown in thousands with one decimal ("35.0" = 35,000 +RPM): the stored integer is RPM / 100 (max 1,600 for 160,000 RPM). See spec +FR-006. + +### Sources + +- [thomasekdahlN/jeti: Jeti ECU telemetry Lua app](https://github.com/thomasekdahlN/jeti) +- [Xicoy telemetry adapter v5 user's guide (PDF)](https://www.xicoy.com/downloads/Telemet1_1_en.pdf) +- [Xicoy telemetry adapter product page](https://www.xicoy.com/catalog/product_info.php?products_id=362) +- [VSpeak ECU converter for Swiwin](https://www.vspeak-modell.de/en/ecu-converter/swiwin) +- [VSpeak ECU converter for JetCat (RPM Jets)](https://rpmjets.com/products/ecu-converter-jetcat) +- [JetCentral Jeti telemetry V2 Lua](https://jetcentral.com.mx/telemetry/) +- [KingTech telemetry unit G2–G5 (Pacific RC Jets)](https://pacificrcjets.com/products/kingtech-telemetry-unit) +- [Swiwin + Jeti + VSpeak (RCU Forums)](https://www.rcuniverse.com/forum/rc-jets-120/11710796-swiwin-jeti-v-speak-=-loosing-my-mind.html) +- [Swiwin turbine telemetry adapter from VSpeak (RCU Forums)](https://www.rcuniverse.com/forum/rc-jets-120/11684464-swiwin-turbine-telemetry-adapter-vspeak.html) +- [Enjet Power E100 (Helidirect)](https://www.helidirect.com/products/enjet-power-e100-turbine) diff --git a/specs/003-flameout-alarm/spec.md b/specs/003-flameout-alarm/spec.md index 3ec1bef..2ed93da 100644 --- a/specs/003-flameout-alarm/spec.md +++ b/specs/003-flameout-alarm/spec.md @@ -105,12 +105,17 @@ threshold < idle RPM < start overshoot. transmitter, and is the layout for single size (User Story 7, FR-031–FR-031c). - Q: If telemetry is lost while the flameout alarm is already sounding, - what should the alarm do? → A: Keep going. No "telemetry lost" callout - interrupts it; the window shows FLAMEOUT with a "no telemetry" note. It - still stops only on Cut or a relight seen in valid data (FR-020a). + what should the alarm do? → A: Keep going; the window shows FLAMEOUT with + a "no telemetry" note. It still stops only on Cut or a relight seen in + valid data (FR-020a). Revised the same day: once the loss passes the + telemetry-loss delay, the "telemetry lost" callout is spoken once between + alarm cycles, so the pilot also knows the data is gone. - Q: How long must the RPM sensor go without valid data before the app treats it as telemetry lost? → A: Adjustable in settings as the telemetry-loss delay, default 2 s (FR-008, FR-020). +- Q: How are idle and max RPM entered, given the transmitter's number entry + stops at 32,767? → A: To the nearest 100 RPM, shown in thousands with one + decimal ("35.0" = 35,000 RPM) (FR-006). - Q: How should the app handle ECUs that send RPM in a scaled unit, such as thousands of RPM? → A: Plain RPM by default, with a manual sensor scale setting (×1, ×10, ×100, ×1000, default ×1). No automatic unit conversion @@ -328,9 +333,10 @@ the window shows NO TELEMETRY. 3. **Given** NO TELEMETRY while Armed, **When** valid data returns with RPM at or above the arming threshold, **Then** the app is Armed again with no alarm. -4. **Given** the flameout alarm is active, **When** RPM data stops being - valid, **Then** the alarm continues, no "telemetry lost" callout plays, - and the window shows FLAMEOUT with a "no telemetry" note. +4. **Given** the flameout alarm is active, **When** RPM data has been + invalid for the telemetry-loss delay, **Then** the alarm continues, the + "telemetry lost" callout is spoken once between alarm cycles, and the + window shows FLAMEOUT with a "no telemetry" note. --- @@ -459,8 +465,9 @@ transmitter window. - **Model copied from another model.** The copied sensor assignment may not exist on the new model's receiver; handled as above. - **Telemetry lost during the flameout alarm.** The engine is most likely - still out, so the alarm keeps going; only Cut or a relight seen in valid - data stops it. + still out, so the alarm keeps going; once the loss passes the + telemetry-loss delay, "telemetry lost" is spoken once between alarm + cycles. Only Cut or a relight seen in valid data stops the alarm. - **Thresholds set nonsensically** (flameout threshold at or above the arming threshold, or arming threshold at or above idle RPM). Setup blocks it and explains. @@ -486,17 +493,22 @@ transmitter window. screen MUST say which is missing when the user tries. - **FR-004**: When the model has no telemetry sensors listed, the settings screen MUST explain that the ECU must send RPM telemetry and that telemetry - must have been received at least once. + must have been received at least once, and that some ECU adapters take up + to a minute after power-up before their sensors appear. - **FR-005**: Users MUST be able to select the Cut switch and its Cut position. A Cut switch is required for monitoring (FR-003). - **FR-006**: Users MUST set idle RPM by typing in the idle specified in the ECU setup. It has no default. There is no automatic or learned idle in this - release. + release. Idle RPM and max RPM (FR-031b) are entered to the nearest 100 RPM + and shown in thousands with one decimal, e.g. "35.0" for 35,000 RPM and + "152.0" for 152,000 RPM, because the transmitter's number entry can't hold + values above 32,767 (research R1). - **FR-006a**: Users MUST be able to set a sensor scale of ×1, ×10, ×100 or ×1000, default ×1 (the sensor sends plain RPM). The app multiplies the sensor's value by the scale before every use: display, arming and - detection. Idle RPM and max RPM are always typed in plain RPM. The app - does not convert units automatically. + detection. Idle RPM and max RPM are always entered as true engine RPM + (FR-006), whatever the sensor scale. The app does not convert units + automatically. - **FR-007**: Users MUST be able to set the arming threshold and the flameout threshold as editable percentages of idle RPM, defaulting to 90% and 70% (see Assumptions). Settings MUST show each one's RPM value next to @@ -549,11 +561,12 @@ transmitter window. telemetry-loss delay while Armed, the app MUST give a "telemetry lost" warning that is clearly different from the flameout alarm, and MUST show NO TELEMETRY. Telemetry loss MUST never trigger the flameout alarm. -- **FR-020a**: When the RPM sensor stops reporting valid data while the - flameout alarm is active, the alarm MUST continue unchanged, with no - "telemetry lost" callout, and the window MUST show FLAMEOUT with a "no - telemetry" note. The alarm still stops only on Cut, or on a relight seen - in valid data (FR-018). +- **FR-020a**: When the RPM sensor has reported no valid data for the + telemetry-loss delay while the flameout alarm is active, the alarm MUST + continue, the "telemetry lost" callout MUST be spoken once between two + alarm cycles (without stopping or restarting the alarm), and the window + MUST show FLAMEOUT with a "no telemetry" note. The alarm still stops only + on Cut, or on a relight seen in valid data (FR-018). - **FR-021**: When valid data returns, the app MUST resume from the RPM it then sees: Armed if RPM is at or above the arming threshold, otherwise following the normal detection rules (the detection delay restarts). @@ -637,8 +650,10 @@ transmitter window. negligible. - **FR-035**: The README MUST state that the app is advisory, does not replace the ECU's failsafe, shutdown or auto-restart logic nor the pilot's own - monitoring, and only works on turbines that send RPM telemetry. It MUST - also explain: entering idle RPM from the ECU setup; the start overshoot and + monitoring, and only works on turbines whose RPM reaches the transmitter + as a normal telemetry sensor. It MUST list what is known per ECU brand + (research R1), including that RPM visible only in a JetiBox screen or + inside another vendor's app can't be used. It MUST also explain: entering idle RPM from the ECU setup; the start overshoot and why the arming threshold sits below idle; the arming threshold having to stay above any RPM the engine reaches before it runs on its own; and the effects of an idle typed too high or too low. @@ -720,6 +735,12 @@ transmitter window. "learn idle" helper or an idle derived automatically each session is a possible later extension; either would need to wait for the overshoot to decay before recording. +- **ECU telemetry** (research R1): JetCat, Xicoy, KingTech and Swiwin (via + a VSpeak converter) deliver RPM as a plain RPM value on a standard + telemetry sensor, so the default ×1 scale fits. JetCentral's adapter V2, + Enjet, and Swiwin's direct connection are unconfirmed and are checked in + testing. ECU status sensors exist for some brands but not all, so v1 uses + RPM only. - **Bar full scale** defaults to 4 × idle RPM when max RPM isn't entered. Typical model turbines reach roughly 3–4.5 times their idle speed at full throttle, so @@ -737,7 +758,8 @@ transmitter window. detection and ECU status sensors are possible later extensions (draft open questions 3 and 5). - **"Telemetry lost" warning** is a short spoken "Engine telemetry lost" in - the app voice, given once per loss event, with NO TELEMETRY shown until data + the app voice, given once per loss event (once the telemetry-loss delay + has passed, whether Armed or alarming), with NO TELEMETRY shown until data returns. It does not repeat, because the transmitter already announces signal loss itself. - **One engine per model** in v1. Two-engine models are out of scope; a later From e4dcff042192b20bf487a3f8e79527fad23bf0df Mon Sep 17 00:00:00 2001 From: Aaron George Date: Sun, 4 Oct 2026 21:32:27 -0600 Subject: [PATCH 05/22] Spec 003: implementation plan for Flameout Alarm - plan.md: technical context, constitution check (pass), layout and build order. One app file, no lib modules. - research.md R2-R13: 5-second cycle WAVs played with AUDIO_IMMEDIATE, stopPlayback on relight/Cut, telemetry-lost cycle variant, sound generator without ffmpeg, fallback beeps, vibration, 100 ms state machine, sensor reading, settings entry, sweep drawing cost, emulator and SIM testing, test alarm switch. - data-model.md, contracts (settings form, telemetry window, audio) and quickstart.md. - Spec: test alarm switch (FR-030a, User Story 8); FR-028 corrected to Speed Gauge's current rule (voice files committed, CC BY-SA 4.0). Co-Authored-By: Claude Opus 5.5 --- .../contracts/audio-events.md | 51 +++ .../contracts/settings-form.md | 51 +++ .../contracts/telemetry-window.md | 75 +++++ specs/003-flameout-alarm/data-model.md | 131 ++++++++ specs/003-flameout-alarm/plan.md | 178 ++++++++++ specs/003-flameout-alarm/quickstart.md | 88 +++++ specs/003-flameout-alarm/research.md | 318 +++++++++++++++++- specs/003-flameout-alarm/spec.md | 73 +++- 8 files changed, 954 insertions(+), 11 deletions(-) create mode 100644 specs/003-flameout-alarm/contracts/audio-events.md create mode 100644 specs/003-flameout-alarm/contracts/settings-form.md create mode 100644 specs/003-flameout-alarm/contracts/telemetry-window.md create mode 100644 specs/003-flameout-alarm/data-model.md create mode 100644 specs/003-flameout-alarm/plan.md create mode 100644 specs/003-flameout-alarm/quickstart.md diff --git a/specs/003-flameout-alarm/contracts/audio-events.md b/specs/003-flameout-alarm/contracts/audio-events.md new file mode 100644 index 0000000..7b9030e --- /dev/null +++ b/specs/003-flameout-alarm/contracts/audio-events.md @@ -0,0 +1,51 @@ +# Contract: Audio and vibration + +All sounds are in `src/Apps/AG-FlmOt/`, generated by +`tools/voice/make_flameout.py` and committed under CC BY-SA 4.0 with the +folder's `CREDITS.txt` (spec FR-026–FR-028, research R5). `A` = +`/Apps/AG-FlmOt/`. `I` = `AUDIO_IMMEDIATE`, `Q` = `AUDIO_QUEUE`. + +| File | Content | Length | +| --- | --- | --- | +| `cycle.wav` | urgent "Flameout!" ×3, then lock tone | 5.000 s exactly | +| `cycletl.wav` | urgent "Flameout!" ×3, "Engine telemetry lost", lock tone | 5.000 s exactly | +| `tlost.wav` | "Engine telemetry lost" | ≈ 1.3 s | +| `armed.wav` | "Flameout alarm armed" | ≈ 1.3 s | +| `relit.wav` | "Engine relit" | ≈ 1 s | + +Mono, 16-bit, 22.05 kHz, peak −1 dBFS. + +## Events + +| Event | Trigger (data-model.md) | Files present | Cycle files missing (R6) | +| --- | --- | --- | --- | +| Alarm start | ARMED → FLAMEOUT | `playFile(A.."cycle.wav", I)`; `vibration(false, 4)`, `vibration(true, 4)` | `playBeep(9, 1800, 120)` now and 2.5 s later; vibration | +| Alarm cycle | FLAMEOUT, 5 s after the previous cycle start | as alarm start, or `cycletl.wav` once if `tlPending` | as above | +| Alarm stop: relight | FLAMEOUT → ARMED | `stopPlayback(AUDIO_IMMEDIATE)`; then `playFile(A.."relit.wav", Q)` if `sayRel` | `stopPlayback(AUDIO_IMMEDIATE)` | +| Alarm stop: Cut | any → DISARMED by Cut while FLAMEOUT | `stopPlayback(AUDIO_IMMEDIATE)` | same | +| Alarm stop: monitoring off | settings change sets `active` false while FLAMEOUT | `stopPlayback(AUDIO_IMMEDIATE)` | same | +| Telemetry lost | ARMED, invalid for `lossMs` | `playFile(A.."tlost.wav", I)` | skipped (screen shows NO TELEMETRY) | +| Telemetry lost | FLAMEOUT, invalid for `lossMs` | next cycle plays `cycletl.wav` (R4) | beeps continue | +| Armed | DISARMED → ARMED, `sayArm = 1` | `playFile(A.."armed.wav", Q)` | skipped | +| Test start | test switch off → on, state OFF or DISARMED (R13) | as alarm start (`cycle.wav`) | as alarm start | +| Test cycle | test on, 5 s after the previous cycle start | as alarm cycle (`cycle.wav` only) | as alarm cycle | +| Test stop | test switch off, or the app arms | `stopPlayback(AUDIO_IMMEDIATE)` | same | + +Rules: + +- **Nothing sounds while OFF or DISARMED** (FR-012, SC-009) except a test + alarm the pilot starts with the test switch (FR-030a). +- Cycle start times are `cycleAt + 5000·k`, checked on the 100 ms tick + (SC-012: ±0.3 s). If the app fell more than one cycle behind (it never + should), it restarts the rhythm from `now`. +- **Pre-emption** (FR-025): `AUDIO_IMMEDIATE` stops any foreground playback, + including other apps' speech. Another app's `AUDIO_IMMEDIATE` (e.g. Speed + Gauge's stall warning) or a transmitter alarm can cut a cycle short; the + alarm resumes at the next 5-second boundary (R3). +- File existence is checked once in `init()` (five `io.open` calls), never + in `loop()`. +- The confirmations use `AUDIO_QUEUE` so they never cut another app's + callout; the alarm and "telemetry lost" use `AUDIO_IMMEDIATE`. +- **Unverified until transmitter testing:** the gap between `playBeep` + repeats; that `stopPlayback(AUDIO_IMMEDIATE)` silences mid-file with no + tail; the firmware's own telemetry "valid" timeout (R9). diff --git a/specs/003-flameout-alarm/contracts/settings-form.md b/specs/003-flameout-alarm/contracts/settings-form.md new file mode 100644 index 0000000..07da71a --- /dev/null +++ b/specs/003-flameout-alarm/contracts/settings-form.md @@ -0,0 +1,51 @@ +# Contract: Settings form + +One form, registered under the Applications menu as "Flameout Alarm" +(`system.registerForm(1, MENU_APPS, ...)` with a close callback). Keys and +ranges: [data-model.md](../data-model.md). Decisions: research R9, R10. + +Rows, top to bottom (`hint` = one `FONT_MINI` line, shown only when stated): + +| # | Row | Component | Behavior | +| --- | --- | --- | --- | +| 1 | **Flameout Alarm** | heading | | +| 2 | Monitoring | checkbox `en` | Refused unless sensor, Cut switch and idle RPM are set: box unticks and row 3 names what's missing (FR-003) | +| 3 | hint | label | "Needs: RPM sensor, Cut switch, idle RPM" (only the missing items); hidden when complete | +| 4 | **Engine** | heading | | +| 5 | RPM sensor | selectbox | "(none)" + every sensor except date/time and GPS, as "Device / Label"; a saved sensor that isn't present shows "