Repository navigation
Merged
Conversation
iOS TX kept silent on air — the rig's Data-VOX never keyed and TUNE was silent at the radio too, even though RX decoded fine and the output route was the USB interface at full volume. The waveform (full-scale GFSK) and session routing were both correct; the audio simply never left the engine. Root cause: RX and TX share one AVAudioEngine that AudioCaptureService starts for *input* capture only. The first thing to touch mainMixerNode is the TX path's `engine.connect(playerNode, to: mainMixerNode)` in ensureConnected(). Forming that mixer -> output link on an already-running engine leaves the new output chain un-pulled by the render thread: the node keys without error but no samples reach the hardware. play() only (re)started the engine when it was stopped, and the capture engine is always running, so the freshly-formed output connection was never brought up rendering. (PR #836 fixed the earlier crash on this same path; this is the silent-TX state it left behind.) Fix: ensureConnected() now reports whether it actually (re)connected, and play() cycles the engine (stop -> start) once after a fresh connect so the whole graph — player -> mixer -> output — comes up rendering. Gated on an actual (re)connect via the pure, unit-tested TxConnectionState.mustCycleEngineAfterConnect(didConnect:), so steady-state transmit slots don't stutter the RX capture tap. This also covers the post-config-change reconnect (DigiRig replug), which returns through the same path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Wavelog user whose Station ID belongs to a different user than the API key passes Test Connection and then has every QSO rejected with HTTP 401 "station id does not belong to the API key owner." The app showed "0 accepted" and nothing else; the reason only lived in debug.log. - Catch-up sync dialog: show each service's rejection reason (JSON wrapper stripped) and, for Cloudlog-family servers, a hint naming the setting to fix (Station ID / API key). - Post-QSO upload and background auto-sync: toast the same reason + hint. - Cloudlog settings dialog: after Test Connection, warn when the entered Station ID is blank or not in the station list the server returned for this key. - debug.log: log the station_profile_id on every rejected upload. New pure helpers (CloudlogFailureHint.kt, StationIdCheck.kt) with unit tests; verified on the emulator against a mock Wavelog. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every PR that touches ft8af/** now gets an installable debug APK, uploaded as an artifact and linked from a sticky PR comment. The PR build in android.yml is an unsigned release APK, which phones refuse to install. Also adds a PR description template (APK link, manual test checklist, what changed, risks, automated tests) and branch-name and PR rules in AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rig catalog (rigaddress.txt) had no entry for the QMX at all, so it never showed up as a selectable rig. Its sibling QDX is listed using the generic Kenwood TS-590/480 command set, but that path sets MD2 (SSB-USB voice mode) rather than the DATA mode + TX-rise-threshold tuning the QMX specifically needs to transmit -- same bug danleetw/FT8TW's native app had to hand-patch around. FT8AF already vendors and statically compiles hamlib 4.7.2, which has a dedicated QRP Labs QMX backend (qrplabs_qmx_caps / RIG_MODEL_QRPLABS_QMX), exposed through the existing HamlibRig bridge -- the same mechanism already used for "Yaesu FT-891 (Hamlib)". This adds the matching catalog line: 0x809 = RIG_MAKE_MODEL(RIG_KENWOOD, 57), hamlib's real QMX model ID, at its minimum supported baud rate (9600). Per QRP Labs' own current QMX firmware guidance, the native hamlib definition is the recommended path over Kenwood emulation, which matches this change. Appended at the end of rigaddress.txt (after ICOM IC-7300MK2), per RigAddressAssetTest's documented append-only invariant -- the selected rig is persisted as a raw list index, so inserting anywhere else would silently reassign every existing user's selected rig on upgrade. Added a matching GOLDEN_ORDER entry, moved the "last entry" pin from the MK2 test to this one, and added a dedicated test (qmxHamlib_isListedWith HamlibModelId) mirroring the existing ic7300mk2_isListedWithIcomDefaults pattern. Verified: builds cleanly through resource merge, manifest processing, and Java/Kotlin compilation on this checkout; the new/changed test assertions were traced by hand against RigNameList's parser (confirmed "809" parses as hex -> 2057 = 0x809) since this environment can't execute them. Could not produce a final APK or run the JVM unit tests locally -- the AGP-bundled aapt2 for Linux is x86_64-only and this build machine is aarch64 (processDebugResources, a dependency of both assembleDebug and testDebugUnitTest, needs it to compile AAR resources). qemu-user-static emulation was attempted but blocked by an unrelated, unsafe apt dependency conflict in this machine's repo configuration (would have required upgrading libc6 system-wide, so it was not forced through). No QMX hardware was available to test TX/RX against either. All three are red flags a reviewer with access to any of them should check before merge -- CI (real x86_64 runners) is this PR's actual first verification. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Copilot review is enabled but can run out of credits. PRs now request Copilot first, fall back to Claude's code-review skill, address every finding before QA, and record which reviewer was used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From a Claude code-review pass (Copilot is out of credits): - Sign with a committed debug keystore so each new PR APK installs over the previous one instead of failing with a signature mismatch. - Check out the PR head SHA so the APK matches the commit it is named after. - Retry assembleDebug like android.yml, since Maven Central rate-limits runners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copying it to ~/.android/debug.keystore was ignored on the runner (the APK still carried a fresh runner key). Use AGP's injected signing properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a 'Save Input Volume per Band' toggle that mirrors the per-band TX output level from #355: the Input Volume set on a band is saved and restored on band change, with bands that have no saved value keeping the current level. Own toggle and config keys (perBandInputLevel / perBandInputLevels); reuses the shared per-band helpers, which now take a max level so the RX map can hold 0..200%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The alternates row laid chips out in a plain Row, so on narrow screens the last chip in a band got squeezed and its label wrapped one character per line. Use a FlowRow that fills the space after the band label so whole chips move to the next line, and keep each chip label on a single line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FTX-1 owners had to guess which Yaesu entry works. A user confirmed CAT works with the FT-891/991(USB) profile (38400 baud, 9-digit Yaesu gen-3 command set), so add a named "YAESU FTX-1(USB)" entry with that profile. Appended at the end of rigaddress.txt because the selected rig is saved as a list index; extend the golden-order test and pin the new entry's profile to the FT-891/991(USB) one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "N of M enabled" label was computed as total - excludedBands.size, but the persisted exclusion set can hold names that are no longer listed, so it read "1 of 20 enabled" with six bands on. The same size check guarded the last band, which blocked turning bands off while several were still on. - Count enabled bands against the listed names, and prune stale names from the exclusion set on toggle. - Keep rig-reported off-plan frequencies (appended by getIndexByFreq) out of the toggle list, and order the list by frequency. - Show a message when the user tries to hide the last enabled band. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto clear TX offset could pick offsets as low as 200 Hz. FT8 TX audio is not a pure sine, so at a low offset its 2nd and 3rd harmonics land inside the SSB passband and waste RF power as spurs. Add a "Lowest auto offset" setting (200-2000 Hz, 50 Hz steps, default 200 Hz so nothing changes until the operator raises it). It appears under the auto-clear toggle in Transmission settings and limits both the CQ-start pick and the mid-CQ relocation. Offsets the operator chose by hand are not touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Enabled Bands list is now sorted by frequency, so the old file-order expectation no longer holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ft8_snr() took the other 7 Costas tones of each sync symbol as the noise. With our 2-symbol Hann window and time_osr=4, neighbouring symbols and leakage put signal energy into those tones, so the noise estimate rose with the signal: reports were squeezed into about -14..-2 dB (a +20 dB station read -3). That value is also the report we transmit. Noise now comes from the spectral baseline around the candidate: the 20th percentile of the time-averaged linear power over +/-48 bins (+/-300 Hz). Signal is the mean linear power of the expected Costas tone. SNR = 10*log10((sig - noise) / noise) - 31.5 dB, clamped to [-24, +40]. The 31.5 dB constant is fitted against the testdata/ft8 jt9 truth. FT4/FT2 are unchanged. decode_bench now pairs each matched decode's ft8_snr() with jt9's SNR (keeping the highest SNR over duplicate decodes, as the app does) and --assert-snr fails the run outside testdata/ft8/snr_bounds.txt, which run_host_tests.sh/.ps1 now pass. Against jt9 (n=462): before: slope 0.19, r 0.48, MAE 8.10 dB, jt9>=+10 mean -6.3 after: slope 0.93, r 0.85, MAE 4.07 dB, jt9>=+10 mean +13.7 Recall and floors are unchanged (462/473). ft8_snr costs ~6.5 us per call on the host, about 1.7 ms per slot, which is within bench noise. Fixes #937 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups on #941: - ft8_snr: measure the noise percentile separately below and above the candidate (its own 8 tones excluded) and keep the louder side. When a rig's audio filter edge falls inside the +/-300 Hz window, the rolled-off bins dragged the single-window percentile down: a synthetic 15 dB roll-off read +24 instead of +10. Percentile 0.2 -> 0.1 per side (as WSJT-X's baseline uses), cal re-fitted 31.5 -> 30.5 dB. Corpus vs jt9: slope 1.00, r 0.89, MAE 3.87 dB, bias -0.08 (was 0.93/0.85/4.07/+0.18). - decode_bench: truth duplicates keep the highest SNR like the decoded side; a truth line without an SNR column still counts as truth (it is only left out of the SNR fit); --snr-table covers up to +49. - snr_bounds.txt: add bias_min/bias_max (+/-1.5 dB) so a calibration shift can't pass; tighten r_min 0.85, mae_max 4.5, strong mean >= +10. - Tests: filter edge on either side, top-edge clamp, too-narrow and out-of-range windows. - monitor.c: note that kMagToPower depends on its byte encoding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dence The maintainer tests on a device once per release batch (the staging Play internal build) instead of installing every PR's APK. Per-PR device testing is limited to PRs labeled needs-device-test (transmit/PTT, CAT, USB audio or serial, audio routing, permissions, signing/release config). QA posts emulator screenshots or a recording as evidence before handoff. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four androidTest flows run on the CI emulator so QA can check a PR without a phone (BUR-25): launch to the main screen, change band and see the new frequency, feed a recorded FT8 WAV through the decoder into the decode list, and a callsign surviving an activity restart. Each saves a screenshot; the instrumented job uploads them (plus logcat on failure) as `emulator-flows`. SlotTimerBar's endless per-frame loop now uses withInfiniteAnimationFrameMillis so Compose UI tests can reach idle (no change on a device). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `instrumented` to android-gate's needs so a failing emulator flow blocks the merge. The flows passed 5 runs in a row (run 39515242176, attempts 1-5). Also harden two flows: BandChangeFlowTest derives the expected dial from the band sheet rows for the current mode instead of hardcoding FT8 frequencies, and both band/setting cleanup steps are best-effort so a teardown throw can't mask the real failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude code-review on #942: the flows drive live app state (decode pipeline incl. PSKReporter/auto-sequencer, band, operator identity) and connectedAndroidTest uninstalls the app, so running them on a personal phone is harmful. Skip every flow unless on an emulator, fail CI if any flow's screenshot is missing (so a misfiring check can't pass as skipped), document emulator-only in AGENTS.md, and bound-check RIFF chunk sizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real-hardware testing on PR #925 found that FA; (frequency) commands from the Hamlib QMX backend never reach the radio after the initial rig_open() handshake, despite hamlib logging the write and timing out waiting for an ack. The sink callback that forwards hamlib's CAT bytes to the real serial connector silently returns when getConnector() is null, with no log anywhere -- making this exact failure mode undiagnosable from the app's own logs. This adds a temporary warning log so the next real-hardware test can confirm or rule out this path directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Ruled out the null-connector theory from the previous diagnostic commit: hamlib's ID; probe does reach the real serial port, but FA; (sent moments later in the same rig_open() call) never does, despite hamlib's own write_block() logging a successful write in both cases. CableSerialPort had zero logging on the receive path, so "the radio never replies" was never actually verified either. This adds: - CableSerialPort.onNewData: logs every byte actually read off the wire (serial.recv[...]), before any rig-specific listener sees it. - hamlib_jni.cpp's pump thread: logs exactly what it reads from hamlib's loopback socket on every read(), to confirm whether FA; ever reaches the pump thread at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
…hange Real-hardware testing on PR #925 found the QMX keeps falling back from Digital to USB mode under the Hamlib backend, even though setUsbModeToRig() correctly asks hamlib for PKTUSB. hamlib's QMX backend (qrplabs_qmx_caps) uses the generic kenwood_set_mode(), which only sends MD6; -- it has no knowledge of Q41;, the QRP Labs-specific follow-up this exact radio needs to actually commit to and stay in Digital mode. This is the same gap Dan Lee's native app previously had to hand-patch for the same radio (see project history). Testing also showed the mode reverts after any frequency change alone (FA...;), not just at mode-set time, so Q41; is re-sent after setFreqToRig() too, scoped to the QMX's model ID only so it never reaches any other Hamlib-backed rig (currently just the FT-891 entry). Not unit-testable: this path is gated behind ensureOpen(), which HamlibRigTest's own doc comment already notes always returns false in a JVM unit test (no native lib) -- same as every other hamlib call in this class. Needs real-hardware re-verification, same as the rest of this PR's hamlib-backed behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Real-hardware testing of the previous Q41; fix found the QMX still reverts from Digital to USB after a frequency change, despite setFreqToRig() now re-sending Q41;. rig_get_freq() sends the same FA; command as a query (no parameter) -- if that alone also triggers the reversion, and the app polls frequency periodically (CAT-liveness/dial sync), mode would keep reverting on every poll regardless of the set-path fix. Mirrors the same Q41; follow-up onto the read path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
… MD6; The Q41; fix (e046e0a, 3789712) was based on a wrong premise. Checked the official QMX CAT manual (qrp-labs.com/images/qmx/manuals/ cat_1_04_003.pdf): there is no Q41 command. The Q-series commands are Q0-QB; Q4 is "Get/Set TX Rise Threshold" (0-99%, default 80), so "Q41;" was actually setting TX Rise Threshold to 1% on every mode-set, freq-set, and freq-read -- an unrelated, actively harmful side effect, not a mode fix. Root cause, verified against hamlib's vendored source: the QMX backend (qrplabs_qmx_caps) sets no mode_table override, so kenwood_set_mode() falls back to the generic kenwood_mode_table, where RIG_MODE_PKTUSB is table index 13. kenwood_set_mode() encodes any index > 9 as a letter ('A' + index - 10), so PKTUSB becomes "MDD;" -- not a valid QMX mode character. The manual only documents single-digit MD values (1, 2, 3, 5, 6, 7, 8, 9), so "MDD;" is silently rejected; setUsbModeToRig() was never actually setting Digital mode via this path at all. Table index 6 holds RIG_MODE_RTTY (commented "// FSK Mode" in hamlib's own source) -- exactly the QMX manual's own mode 6, "FSK", its real digital mode. hamlib's string table maps "RTTY" to RIG_MODE_RTTY directly, so requesting that mode produces the valid "MD6;" through hamlib's normal, standard code path -- no raw command bypass needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Real-hardware testing keeps showing "hamlib model 9" open instead of
2057 when "Qrp Labs QMX (Hamlib)" is selected, even right after a
fresh install. Traced every writeConfig("civ", ...) call site and
found none of them apply to a Hamlib-backed rig selection -- the only
writers are the ICOM-gated repair path (repairCivAddressAgainstModel,
which explicitly returns early for any non-ICOM instructionSet) and
the manual hex-edit field. So the earlier civAddress/CivAddressConfig
byte-masking theory doesn't hold up on inspection and needs direct
verification rather than more static reading.
Logs the exact rigName/address applied in setAddrAndBauRate() (rig
selection) and the exact civAddress value used to construct HamlibRig
in connectRig(), to see empirically where 2057 becomes 9.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Root-caused via real hardware testing + diagnostic logging: selecting "Qrp Labs QMX (Hamlib)" always opened hamlib model 9 instead of 2057 after any app restart. RadioAudioSettings.kt (the active Compose rig picker) persists civAddress through CivAddressConfig.encode(), which masks with "& 0xFF" since a real ICOM CI-V address is always a byte -- 2057 (0x809) round-trips through that as 9. Confirmed on-device: connectRig() logged "constructing HamlibRig(civAddress=9)", and hamlib model 9 (SDR#) opened, sending its own unrelated handshake bytes that put the QMX into its firmware debug terminal menu instead of CAT mode. Fix: a new GeneralVariables.hamlibModelId field and "hamlibModel" config key, written as a plain unmasked decimal string and loaded the same way, used instead of civAddress to construct HamlibRig in MainViewModel.connectRig(). civAddress and its existing byte-range persistence/repair logic (issue #753) are untouched -- that contract is relied on elsewhere for genuine ICOM addresses and didn't need widening. Updated both the active picker (RadioAudioSettings.kt) and the legacy one (ConfigFragment.setAddrAndBauRate) to keep the new field in sync; added hydration tests covering the fix and a malformed value. This also explains several earlier rounds of real-hardware testing on this PR that looked like CAT/mode bugs in the Hamlib backend itself (including the reverted Q41; mistake) -- they were actually testing the wrong hamlib backend (SDR#) the whole time, not QMX at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
The previous fix (47c8fc4) left a real gap: an install that had already selected a Hamlib rig before "hamlibModel" existed loads hamlibModelId=0 on first launch after updating. Confirmed on real hardware: HamlibNative.nativeOpen(0, ...) -> hamlib's rig_init(0) fails, and ~0.5s later the whole process aborts with a native SIGABRT ("pthread_mutex_lock called on a destroyed mutex") -- not a graceful failure, a hard crash loop on every launch. Two fixes: 1. HamlibRig.ensureOpen() now refuses to call nativeOpen at all when modelId <= 0 -- a bad model ID can never reach native code again, regardless of how it got that way. 2. connectRig() self-heals a <= 0 hamlibModelId from the currently selected rig's own rigaddress.txt entry (by modelNo) and persists it, so an existing install recovers on its next connect instead of needing the user to manually re-select the rig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
Found via real-hardware testing: every single Hamlib CAT failure in this whole PR's testing reported "0 chars" from the radio, even when serial.recv showed real traffic arriving in the same window. Root cause: HamlibRig.onReceiveData() only forwards radio bytes into the native bridge when handle != 0, but the original single nativeOpen() call set `handle` only *after* it returned -- and nativeOpen() itself ran rig_open() internally, including its own synchronous ID;/FA; handshake, before returning. So every byte the radio sent back during that handshake was silently dropped on the Java side, and hamlib always timed out waiting for an ack that could never reach it, even against a radio that was genuinely replying. Split the single native call into nativeCreate() (builds the bridge, starts the pump thread, returns a handle -- does NOT call rig_open()) and nativeRigOpen(handle) (runs rig_open() on an already-created bridge). HamlibRig.ensureOpen() now publishes `handle` between the two calls, so nativeFeedFromRig can deliver the radio's handshake replies in real time, matching the threading model this file's own top comment already documented (the serial read thread feeding acks while the worker thread blocks in a synchronous rig_* call) -- the original combined nativeOpen() just violated it for its own internal rig_open(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf
#950 raised LogFileImport.MAX_IMPORT_BYTES from 50 to 100 MB. readFully_throwsWhenExceedingCap pushes one byte past the cap through readFully(), which buffers everything up to the cap first: over 100 MB in a ByteArrayOutputStream, plus its growth copies, inside the 512 MB test JVM. Depending on what else the run left on the heap it threw OutOfMemoryError instead of the expected IOException, failing "Unit tests & coverage" on dev (cc3a006) and on unrelated PRs (#966). readFully() now takes the cap as a parameter; the one-argument form the import calls still passes MAX_IMPORT_BYTES, so the app is unchanged. The test uses a 16 KB cap, with a new at-the-cap case and a check that the default stays 100 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stop the readFully cap test from running CI out of heap
New setting under Logging > Cloudlog/Wavelog, "Download QSOs from Wavelog when syncing", off by default. With it on, Sync (Logbook button and auto-sync) reads the Wavelog logbook once and uses it both ways: insert the remote QSOs no local row accounts for, then upload the pending rows the logbook doesn't have. - The download asks get_contacts_adif for the fields a full row needs; the duplicate-only check keeps its 7 fields. Older Wavelog sends full ADIF, so kept records are trimmed to the same field list. - Remote QSOs are paired with every local row (synced or not) by #936's one-to-one matcher, so a second sync inserts nothing. - Rows go in through the ADIF import's QSLRecord(map) + doInsertQSLData(..., false), in batches of 200 per transaction. A record the insert treats as an existing row (same call/date/time/mode) is not counted or flagged, so a pending local row stays pending. - Inserted rows are marked synced for Cloudlog, QRZ and WRL, so a Wavelog download never pushes another logger's QSOs to QRZ/WRL by itself. - The worked-before cache is reloaded after inserts. - Plain Cloudlog: the settings row is greyed out with a hint once a probe says the server can't list QSOs; a sync just skips the download. - Sync dialog line "Cloudlog / Wavelog / Nextlog: N downloaded" in all locales; auto-sync debug.log gets cloudlogDownloaded=N and also runs with nothing pending when the download is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…port note (#965 review round 1) - Downloaded QSOs skip PotaSessionManager.stampQso (new doInsertQSLData overload). - A remote QSO with the same call/date/time_on/mode as an unpaired local row is skipped instead of overwriting that row's grid and reports. - A remote QSO without COMMENT gets an empty comment, not 'imported at ...'. - The toggle stays switchable while on, even when the server can't list QSOs. - A local-log read failure falls back to the upload pass's own duplicate check; download read/insert failures show in the sync dialog's Cloudlog error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host Review fixes for the two-way sync: - Memory: at most MAX_KEPT_RECORDS (20,000) downloaded QSOs keep their fields. Past that the kept fields are dropped, nothing is inserted, and the sync says the logbook is too big to download (the duplicate check still sees every QSO). Reading stops at 250,000 QSOs overall, pages are 2,000 QSOs, and an OutOfMemoryError or StackOverflowError while reading the logbook drops the partial log instead of crashing the app. Uncaught, that crash repeated on every launch, since auto-sync retries at start. - Hostile or broken replies: response bodies are read in chunks and capped at 8M chars (readLine() buffered one endless line whole). Deeply nested JSON no longer overflows the stack in the page parser, the station_info parser or the Logging settings probe. Downloaded field values are cut to 1 KB (COMMENT 4 KB), so one huge field can't make a row too big for a CursorWindow and break every later read of the logbook. - Redirects: a POST follows a redirect only to the same host and never from https down to http. The body carries the API key. - A Station ID / API key rejection now replaces an earlier download error as the reported error, so its fix-it hint still shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add QRP Labs QMX via the hamlib backend
Download QSOs Wavelog has and the phone doesn't during Sync (#965)
QMX: transmit by CAT tone commands (stacked on #925)
…on-grant Restart mic capture when RECORD_AUDIO is granted on first launch
Resolves the rig-list conflict with #946 and #925 (both QMX entries are on dev now): "QRP Labs QMX" and "Qrp Labs QMX (Hamlib)" keep their indexes and "XIEGU G106(USB)" is appended after them in rigaddress.txt and GOLDEN_ORDER. InstructionSet's comment no longer calls 27 an open PR's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#952 builds the TX AudioTrack with setPerformanceMode(LOW_LATENCY) and logs getPerformanceMode(). Both exist only from Android 8.0 (API 26), and minSdk is 23. On Android 6.0-7.1 they throw NoSuchMethodError, an Error the TX paths' catch (Exception) doesn't catch, so every sound-card over and TUNE crashed the app there. Request the mode only on API 26+, with the SDK_INT check inline so lint's NewApi check sees it. Older Android builds the same track without it, as before #952. TxAudioTrackApiLevelTest builds the track under Robolectric on API 25, 26 and the current runtime; against the unguarded code the API 25 case fails with NoSuchMethodError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix TX crash on Android 6–7: guard AudioTrack performance mode behind API 26
Add safe Xiegu G106/DE-19 support and fixed receive timing
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes everything merged into
devsince the last staging promotion (33 PRs, 181 commits).Decode screen
Rig / TX
Audio
Bands / settings
Logging
iOS / desktop
CI / tooling
🤖 Generated with Claude Code