Satellite QSOs uploaded with BAND=SAT, which is not a legal ADIF Band
enumeration value (the legal values are concrete bands: 160M/80M/.../
2M/70CM/23CM...). Loggers that fail to parse an unknown band fall back
to a default — observed as QSOs landing in 160m. SAT is only legal as
PROP_MODE (propagation mode), which is already sent for v1.
Changes (WaveLogApi):
- bandFromHz(): map TX frequency to the real ADIF band (2M for VHF,
70CM for UHF, etc.)
- satModeFrom(): derive the ADIF SAT_MODE convention string from TX/RX
bands ("V/U" = VHF up / UHF down, "U/V", "V/S", "U/S"...; empty for
same-band links)
- v2 JSON: band=<real band>, add sat_mode when non-empty
- v1 ADIF: <band:> real band, add <sat_mode:> when non-empty;
PROP_MODE=SAT kept
Verification:
- New tests: SO-50 (145.850 up / 436.795 down) -> band 2M, sat_mode V/U;
AO-73 (435.150 up / 145.950 down) -> band 70CM, sat_mode U/V;
same-band -> empty sat_mode; satellite freqs never map to 160M.
- All wavelog payload tests + full domain suite green.
The fldigi port ran a fixed 600 Hz NCO, so any real signal not inside
600±75 Hz (the 150 Hz filter passband) decoded nothing — the decode rate
was effectively zero unless the tone happened to be on frequency. This
mirrors the behaviour of the removed channelTracker: a sliding spectral
peak detector now steers the NCO to the strongest tone.
Changes:
- Collect raw input, run a 512-pt Hann-windowed FFT every frame, find
the strongest bin in 300..1500 Hz (CW range), smooth-track it.
- First strong peak locks immediately (no RX reset, so the triggering
element survives); later large jumps (>120 Hz) retune and reset the
fldigi state machine; small drifts are eased at 20%.
- Absolute energy floor (peak < 30) so silence/noise never steers.
- estimatedPitch now reflects the tracked tone frequency.
Verification:
- New unit test: 900 Hz "CQ" with decoder initialized at 600 Hz decodes
correctly and pitch moves to ~900 Hz.
- All 9 decoder tests pass; full domain/cw/radar test suites green.
Background:
The CW decoder previously shipped a decompiled copy of the proprietary
Morse Expert 1.15 (com/ve3nea/morse_expert + obfuscated classes,
libnativedecoderjni.so, suncompat black-magic) — a copyright liability.
This removes all of it and reimplements the decoder on the open-source
fldigi (GPL v3) CW engine as a faithful pure-Kotlin port with no JNI.
Changes:
- Delete all Morse Expert reverse-engineered code: MainActivity,
obfuscated packages (B/B0/D/E2/...), suncompat/, pas/nativedecoder,
armeabi-v7a libnativedecoderjni.so, and the original View-based layouts
(activity_main, cw_panel_main, options_menu).
- Add a full fldigi CW pipeline in core/domain/cw:
- CwFldigiDsp: NCO down-conversion, FFT filter, movavg constants
- CwFftFilt: overlap-add FFT band-pass filter (fftfilt port)
- MorseTable + SomTable: full Morse code table + SOM codebook
- CwFldigiDecoder: decode_stream AGC + hysteresis, state machine,
adaptive speed tracking (5-55 WPM), SOM winner/normalize matching
- Rewrite CwDecodeScreen as pure Compose (DeepCW-style waterfall,
live decode line, history, status cards) and CwSettingsDialog
(speed/bandwidth/SOM) with no View interop.
- Replace the Morse Expert panel in TransceiversPage with a Compose
panel driving the same decoder; mic capture at 8000 Hz.
- Drop the forced armeabi-v7a abiFilters now that no native lib exists.
Verification:
- 8 unit tests pass (CQ/HELLO at 18-20 wpm, A-J at 30 wpm with
adaptive tracking, dot/dash/Farnsworth edge cases) — all decode
correctly from synthesized CW.
- :feature:cw and :feature:radar compile; app assembleDebug succeeds.
- APK contains no ve3nea/nativedecoder/morse_expert classes.
Addresses @AlanCui4080's review feedback about the manual date calculation.
Uses SimpleDateFormat to eliminate the hand-written calendar math (regex +
days-from-epoch calculation). Avoids java.time since it requires desugaring
on minSdk 24, keeping dependencies minimal.
Before: 23 lines of manual day-from-epoch calculation
After: 7 lines using SimpleDateFormat
Ref: https://github.com/rt-bishop/Look4Sat/pull/233#discussion_r1868599947
WavelogQueue serialized and deserialized every field except
gridsquare: updateGridsquare() wrote it, but save() skipped
put("gridsquare") and all() never read it back, so the QRZ-backfilled
grid was always empty at upload time (GRIDSQUARE never made it into
the ADIF). Add both directions.
WaveLog's parse_frequency() (Logbook_model.php) treats integer input
as Hz but reads string suffixes ("145.852038M" -> 145852038 Hz).
The bare-integer freq/freq_rx values in the v2 JSON envelope could be
misread as MHz by older WaveLog versions, corrupting the band
derivation (145.852 MHz showed up as 160m). ADIF string (v1/v2) was
already correct; now the extra fields match the same semantics.
Per upstream author feedback (PR #233) and tablet UX report:
- Bottom bar keeps max 5 primary destinations: Satellites/Passes/
AMSAT/Map/Settings; Radar moves to the More menu (still reachable
from Passes via item click)
- Legacy migration: persisted orders are rewritten in memory
(main menu drops Radar + appends AMSAT; More menu drops AMSAT +
appends Radar) so existing installs get the new layout
- Wide screens / tablets (width breakpoint) now use the side
navigation rail instead of the bottom bar - fixes the wasted
bottom strip ("big chin") in landscape/tablet layouts
- New AMSAT tab icon: MDI satellite-variant (Apache 2.0,
https://pictogrammers.com)
- What's new updated in 5 locales; version 4.5.6 (457)
- P0: fetchStatus() now runs on Dispatchers.IO - the previous
synchronous URLConnection on the main thread threw
NetworkOnMainThreadException and showed "load failed" on every open
- P1: refresh button rotates a vector icon (ic_refresh) instead of the
"↻" text glyph, whose off-center font metrics made the spinner
orbit around a shifted pivot
- P2: error state gains a Retry button (4 locales); amsat_refresh
string added (5 locales)
- versionCode 456 (bump for reinstalling over 455), versionName stays 4.5.5
- Replace HTML parsing with the official AMSAT Satellite Status API v1
(catalog.php + reports.php, JSON): AmSatApiClient (pure JVM, hand
rolled ISO-8601/epoch parsing for minSdk 24) + rewritten
AmSatRepository (satellite list from catalog, reports slotted into
6 days x 12 two-hour slots, status colors per report value)
- Fix edge-to-edge: status bar / navigation bar insets on the status
page (refresh button and update time were unreachable)
- Version 4.5.5 (455), What's new in all 5 locales
All Chinese comments (//, /* */, KDoc) across core/app/feature/build-
logic translated to English (550 lines, 73 files after FT8 rollback).
Code logic untouched - comment text only. Verified: all modules
compileDebugKotlin BUILD SUCCESSFUL.
User-prioritized WaveLog fixes (4.5.5, commit-only per instruction):
- QRZ 对方网格爬虫: QrzGridClient (domain, pure JVM) fetches
https://www.qrz.com/db/{call} with user-supplied cookies (parses
EditThisCookie JSON or raw "k=v; k=v"), extracts Grid Square from
the Detail table. Cookies NEVER built in - entered in settings.
- Settings: WaveLog card top-right gear opens QRZ cookie dialog with
test query button (detects logged-in callsign from cookie, looks up
its grid, shows result or failure in the dialog).
- LogTab: on Enter, async lookup of the other station's grid ->
queue.updateGridsquare -> uploaded with QSO (postQso gridsquare).
- LoTW satellite list: 112 names embedded (lotw.arrl.org config.tq6),
normalizeSatName maps Celestrak TLE names to LoTW names (SAUDISAT-1C
-> SO-50, FUNCUBE-1 -> AO-73, DIWATA-2B -> PO-101, ZARYA/ARISS ->
ARISS); "Update sats" button in WaveLog card downloads the live list
(LotwSatellitesRepo, SharedPreferences persisted, never in build).
- RST: rst_sent/rst_rcvd = 59/59 in both v1 ADIF and v2 JSON.
- Grid mismatch dialog kept (cloud station grid vs station QTH);
QSO gridsquare no longer uses station grid.
- New strings in 5 locales; check_strings 9 files OK.
Verified: core:domain/data + feature:settings/radar + app
compileDebugKotlin BUILD SUCCESSFUL.
Added to the prefs_outro_thanks list after BG7NTA in all 5 locale
files (values/zh/tr/in/id), keeping the original bullet style and the
24dp spacer before the license block (user: keep bottom spacing).
Verified: check_strings 9 files OK.
User testing round 3 (4.5.4): no success feedback on upload, aprs.fi
shows nothing, passcode calculator OK, notification present.
- Manual report now works even when service not running: ACTION_REPORT_NOW
starts the service first (Toast "not configured" if missing callsign)
- Upload result feedback guaranteed: Toast always shows (short OK /
long fail+reason), last result persisted (time/ok/detail) and shown
in the settings card "Last report: HH:mm:ss OK/failed - detail"
- Position source: station position from settingsRepo (user decision)
with live GPS last-known as fallback
- sendPacket reads the server confirmation line (short 3s timeout);
server error text (Invalid/error) surfaces in Toast + card
- Strings EN/ZH/TR/IN/ID +4 keys
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
User feedback round 2 (4.5.4): no notification shown, report not
sending, no error visibility on crash. Diagnosis: APRS-IS port 14580
reachable (verified with real login test), 24580 SSL refused; login
format OK. Fixes:
- Settings dialog: "Compute passcode" button - fills passcode from
callsign via the ported 0x73E2 algorithm (user can see the result)
- Global crash handler in MainApplication: stack trace appended to
files/crash_log.txt so crashes are diagnosable (user: no crash logs
were available before)
- POST_NOTIFICATIONS runtime permission requested when enabling APRS
(Android 13+ otherwise silently hides the service notification)
- AprsIsClient: read the login response (aprsc "# logresp ...
unverified"/"Invalid") and surface the server message as the error
- AprsForegroundService: Toast on manual report result (OK / failure
with server reason)
- Strings EN/ZH/TR/IN/ID +3 keys
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
Root cause: user reported crash on enabling APRS with a callsign set
(empty callsign worked because the service stops early and never
reaches startForeground). Android 14+ requires the service to declare
foregroundServiceType when startForeground passes a type; the service
had none -> process died on toggle.
- Manifest: add android:foregroundServiceType="dataSync"
- AprsCard: use startForegroundService() for start/report actions
(Android 8+ standard for foreground services)
- AprsForegroundService: try-catch around startForeground, fallback
stopSelf instead of killing the process
- AprsReporter: passcode "-1" (APRSdroid "no auth" convention) also
auto-computes from callsign
- Merged upstream 418a05e3 (zh wording fix, no conflicts)
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
aapt2 treats in/b+in as the same locale config as id -> Duplicate
resources build failure. in and id are equivalent in Android's
resource matcher (both normalize to id), so values-in + values-id
is sufficient coverage for Indonesian devices.
values-in-rID did not survive aapt2 linking (normalized away).
Switch to values-b+in (BCP-47) which compiles cleanly and keeps an
explicit "in" language config alongside values-id. Verified: aapt2
compile of values-in/values-id/values-b+in OK.
Root cause: aapt2 merges values-in into values-id (in is the legacy
alias of id), so the APK only carried the (id) config. New devices
report "id" and match; older devices report "in" and find no (in)
config -> fall back to English (friend's report: only system date
showed Indonesian).
Fixes:
- Add values-in-rID (core/presentation + feature/cw) so the APK
keeps a real "in" language config; values-in and values-id both
get translatable="false" on the 27 entries that English marks
(aapt2 rejects those with multiple %-substitutions otherwise)
- Verified: aapt2 compile of values/values-in/values-id/values-in-rID
OK; :core:presentation:mergeDebugResources + :feature:cw:mergeDebugResources
BUILD SUCCESSFUL
New feature/status module: fetches https://amsat.org/status/ and
renders a live status grid in the official site colors:
- Parser (AmSatParser): 47 satellites x 6 days x 12 two-hour slots,
official colors (blue=Active, orange=TLM/Beacon, pink=Not Heard,
deep-orange=Conflicting, gray=none); 598+ report details extracted
from inline JS tooltips (callsign/date/time/grid)
- Three-level viz: color grid -> report count -> tap day cell opens
report list dialog
- Manual refresh with spin animation + last-updated timestamp +
legend row; loading/error states
- New "AMSAT" entry in the More menu (Screen.AmSat), integrated with
page-order / hide-page settings (SettingsScreen screens list,
defaultSubMenuOrder, allNavItems, migration for existing users)
- AmSatRepository via IRemoteSource.getStatusHtml() (UA header);
shared remoteSource promoted to a lazy class property in MainContainer
Radar page Log tab: local entries now grouped by pass session with a
thick divider + satellite label between groups (matches the log page).
What's new updated in EN/ZH/TR/IN/ID. Version bumped to 4.5.3/453.
Not released (user gates all releases).
User request: logs should be separated by satellite/pass. Each pass
session gets an ID = satellite name + AOS timestamp (the second the
elevation hits 0, from OrbitalPass.aosTime), e.g.
"ASRTU-1-20260804-2014". Log page groups entries by session:
group title (satellite - local time) + thick divider line between
groups (md --- style); legacy entries without sessionId fall into
"Ungrouped" at the end.
- WavelogQso: +sessionId (persisted in queue JSON)
- LogTab: sessionId built from satelliteName + aosTimeMs (radar page
passes currentPass.aosTime)
- WavelogLogScreen: grouped rendering + wavelog_ungrouped string (4 locales)
- sessionId UTC yyyyMMdd-HHmm; display converts to local time
Verified: check_strings OK (9 files); :app:compileDebugKotlin
BUILD SUCCESSFUL. Not released (batched).
Friend's device (system language Bahasa Indonesia) fell back to
English even though values-in exists. Modern Android devices report
the Indonesian locale as "id" (ISO-639-1 current code; "in" is the
legacy alias) and resource matching is strict. Added values-id as a
copy of values-in in core/presentation and feature/cw (both language
directories ship the same translations).
Verified: check_strings OK (9 files incl. values-id);
:app:compileDebugKotlin BUILD SUCCESSFUL. Not released (batch with
pending fixes).
Official code issues found during review (user-reported):
1. GPS "success" was shown instantly even when no fix was obtained:
- setStationPosition() always returned true (permissions exception
swallowed, async requestLocationUpdates without waiting)
- now: suspend + LocationManagerCompat.getCurrentLocation (GPS
first, network fallback), permission check upfront, 15s timeout,
success only when onLocationChanged fires; SettingsRepo takes
Context for the permission check; ViewModel waits for the real
result and shows "Unable to get location - check permission and
GPS/network signal" on failure (4 locales)
2. Data update faked success on total failure:
- updateFromRemote now counts successful sources; 0 success throws
IOException -> timestamp NOT refreshed, Toast "Update failed -
check your network" (4 locales, new IShowToast resId overload)
- OkHttp timeouts widened: connect 15s / read 20s / write 20s
Verified: check_strings OK; all modules compile.
Release intentionally NOT triggered (user: fix everything first, then
one release).
User-test fixes (3rd overwrite, version stays 4.5.2/452):
1. Log page was invisible everywhere outside the radar tab:
- allNavItems in MainScreen.kt was missing Screen.WavelogLog ->
not in bottom nav, not in More menu
- SettingsScreen UI-order lists were missing the WavelogLog row ->
not in page order settings either
Now the Log page appears in the More menu (default sub menu tail,
migration appends it) AND in UI settings page-order lists.
2. Frequency display: formatFrequency was MHz.kHz.Hz (145.900.000);
now MHz.kHz (145.900) per request. Applies to transceiver panel
and Log page alike (shared formatter).
3. Log tab frequency: now extracts the exact numbers shown in the
transceiver panel (txBaseFrequencyHz for TX, uplinkLow/uplinkHigh
range for linear) - no re-computation, no extra decimals. Upload
uses the same tuned frequency (Hz precision kept internally).
4. Log page (More menu) table: date+time column (MM-dd HH:mm),
row separators (table lines), uploaded checkmark kept.
Verified: check_strings OK (8 files); :app:compileDebugKotlin
BUILD SUCCESSFUL (includes upstream merge 1a552417).