Skip to content

FlexRadio: meters HUD, network auto-discovery & one-tap connect#327

Closed
patrickrb wants to merge 9 commits into
devfrom
feat/meters-hud
Closed

FlexRadio: meters HUD, network auto-discovery & one-tap connect#327
patrickrb wants to merge 9 commits into
devfrom
feat/meters-hud

Conversation

@patrickrb

@patrickrb patrickrb commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Combines the meters HUD and the FlexRadio connection work into one branch (supersedes #328).

1. Meters HUD (pull-down)

A pull-down HUD opened by a visible "METERS ▾" tab at the top center (tap or short drag — the old invisible top-edge swipe fought Android's notification-shade gesture and was undiscoverable).

  • Adapts per rig: shows the meters the connected rig actually reports — universally ALC + SWR (serial rigs, via MeterProtectionController), plus power / S-meter / voltage / PA-temp on rigs that stream them (FlexRadio/Xiegu over the network).
  • Pure per-source converters normalize each rig's native units into a common bar + readout + zone; Settings → Meters toggles each meter (SWR + ALC + PWR on by default).
  • Live during TX, "LAST TX" otherwise.
  • In-HUD TX-power slider for FlexRadio (0–100 W, pushed to the rig + persisted), since the old Flex power control was unreachable in the Compose UI.

2. FlexRadio network connection

  • Auto-discovery actually works: added CHANGE_WIFI_MULTICAST_STATE + a MulticastLock while the picker is open — Android was silently dropping the discovery broadcasts without it. The picker leads with discovery ("Searching…"), and auto-connects when one radio is found (gated so it never fires on a stale cached entry or over a live session); manual IP is a fallback.
  • One-tap connect from the CAT chip: the Flex address is now persisted, so tapping the CAT chip near CQ reconnects to the saved rig on a cold start instead of no-opping.
  • Connection feedback: the Flex path now reports success, so the CAT chip turns connecting→connected and a "connected" toast fires (it never did before).

Testing

  • Unit tests: meter converters + enabled/available filter, clampTxPowerWatts, shouldAutoConnectFlex, mergeDiscovered, catTapAction. All green.
  • On device (Pixel 8): builds + installs; verified the METERS tab is visible and opens the sheet; verified (via debug log) discovery finds a real FLEX-6400 and the connection streams meters.

🤖 Generated with Claude Code

A top-anchored HUD, opened by a swipe-down from the top edge anywhere in
the app, showing the two meters every supported CAT rig reports back over
the existing link: ALC and SWR. These are only measurable while keyed, so
the readout is live during TX and labelled "LAST TX" otherwise (and "no
data" until the rig has ever reported, distinguished from a legitimate 0
reading via a new meterDataReceived flag on MeterProtectionController).

Reuses the existing meter plumbing: MetersSheet observes lastAlc/lastSwr
and reuses normalizedSwrToRatio for the readout; the ALC target window and
SWR halt threshold from GeneralVariables drive the green/amber/red zones so
the HUD and the protection controller agree on what "good" means.

Decision/geometry logic (bar fraction, ALC/SWR zones, freshness, edge-open
commit rule) is extracted into pure functions in MetersDisplay.kt and unit
tested; the top-sheet scaffold mirrors FT8AFBottomSheet (slide from top,
drag-up/scrim/Back to dismiss) without touching the tested bottom sheet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 16.01363% with 493 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.74%. Comparing base (bc6c21e) to head (d9e8b87).

Files with missing lines Patch % Lines
...in/radio/ks3ckc/ft8af/ui/components/MetersSheet.kt 0.00% 252 Missing ⚠️
...adio/ks3ckc/ft8af/ui/settings/ConnectionDialogs.kt 0.00% 81 Missing ⚠️
...n/radio/ks3ckc/ft8af/ui/settings/MetersSettings.kt 0.00% 70 Missing ⚠️
...n/radio/ks3ckc/ft8af/ui/components/MetersSource.kt 0.00% 45 Missing ⚠️
...app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt 0.00% 26 Missing ⚠️
.../radio/ks3ckc/ft8af/ui/components/MetersDisplay.kt 88.29% 1 Missing and 10 partials ⚠️
...n/radio/ks3ckc/ft8af/ui/settings/SettingsScreen.kt 0.00% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #327      +/-   ##
============================================
+ Coverage     11.51%   11.74%   +0.23%     
  Complexity      113      113              
============================================
  Files            88       93       +5     
  Lines         12466    13016     +550     
  Branches       2230     2344     +114     
============================================
+ Hits           1435     1529      +94     
- Misses        10900    11346     +446     
- Partials        131      141      +10     
Files with missing lines Coverage Δ
.../radio/ks3ckc/ft8af/ui/components/CatStatusChip.kt 40.29% <100.00%> (+4.81%) ⬆️
...dio/ks3ckc/ft8af/ui/settings/FlexDiscoveryLogic.kt 100.00% <100.00%> (ø)
...n/radio/ks3ckc/ft8af/ui/settings/SettingsScreen.kt 1.24% <0.00%> (-0.05%) ⬇️
.../radio/ks3ckc/ft8af/ui/components/MetersDisplay.kt 88.29% <88.29%> (ø)
...app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt 0.54% <0.00%> (-0.05%) ⬇️
...n/radio/ks3ckc/ft8af/ui/components/MetersSource.kt 0.00% <0.00%> (ø)
...n/radio/ks3ckc/ft8af/ui/settings/MetersSettings.kt 0.00% <0.00%> (ø)
...adio/ks3ckc/ft8af/ui/settings/ConnectionDialogs.kt 0.00% <0.00%> (ø)
...in/radio/ks3ckc/ft8af/ui/components/MetersSheet.kt 0.00% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

patrickrb and others added 8 commits June 23, 2026 13:33
The HUD only read MeterProtectionController.lastAlc/lastSwr, which is fed
solely by the serial-CAT meter path (Yaesu RM4/RM6, Kenwood, Icom, serial
Xiegu). On a network rig the HUD sat blank: a FlexRadio streams its meters
over UDP into FlexConnector.mutableMeterList, and Xiegu network into
X6100Radio.mutableMeters — neither ever reached that controller.

Make the HUD source-agnostic and adaptive:

- rememberRigMeterSamples observes whichever stream the connected rig
  produces — Flex (SWR ratio, ALC dB, power W, S-meter dBm, PA temp),
  Xiegu (SWR, ALC, power, S-meter, voltage), or the serial controller
  (ALC, SWR) — and returns the rig's available meters.
- Pure per-source converters (MetersDisplay.kt) normalize each rig's
  native units into a common MeterSample (bar fraction + readout + zone),
  so the HUD renders uniformly. Flex ALC is dB (low = good) vs the serial
  0-255 window; SWR converts from ratio or the normalized scale.
- A configurable meter set: Settings → Meters toggles each meter (SWR +
  ALC on by default, power/S-meter/voltage/temp off). The HUD shows the
  intersection of enabled and rig-available meters ("adapt per rig"), so
  an enabled meter the rig doesn't report is dropped, not shown empty.

All converters + the enabled/available filter are unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Network rigs set TX power via flexMaxRfPower (default 10 W), pushed to the
rig at connect — but the only screen that ever showed or changed it
(FlexRadioInfoFragment's seekbar) is part of the legacy Java UI the Compose
app no longer reaches, so there was no way to see or adjust it.

Add an adjustable TX-power row at the top of the meters HUD, shown when the
connected rig supports it (FlexRadio; Xiegu uses a different scale and is
left for later). The label tracks the slider live; the value is pushed to
the radio (FlexConnector.setMaxRfPower → RFPOWER) and persisted to config on
release, not on every drag tick, to avoid spamming the network.

Default the PWR (output watts) meter on, so set-power and live output read
together on the rigs that report power; serial rigs don't report it, so
"adapt per rig" still hides it there. The 0..100 W clamp is a pure,
unit-tested helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selecting Network + FlexRadio dropped the user on a manual "IP Address"
field. Discovery code existed (FlexRadioFactory listens for the Flex UDP
broadcast on 4992) and the picker even rendered found radios — but the list
was always empty, so typing the IP was the only thing that worked.

Root cause: Android's Wi-Fi chip filters out incoming broadcast/multicast
packets unless an app holds a WifiManager.MulticastLock, and we never took
one (nor held the CHANGE_WIFI_MULTICAST_STATE permission needed for it). So
the discovery socket received nothing.

- Add CHANGE_WIFI_MULTICAST_STATE and acquire a MulticastLock while the Flex
  picker is open (released on dismiss, so no battery cost otherwise).
- Lead the picker with discovery: a "Searching…" state, the found radios as
  the primary list, and manual IP entry collapsed behind a link as a fallback
  (different subnet / VPN / broadcast blocked).
- Auto-connect when discovery finds exactly one radio and the user hasn't
  started typing — connect to your Flex with zero input. The decision
  (shouldAutoConnectFlex) is a pure, unit-tested one-shot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…session

Field report: the picker "closes the instant you open it" and the rig stops
connecting. Cause: FlexRadioFactory is a singleton, so once a radio is cached,
reopening the picker seeds the list with it and the auto-connect LaunchedEffect
fired immediately — dismissing the dialog and, worse, reconnecting an
already-connected rig, which tears down the working session.

Gate auto-connect so it only fires on a genuinely fresh discovery:
- startedEmpty: the picker must have opened with no cached radios, so we never
  auto-connect to a stale singleton entry.
- rigAlreadyConnected: never reconnect over a live session.
(plus the existing single-radio / not-typing / one-shot guards.)

Also add debug.log diagnostics (multicastLock.held, seeded count, each radio
added, and connect path) so we can confirm whether discovery actually receives
the UDP broadcasts on the user's network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"If my Flex settings are set, I should just click the CAT button near CQ and
have it connect." Two gaps stopped that: the Flex address was never persisted,
and reconnectRig() no-ops when no connector exists yet (a cold start) — so the
CAT chip did nothing until you'd already connected once via the picker.

- Persist the Flex address (flexLastIp) whenever we connect — discovered, tapped,
  or typed — and load it at startup.
- Route the CAT chip tap (catTapAction): reconnect an existing connector if one's
  live; otherwise, on a cold start with a saved network Flex, connect straight to
  the remembered address; with nothing saved, point the user to setup.

Pure routing (catTapAction) is unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Field test on a real FLEX-6400 exposed two bugs behind "it never found the
radio / didn't connect until I hit CAT":

1. Discovery worked (MulticastLock fine — debug.log showed the radio found in
   0.4s) but the picker kept spinning. FlexRadioFactory fires OnFlexRadioAdded
   BEFORE appending to its list, so the listener re-read an empty list and
   dropped the radio (logged "total=0"). It also meant the discoveredRadios
   count stayed 0, so auto-connect never fired. Now we include the radio handed
   to the callback (mergeDiscovered, deduped by address) — so it shows and
   auto-connect can fire.

2. The Flex connect never reported success: FlexConnector.onConnectSuccess
   didn't notify the rig state, so onConnected() (which sets CONNECTED + toasts)
   never ran — the CAT chip stayed idle and there was no confirmation. Added an
   OnConnectionResult callback; connectFlexRadioRig now sets CONNECTING up front
   and CONNECTED + a "connected" toast on success (ERROR on failure).

mergeDiscovered is pure + unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The open gesture was an invisible 24dp strip at the very top — undiscoverable,
and it fought Android's notification-shade gesture, which owns the screen's top
edge, so the swipe often just opened the system shade ("can't get it to slide
down").

Replace it with a small visible "METERS ▾" tab centered at the top, below the
status bar (statusBarsPadding, so the system doesn't steal the gesture). It
opens the HUD on a tap or a short downward drag — whichever the user reaches
for. Verified on device: tab is visible and opens the sheet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@patrickrb patrickrb changed the title Pull-down meters HUD (ALC/SWR) from top edge FlexRadio: meters HUD, network auto-discovery & one-tap connect Jun 23, 2026
@patrickrb

Copy link
Copy Markdown
Owner Author

Consolidated into #329 (single combined PR) as requested.

@patrickrb patrickrb closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant