diff --git a/CLAUDE.md b/CLAUDE.md index 44044ac..df94b99 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,7 @@ other work. Remove an item once he says it's done. | App (menu name) | Script | Spec | Based on | | --- | --- | --- | --- | | Speed Gauge | `src/Apps/AG-SpdGa.lua` | `specs/001-speed-gauge/` | DFM Speed Announcer (Dave McQueeney, MIT) | +| Flameout Alarm | `src/Apps/AG-FlmOt.lua` | `specs/003-flameout-alarm/` | original | Keep this table current when an app is added. diff --git a/CREDITS.md b/CREDITS.md index 2d10f08..882d561 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -80,6 +80,29 @@ Amy model was trained on is undocumented. They were generated with [`tools/voice/`](tools/voice/README.md), which can regenerate them (the user chose to commit them, 2026-10-03; spec FR-035). +## Flameout Alarm sounds — Piper and the "Amy" voice + +Flameout Alarm (`AG-FlmOt`) is original work; no code comes from another +app. Its alarm sounds use the same **Piper** engine and **"Amy"** +(`en_US-amy-medium`) voice model by **Mycroft / Rhasspy** as Speed Gauge's +voice, credited above: + +- the urgent "Flameout!" callout, sped up, pitch-shifted, equalized and + compressed by [`tools/voice/make_flameout.py`](tools/voice/README.md); +- "Engine telemetry lost", "Flameout alarm armed" and "Engine relit". + +The lock tone is synthesized by the same script and contains no voice. + +The generated files are in +[`src/Apps/AG-FlmOt/`](src/Apps/AG-FlmOt/) and are licensed **CC BY-SA 4.0** +(Amy model), not MIT; that folder's `CREDITS.txt` carries the attribution. +No Piper code is part of this repository or of the app. + +The ECU telemetry facts behind the app (spec 003, research R1) come partly +from Thomas Ekdahl's Jeti ECU telemetry app +([thomasekdahlN/jeti](https://github.com/thomasekdahlN/jeti)), which was +studied for facts only; none of its code is used. + ## JETI model official demos [`docs/examples/jeti-demos/`](docs/examples/jeti-demos/) holds unmodified demos diff --git a/README.md b/README.md index e8a7504..7360419 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,74 @@ spec-driven workflow (GitHub Spec Kit + Claude Code) in VS Code. | App | Script | Status | Spec | Based on | | --- | --- | --- | --- | --- | | Speed Gauge | [`AG-SpdGa.lua`](src/Apps/AG-SpdGa.lua) | Released (0.3.0) | [001](specs/001-speed-gauge/spec.md) | DFM Speed Announcer by Dave McQueeney (MIT) | +| Flameout Alarm | [`AG-FlmOt.lua`](src/Apps/AG-FlmOt.lua) | Released (0.1.0) | [003](specs/003-flameout-alarm/spec.md) | Original | Speed Gauge speaks in its own voice (Piper "Amy"), in `src/Apps/AG-SpdGa/voice/`. Those files are licensed CC BY-SA 4.0, not MIT (see [CREDITS.md](CREDITS.md)); [`tools/voice/`](tools/voice/README.md) regenerates them. Without them the app works in the transmitter's voice. +### Flameout Alarm + +Watches a turbine's RPM telemetry and sounds an urgent, repeating alarm +("Flameout! Flameout! Flameout!" and a lock tone every 5 seconds, with stick +vibration) when the engine spools down after it has been running. A +double-size window shows RPM as a segmented bar with an idle marker. + +**It is advisory only.** It never controls the model or the engine. It does +not replace the ECU's own failsafe, shutdown or auto-restart logic, or the +pilot's own monitoring. + +**It only works if the turbine's RPM reaches the transmitter as a normal +telemetry sensor**, i.e. you can pick it in the transmitter's sensor list. +What is known per ECU brand ([research](specs/003-flameout-alarm/research.md#r1-how-ecus-deliver-rpm-to-a-jeti-transmitter)): + +| ECU | RPM as a telemetry sensor | +| --- | --- | +| JetCat | Yes, through JetCat's, VSpeak's, Digitech's or CB-Electroniks' Jeti converter | +| Xicoy (V6/V10, also JetsMunt) | Yes, through the Xicoy telemetry adapter (sensor group "Turbine") | +| KingTech | Yes, through the KingTech telemetry unit or a VSpeak/Digitech converter | +| Swiwin | Yes, through VSpeak's Swiwin converter. Direct connection: unconfirmed | +| JetCentral | Unconfirmed: the Telemetry Adapter V2 uses its own Lua app | +| Enjet Power | Unconfirmed | + +RPM shown only on a JetiBox screen, or only inside another maker's Lua app, +can't be used. Swiwin users: set the receiver's output period to 11-13 ms, +not "Auto" (a known Swiwin ECU issue, unrelated to this app). + +**Setup** (Applications → Flameout Alarm). Monitoring stays off until three +things are set: + +- **RPM sensor.** If the ECU sends RPM in units other than RPM (e.g. + thousands), set **Sensor scale** so **Live RPM** reads true engine RPM. +- **Idle RPM**, typed from the ECU's setup, in thousands (35.0 = 35,000 + RPM). Compare it with **Live RPM** at idle. Too high, and the app arms only + on a run-up and a relight won't clear the alarm until you open the + throttle. Too low, and the alarm comes later and a restart clears it + sooner. +- **Cut switch**, assigned in its Cut (engine off) position. Moving it to Cut + disarms the app silently and is the only way to silence an alarm. + +How it decides: + +- A turbine start overshoots idle and slowly settles back down, so the app + arms on an **arming threshold** below idle (default 90%) held for the + arming time (3 s). The arming threshold must stay above any RPM the engine + reaches before it runs on its own; raise the percentage if your engine's + start or auto-restart comes close to it. +- RPM below the **flameout threshold** (default 70% of idle) for the + detection delay (1 s) starts the alarm. A throttle chop to idle stays + above it. +- During an ECU auto-restart the alarm keeps going; it clears once RPM holds + the arming threshold for the arming time. +- If telemetry stops for the telemetry-loss delay (2 s), the app says "Engine + telemetry lost" instead of alarming. + +A **Test alarm** switch plays the real alarm on the ground (not while armed). +The sounds in `src/Apps/AG-FlmOt/` are generated with +[`tools/voice/make_flameout.py`](tools/voice/README.md) and licensed CC BY-SA +4.0, not MIT. Install = `AG-FlmOt.lua` plus the `AG-FlmOt/` folder. + ## Install with JETI Studio 1. In JETI Studio open **File → Configuration**, and add this line to the @@ -27,7 +89,9 @@ regenerates them. Without them the app works in the transmitter's voice. Studio's Lua app list; select one and install it. Each app installs exactly its released version (its files come from the -release tag). Descriptions: [Speed Gauge](docs/apps/speed-gauge.md). +release tag). Descriptions: [Speed Gauge](docs/apps/speed-gauge.md), +[Flameout Alarm](docs/apps/flameout-alarm.md) (in JETI Studio once its +`Apps.json` entry is published). For maintainers: after tagging a release, run `python tools/publish/make_apps_json.py`, commit `Apps.json` and merge it to diff --git a/docs/apps/flameout-alarm.html b/docs/apps/flameout-alarm.html new file mode 100644 index 0000000..94be4f7 --- /dev/null +++ b/docs/apps/flameout-alarm.html @@ -0,0 +1,61 @@ + + + + + Flameout Alarm + + +

Flameout Alarm

+

An urgent, repeating alarm when a turbine flames out, from the + engine's RPM telemetry, and an RPM bar for the main screen, for the + DS-24 II / DC-24 II.

+ Flameout Alarm, armed + + Flameout Alarm during a flameout +

Advisory only. It never controls the model or the engine, + and does not replace the ECU's failsafe, shutdown or auto-restart + logic, or your own monitoring.

+

Needs a turbine whose RPM reaches the transmitter as a normal + telemetry sensor: JetCat, Xicoy and KingTech through their Jeti + telemetry adapters or a VSpeak/Digitech converter, Swiwin through + VSpeak's converter. JetCentral, Enjet and Swiwin's direct connection + are unconfirmed.

+

Setting up

+
    +
  1. Add Flameout Alarm in Applications - User Applications.
  2. +
  3. In its settings choose the RPM sensor (check Live + RPM; set Sensor scale if it's off by 10x or 1000x), + enter Idle RPM in thousands from your ECU's setup (35.0 = + 35,000 RPM), assign the Cut switch in its Cut position, then + tick Monitoring.
  4. +
  5. Put Flameout Alarm at double size on a page in + Timers/Sensors - Displayed telemetry.
  6. +
  7. Optional: assign a Test alarm switch to hear the + alarm.
  8. +
+

Defaults: arms at 90% of idle held for 3 s, alarms below 70% of + idle for 1 s, "telemetry lost" after 2 s.

+

Version: 0.1.0

+

Compatibility: DC/DS-24II, firmware 6.x

+

Voice: Piper text-to-speech with the "Amy" voice by Mycroft / + Rhasspy; the sound files are licensed CC BY-SA 4.0. Flameout Alarm: MIT + license, Copyright (c) 2026 Aaron George.

+

Source and full credits: + github.com/Flazzarious/jeti-lua-apps

+ + diff --git a/docs/apps/flameout-alarm.md b/docs/apps/flameout-alarm.md new file mode 100644 index 0000000..1f7908b --- /dev/null +++ b/docs/apps/flameout-alarm.md @@ -0,0 +1,73 @@ +# Flameout Alarm + +An urgent, repeating alarm when a turbine flames out, from the engine's RPM +telemetry, and an RPM bar for the main screen, for the JETI +**DS-24 II / DC-24 II**. + +![Flameout Alarm on the main screen, armed](https://raw.githubusercontent.com/Flazzarious/jeti-lua-apps/main/docs/apps/img/flameout-alarm-armed.png) + +## What it does + +- **Flameout alarm:** "Flameout! Flameout! Flameout!" followed by a rapid + lock tone, repeating every 5 seconds, with three short pulses on both + sticks. It cuts in over other apps' speech. +- **No false alarms on the ground:** the app arms only once the engine has + been running at idle (it ignores the start sequence and the start + overshoot), stays quiet through throttle chops, and disarms silently when + you move the throttle cut switch to Cut. +- **Auto-restart aware:** while the ECU tries a restart, the alarm keeps + going; it clears once the engine holds idle again. +- **Telemetry loss is not a flameout:** if the RPM data stops, you hear + "Engine telemetry lost" instead of the alarm. +- **RPM window:** a segmented RPM bar with a yellow idle marker and the RPM + as a number (double size), or a compact bar (single size). On a flameout + the window turns into a red FLAMEOUT banner. + + ![Flameout Alarm during a flameout](https://raw.githubusercontent.com/Flazzarious/jeti-lua-apps/main/docs/apps/img/flameout-alarm-flameout.png) +- **Test alarm switch:** hear the real alarm on the ground, without an + engine. + +**Advisory only.** The app never controls the model or the engine. It does +not replace the ECU's own failsafe, shutdown or auto-restart logic, or your +own monitoring. + +## Requirements + +- JETI **DS-24 II or DC-24 II**, firmware 6.0 or newer. On other DC/DS-24 + transmitters the alarm works and the window shows text only. +- A turbine whose **RPM reaches the transmitter as a normal telemetry + sensor**: JetCat, Xicoy and KingTech through their Jeti telemetry adapters + or a VSpeak/Digitech converter, Swiwin through VSpeak's converter. + JetCentral, Enjet and Swiwin's direct connection are unconfirmed. RPM shown + only on a JetiBox screen or inside another maker's app can't be used. + +## Setting up + +1. Add **Flameout Alarm** in *Applications → User Applications*. +2. Open its settings (*Applications → Flameout Alarm*). Monitoring stays off + until these three are set: + - **RPM sensor**: your ECU's RPM. **Live RPM** shows what the app reads; + if it's off by 10× or 1000×, set **Sensor scale**. + - **Idle RPM**, in thousands (35.0 = 35,000 RPM), from your ECU's setup. + Check it against Live RPM with the engine idling. + - **Cut switch**, assigned while holding the switch in its Cut (engine + off) position. + Then tick **Monitoring**. +3. Put **Flameout Alarm** at double size on a page in *Timers/Sensors → + Displayed telemetry*. +4. Optional: assign a **Test alarm** switch and hold it on to hear the alarm. + +The defaults suit most engines: arming at 90% of idle held for 3 s, alarm +below 70% of idle for 1 s, "telemetry lost" after 2 s. If your engine's start +or auto-restart climbs close to 90% of idle before it's running on its own, +raise the arming percentage. Settings are saved per model. + +## Credits and licenses + +- Voice: **Piper** text-to-speech with the voice model **"Amy"** by + Mycroft / Rhasspy. The sound files (in `/Apps/AG-FlmOt/`) are licensed + **CC BY-SA 4.0**. +- Flameout Alarm itself: MIT license, Copyright (c) 2026 Aaron George. + +Source, full credits and license texts: +[github.com/Flazzarious/jeti-lua-apps](https://github.com/Flazzarious/jeti-lua-apps) diff --git a/docs/apps/img/flameout-alarm-armed.png b/docs/apps/img/flameout-alarm-armed.png new file mode 100644 index 0000000..36b4bd5 Binary files /dev/null and b/docs/apps/img/flameout-alarm-armed.png differ diff --git a/docs/apps/img/flameout-alarm-disarmed.png b/docs/apps/img/flameout-alarm-disarmed.png new file mode 100644 index 0000000..2acd468 Binary files /dev/null and b/docs/apps/img/flameout-alarm-disarmed.png differ diff --git a/docs/apps/img/flameout-alarm-flameout.png b/docs/apps/img/flameout-alarm-flameout.png new file mode 100644 index 0000000..e6c8b2e Binary files /dev/null and b/docs/apps/img/flameout-alarm-flameout.png differ diff --git a/docs/apps/img/flameout-alarm-icon.png b/docs/apps/img/flameout-alarm-icon.png new file mode 100644 index 0000000..2f8b7a6 Binary files /dev/null and b/docs/apps/img/flameout-alarm-icon.png differ diff --git a/docs/apps/img/flameout-alarm-idle.png b/docs/apps/img/flameout-alarm-idle.png new file mode 100644 index 0000000..689aa5f Binary files /dev/null and b/docs/apps/img/flameout-alarm-idle.png differ 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`. diff --git a/docs/jeti-api-notes.md b/docs/jeti-api-notes.md index 7ee0cfb..a389d95 100644 --- a/docs/jeti-api-notes.md +++ b/docs/jeti-api-notes.md @@ -137,6 +137,10 @@ the emulator's numbers above: - To see live per-call numbers, call `system.getCPU()` at the end of `loop()` and of the print function and log them (Speed Gauge was measured this way: init 24, loop 0–1, small/double draw 12–18, full-screen draw 27–43). +- Flameout Alarm's double-size window (12 opaque renderer polygons, 12 + thin opaque outlines, three texts) measured **5–6%** per call on the + DS-24 II (2026-10-06, `DEBUG_CPU`): 5% armed, 6% with the FLAMEOUT + banner. Opaque fills and outlines are cheap, as below. - Cost follows semi-transparent anti-aliased drawing (renderer polylines with alpha, e.g. glow bands) far more than plain fills: removing a full-window `drawFilledRectangle` and the filled face polygon changed nothing, while @@ -159,6 +163,17 @@ the emulator's numbers above: - It also replaces `system.playFile`, `playNumber`, `playBeep`, `playSystemSound` and `vibration` with `print`: in the emulator, audio and vibration appear as lines in the Lua console instead of sound. + `system.stopPlayback` is **not** replaced, so a stop leaves no console + line; it shows only as the repeating lines ending (Flameout Alarm, + 2026-10-06). +- **`io.open` in the emulator needs a relative path** (JETI Studio 6.04, + 2026-10-05, Flameout Alarm): `io.open("/Apps/AG-FlmOt/cycle.wav", "r")` + returns nil, while `io.open("Apps/AG-FlmOt/cycle.wav", "r")` works ("r" + and "rb" alike). The transmitter opens the absolute path (Speed Gauge's + voice check passes there). A file-existence check should try the + absolute path, then the same path without its leading "/". Playback keeps + the absolute path. Speed Gauge 0.3.0's check doesn't do this yet, so in + the emulator it always falls back to DFM's sounds and `playNumber`. - **The emulator plays no Lua audio even without that app** (JETI Studio, firmware 6.04, observed 2026-10-03: `playFile` with the telemetry app removed was silent). Anything that must be heard, such as WAV formats and 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/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 "