Skip to content

Flex network: auto-discover & connect (no IP typing)#328

Closed
patrickrb wants to merge 4 commits into
devfrom
feat/flex-discovery
Closed

Flex network: auto-discover & connect (no IP typing)#328
patrickrb wants to merge 4 commits into
devfrom
feat/flex-discovery

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Problem

Picking Network + FlexRadio dropped you on a manual IP Address field. Discovery already existed — FlexRadioFactory listens for the Flex UDP discovery broadcast (port 4992) and the picker rendered any radios it found — 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 the app never took one (nor declared CHANGE_WIFI_MULTICAST_STATE, which is required to acquire it). The discovery socket was bound and listening, but the OS delivered nothing to it.

Fix

  • Add CHANGE_WIFI_MULTICAST_STATE and acquire a MulticastLock while the Flex picker is open (released on dismiss → no battery cost otherwise). This is the load-bearing change that makes discovery actually receive the broadcasts.
  • Lead with discovery: the picker now shows a "Searching for FlexRadio…" state, lists found radios as the primary action, and tucks manual IP entry behind an "Enter IP manually" link as a fallback (different subnet / VPN / broadcast blocked).
  • Auto-connect when discovery finds exactly one radio and you haven't started typing — connect to your Flex with zero input. The decision (shouldAutoConnectFlex) is a pure, one-shot, unit-tested helper.

Testing

  • FlexDiscoveryLogicTest — auto-connect fires only on a single found radio, not on zero / multiple / mid-typing / already-fired. Green.
  • installDebug on the Pixel 8 — builds (incl. NDK) and installs clean.
  • ⚠️ Not yet verified against a live Flex on the LAN — needs the phone unlocked on the same network as the radio. The MulticastLock omission is a well-known Android discovery gotcha and the fix is standard, but the end-to-end "radio appears and auto-connects" path is worth a real check.

🤖 Generated with Claude Code

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>
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 10.18519% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.53%. Comparing base (bc6c21e) to head (89f64a6).

Files with missing lines Patch % Lines
...adio/ks3ckc/ft8af/ui/settings/ConnectionDialogs.kt 0.00% 81 Missing ⚠️
...app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt 0.00% 16 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #328      +/-   ##
============================================
+ Coverage     11.51%   11.53%   +0.02%     
  Complexity      113      113              
============================================
  Files            88       89       +1     
  Lines         12466    12537      +71     
  Branches       2230     2245      +15     
============================================
+ Hits           1435     1446      +11     
- Misses        10900    10960      +60     
  Partials        131      131              
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%> (ø)
...app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt 0.56% <0.00%> (-0.03%) ⬇️
...adio/ks3ckc/ft8af/ui/settings/ConnectionDialogs.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 3 commits June 23, 2026 14:29
…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>
@patrickrb

Copy link
Copy Markdown
Owner Author

Folded into #327 (single combined PR), as requested. The flex-discovery branch was merged into feat/meters-hud.

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