+ {/* TX audio bandwidth — low/high cut (Hz). Typed locally, committed on
+ blur/Enter so we don't spam a command per keystroke; the hold guard
+ keeps the poll from clobbering the field mid-edit. */}
+
- )}
- >
- );
- })()}
-
);
diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx
index 3969f21..6958a70 100644
--- a/frontend/src/lib/i18n.tsx
+++ b/frontend/src/lib/i18n.tsx
@@ -195,7 +195,7 @@ const en: Dict = {
'dvkp.voiceKeyer': 'Voice keyer', 'dvkp.stop': 'Stop', 'dvkp.disable': 'Disable voice keyer', 'dvkp.noMsgPre': 'No messages recorded yet. Open', 'dvkp.settingsPath': 'Settings → Audio devices & voice keyer', 'dvkp.noMsgPost': 'to record F1–F6.', 'dvkp.transmit': 'Transmit F{slot}{label} ({dur}s)', 'dvkp.empty': 'F{slot} — empty', 'dvkp.message': 'message',
'agp.portDeselect': 'Port {letter} — click to deselect', 'agp.portSelect': 'Select on port {letter}', 'agp.online': 'online', 'agp.offline': 'offline', 'agp.close': 'Close', 'agp.connecting': 'Connecting…', 'agp.noAntennas': 'No antennas configured.', 'agp.filterOnHint': 'Showing antennas for {band} only — click to show all bands', 'agp.filterOffHint': 'Showing all antennas — click to show only the current band',
'flxp.smartsdrRemote': 'SmartSDR remote control', 'flxp.offline': 'OFFLINE', 'flxp.waiting': 'Waiting for the FlexRadio… (set CAT to FlexRadio and connect)', 'flxp.transmit': 'Transmit', 'flxp.rfPower': 'RF Power', 'flxp.tunePwr': 'Tune Pwr', 'flxp.splitHint': 'Split: RX/TX on separate slices. ON creates a TX slice +1 kHz (CW) / +5 kHz (SSB) up, like SmartSDR.', 'flxp.sliceHint': 'Click to make this the active slice — frequency, mode, DSP and spot-clicks all follow it.', 'flxp.txSlice': 'This slice transmits', 'flxp.setTxSlice': 'Move TX to this slice (transmit here)', 'flxp.voxDly': 'VOX Dly', 'flxp.speed': 'Speed', 'flxp.pitch': 'Pitch', 'flxp.delay': 'Delay',
- 'flxp.receiveActive': 'Receive (active slice)', 'flxp.muted': 'Muted — click to unmute', 'flxp.mute': 'Mute RX audio', 'flxp.filter': 'Filter', 'flxp.amplifier': 'Amplifier', 'flxp.ampInLine': 'Amplifier is in line (transmitting through PA).', 'flxp.ampBypassed': 'Amplifier bypassed (standby).', 'flxp.pgConnected': 'PowerGenius connected', 'flxp.pgOffline': 'PowerGenius offline', 'flxp.fan': 'Fan', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Broadcast', 'flxp.fault': 'FAULT', 'flxp.meters': 'Meters', 'flxp.noMeters': "No meters yet — waiting for the radio's UDP stream…", 'flxp.amplifierHdr': 'AMPLIFIER',
+ 'flxp.receiveActive': 'Receive (active slice)', 'flxp.muted': 'Muted — click to unmute', 'flxp.mute': 'Mute RX audio', 'flxp.filter': 'Filter', 'flxp.amplifier': 'Amplifier', 'flxp.ampInLine': 'Amplifier is in line (transmitting through PA).', 'flxp.ampBypassed': 'Amplifier bypassed (standby).', 'flxp.pgConnected': 'PowerGenius connected', 'flxp.pgOffline': 'PowerGenius offline', 'flxp.fan': 'Fan', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Broadcast', 'flxp.fault': 'FAULT', 'flxp.meters': 'Meters', 'flxp.voltage': 'VOLTAGE', 'flxp.paTemp': 'PA TEMP', 'flxp.txFilter': 'TX filter', 'flxp.micProfile': 'Mic profile', 'flxp.noMeters': "No meters yet — waiting for the radio's UDP stream…", 'flxp.amplifierHdr': 'AMPLIFIER',
'icmp.spectrum': 'Spectrum', 'icmp.scopeFixed': 'Fixed — double-click / wheel to tune', 'icmp.scopeCenter': 'Center — follows VFO', 'icmp.scopeOff': 'Scope off', 'icmp.scopePanDown': 'Shift scope −50 kHz', 'icmp.scopePanUp': 'Shift scope +50 kHz', 'icmp.scopeCenterVfo': 'Center scope on the current frequency (±50 kHz)', 'icmp.notConnected': "Icom not connected. Enable the Icom CI-V backend in Settings → CAT and connect the radio's USB port.", 'icmp.refresh': 'Refresh', 'icmp.meters': 'Meters', 'icmp.transmit': 'Transmit', 'icmp.power': 'Power', 'icmp.mic': 'Mic', 'icmp.receive': 'Receive', 'icmp.preamp': 'Preamp', 'icmp.filter': 'Filter', 'icmp.noiseNotch': 'Noise / Notch', 'icmp.autoNotch': 'Auto notch filter', 'icmp.apf': 'Audio peak filter (CW)', 'icmp.clarifiers': 'RIT / ΔTX', 'icmp.ritHint': 'Wheel or ± to shift · Ctrl+←/→ shifts RIT when active', 'icmp.bandsAntenna': 'Bands & Antenna', 'icmp.antenna': 'Antenna', 'icmp.passband': 'Passband / Notch', 'icmp.pbtCenter': 'Center PBT', 'icmp.manualNotch': 'Manual notch — MN on, then set position', 'icmp.squelch': 'Squelch', 'icmp.powerOnHint': 'Power the radio ON (boots ~15 s)', 'icmp.powerOffHint': 'Power the radio OFF', 'icmp.powerOffConfirm': 'Switch the radio OFF?',
'rst.clickToFill': 'Click to set RST tx from the signal',
'qrz.openTitle': 'Open {call} on QRZ.com',
@@ -384,7 +384,7 @@ const fr: Dict = {
'dvkp.voiceKeyer': 'Manipulateur vocal', 'dvkp.stop': 'Stop', 'dvkp.disable': 'Désactiver le manipulateur vocal', 'dvkp.noMsgPre': 'Aucun message enregistré. Ouvre', 'dvkp.settingsPath': 'Réglages → Périphériques audio & manipulateur vocal', 'dvkp.noMsgPost': 'pour enregistrer F1–F6.', 'dvkp.transmit': 'Émettre F{slot}{label} ({dur}s)', 'dvkp.empty': 'F{slot} — vide', 'dvkp.message': 'message',
'agp.portDeselect': 'Port {letter} — clic pour désélectionner', 'agp.portSelect': 'Sélectionner sur le port {letter}', 'agp.online': 'en ligne', 'agp.offline': 'hors ligne', 'agp.close': 'Fermer', 'agp.connecting': 'Connexion…', 'agp.noAntennas': 'Aucune antenne configurée.', 'agp.filterOnHint': 'Antennes du {band} uniquement — clic pour afficher toutes les bandes', 'agp.filterOffHint': 'Toutes les antennes affichées — clic pour n’afficher que la bande courante',
'flxp.smartsdrRemote': 'Contrôle à distance SmartSDR', 'flxp.offline': 'HORS LIGNE', 'flxp.waiting': 'En attente du FlexRadio… (règle le CAT sur FlexRadio et connecte)', 'flxp.transmit': 'Émission', 'flxp.rfPower': 'Puissance RF', 'flxp.tunePwr': 'Puiss. TUNE', 'flxp.splitHint': 'Split : RX/TX sur des slices séparées. ON crée une slice TX +1 kHz (CW) / +5 kHz (SSB) au-dessus, comme SmartSDR.', 'flxp.sliceHint': 'Cliquer pour rendre cette slice active — fréquence, mode, DSP et clics de spot la suivent tous.', 'flxp.txSlice': 'Cette slice émet', 'flxp.setTxSlice': 'Déplacer le TX sur cette slice (émettre ici)', 'flxp.voxDly': 'Délai VOX', 'flxp.speed': 'Vitesse', 'flxp.pitch': 'Tonalité', 'flxp.delay': 'Délai',
- 'flxp.receiveActive': 'Réception (slice active)', 'flxp.muted': 'Coupé — clic pour rétablir', 'flxp.mute': "Couper l'audio RX", 'flxp.filter': 'Filtre', 'flxp.amplifier': 'Amplificateur', 'flxp.ampInLine': 'Amplificateur en ligne (émission via le PA).', 'flxp.ampBypassed': 'Amplificateur en bypass (standby).', 'flxp.pgConnected': 'PowerGenius connecté', 'flxp.pgOffline': 'PowerGenius hors ligne', 'flxp.fan': 'Ventilo', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Diffusion', 'flxp.fault': 'DÉFAUT', 'flxp.meters': 'Mesures', 'flxp.noMeters': 'Aucune mesure — en attente du flux UDP de la radio…', 'flxp.amplifierHdr': 'AMPLIFICATEUR',
+ 'flxp.receiveActive': 'Réception (slice active)', 'flxp.muted': 'Coupé — clic pour rétablir', 'flxp.mute': "Couper l'audio RX", 'flxp.filter': 'Filtre', 'flxp.amplifier': 'Amplificateur', 'flxp.ampInLine': 'Amplificateur en ligne (émission via le PA).', 'flxp.ampBypassed': 'Amplificateur en bypass (standby).', 'flxp.pgConnected': 'PowerGenius connecté', 'flxp.pgOffline': 'PowerGenius hors ligne', 'flxp.fan': 'Ventilo', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Diffusion', 'flxp.fault': 'DÉFAUT', 'flxp.meters': 'Mesures', 'flxp.voltage': 'TENSION', 'flxp.paTemp': 'TEMP PA', 'flxp.txFilter': 'Filtre TX', 'flxp.micProfile': 'Profil micro', 'flxp.noMeters': 'Aucune mesure — en attente du flux UDP de la radio…', 'flxp.amplifierHdr': 'AMPLIFICATEUR',
'icmp.spectrum': 'Spectre', 'icmp.scopeFixed': 'Fixe — double-clic / molette pour accorder', 'icmp.scopeCenter': 'Centré — suit le VFO', 'icmp.scopeOff': 'Scope éteint', 'icmp.scopePanDown': 'Décaler le scope −50 kHz', 'icmp.scopePanUp': 'Décaler le scope +50 kHz', 'icmp.scopeCenterVfo': 'Centrer le scope sur la fréquence actuelle (±50 kHz)', 'icmp.notConnected': 'Icom non connecté. Active le backend CI-V Icom dans Réglages → CAT et connecte le port USB de la radio.', 'icmp.refresh': 'Rafraîchir', 'icmp.meters': 'Mesures', 'icmp.transmit': 'Émission', 'icmp.power': 'Puissance', 'icmp.mic': 'Micro', 'icmp.receive': 'Réception', 'icmp.preamp': 'Préampli', 'icmp.filter': 'Filtre', 'icmp.noiseNotch': 'Bruit / Notch', 'icmp.autoNotch': 'Filtre notch auto', 'icmp.apf': 'Filtre de pic audio (CW)', 'icmp.clarifiers': 'RIT / ΔTX', 'icmp.ritHint': 'Molette ou ± pour décaler · Ctrl+←/→ décale le RIT si actif', 'icmp.bandsAntenna': 'Bandes & Antenne', 'icmp.antenna': 'Antenne', 'icmp.passband': 'Passe-bande / Notch', 'icmp.pbtCenter': 'Centrer PBT', 'icmp.manualNotch': 'Notch manuel — active MN, puis règle la position', 'icmp.squelch': 'Squelch', 'icmp.powerOnHint': 'Allumer la radio (démarre en ~15 s)', 'icmp.powerOffHint': 'Éteindre la radio', 'icmp.powerOffConfirm': 'Éteindre la radio ?',
'rst.clickToFill': 'Clic pour remplir le RST tx depuis le signal',
'qrz.openTitle': 'Ouvrir {call} sur QRZ.com',
diff --git a/frontend/src/version.ts b/frontend/src/version.ts
index 7b7e42b..9c54817 100644
--- a/frontend/src/version.ts
+++ b/frontend/src/version.ts
@@ -1,6 +1,6 @@
// Single source of truth for the app version shown in the UI (header + About).
// Bump this on a release (the release script updates it alongside telemetry.go).
-export const APP_VERSION = '0.19.1';
+export const APP_VERSION = '0.19.2';
// Author / credits, shown in Help -> About.
export const APP_AUTHOR = 'F4BPO';
diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts
index 94a9fbb..b2a1118 100644
--- a/frontend/wailsjs/go/main/App.d.ts
+++ b/frontend/wailsjs/go/main/App.d.ts
@@ -202,6 +202,8 @@ export function FlexSetFilter(arg1:number,arg2:number):Promise;
export function FlexSetMic(arg1:number):Promise;
+export function FlexSetMicProfile(arg1:string):Promise;
+
export function FlexSetMon(arg1:boolean):Promise;
export function FlexSetMonLevel(arg1:number):Promise;
@@ -230,6 +232,8 @@ export function FlexSetSplit(arg1:boolean):Promise;
export function FlexSetTXAntenna(arg1:string):Promise;
+export function FlexSetTXFilter(arg1:number,arg2:number):Promise;
+
export function FlexSetTXSlice(arg1:number):Promise;
export function FlexSetTunePower(arg1:number):Promise;
@@ -240,6 +244,10 @@ export function FlexSetVoxDelay(arg1:number):Promise;
export function FlexSetVoxLevel(arg1:number):Promise;
+export function FlexSetWNB(arg1:boolean):Promise;
+
+export function FlexSetWNBLevel(arg1:number):Promise;
+
export function FlexTune(arg1:boolean):Promise;
export function GetActiveProfile():Promise;
diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js
index 0132188..cff577d 100644
--- a/frontend/wailsjs/go/main/App.js
+++ b/frontend/wailsjs/go/main/App.js
@@ -366,6 +366,10 @@ export function FlexSetMic(arg1) {
return window['go']['main']['App']['FlexSetMic'](arg1);
}
+export function FlexSetMicProfile(arg1) {
+ return window['go']['main']['App']['FlexSetMicProfile'](arg1);
+}
+
export function FlexSetMon(arg1) {
return window['go']['main']['App']['FlexSetMon'](arg1);
}
@@ -422,6 +426,10 @@ export function FlexSetTXAntenna(arg1) {
return window['go']['main']['App']['FlexSetTXAntenna'](arg1);
}
+export function FlexSetTXFilter(arg1, arg2) {
+ return window['go']['main']['App']['FlexSetTXFilter'](arg1, arg2);
+}
+
export function FlexSetTXSlice(arg1) {
return window['go']['main']['App']['FlexSetTXSlice'](arg1);
}
@@ -442,6 +450,14 @@ export function FlexSetVoxLevel(arg1) {
return window['go']['main']['App']['FlexSetVoxLevel'](arg1);
}
+export function FlexSetWNB(arg1) {
+ return window['go']['main']['App']['FlexSetWNB'](arg1);
+}
+
+export function FlexSetWNBLevel(arg1) {
+ return window['go']['main']['App']['FlexSetWNBLevel'](arg1);
+}
+
export function FlexTune(arg1) {
return window['go']['main']['App']['FlexTune'](arg1);
}
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts
index 92f6f14..2ef98b3 100644
--- a/frontend/wailsjs/go/models.ts
+++ b/frontend/wailsjs/go/models.ts
@@ -585,6 +585,10 @@ export namespace cat {
mon: boolean;
mon_level: number;
mic_level: number;
+ tx_filter_low: number;
+ tx_filter_high: number;
+ mic_profile?: string;
+ mic_profiles?: string[];
atu_status?: string;
atu_memories: boolean;
rx_avail: boolean;
@@ -605,6 +609,8 @@ export namespace cat {
nr_level: number;
anf: boolean;
anf_level: number;
+ wnb: boolean;
+ wnb_level: number;
mode?: string;
cw_speed: number;
cw_pitch: number;
@@ -642,6 +648,10 @@ export namespace cat {
this.mon = source["mon"];
this.mon_level = source["mon_level"];
this.mic_level = source["mic_level"];
+ this.tx_filter_low = source["tx_filter_low"];
+ this.tx_filter_high = source["tx_filter_high"];
+ this.mic_profile = source["mic_profile"];
+ this.mic_profiles = source["mic_profiles"];
this.atu_status = source["atu_status"];
this.atu_memories = source["atu_memories"];
this.rx_avail = source["rx_avail"];
@@ -662,6 +672,8 @@ export namespace cat {
this.nr_level = source["nr_level"];
this.anf = source["anf"];
this.anf_level = source["anf_level"];
+ this.wnb = source["wnb"];
+ this.wnb_level = source["wnb_level"];
this.mode = source["mode"];
this.cw_speed = source["cw_speed"];
this.cw_pitch = source["cw_pitch"];
diff --git a/internal/cat/cat.go b/internal/cat/cat.go
index 9cec63c..976aa43 100644
--- a/internal/cat/cat.go
+++ b/internal/cat/cat.go
@@ -275,6 +275,11 @@ type FlexTXState struct {
Mon bool `json:"mon"`
MonLevel int `json:"mon_level"`
MicLevel int `json:"mic_level"`
+ TXFilterLow int `json:"tx_filter_low"` // TX filter low cut (Hz)
+ TXFilterHigh int `json:"tx_filter_high"` // TX filter high cut (Hz)
+ // Mic profiles (SmartSDR): the list of available profiles + the loaded one.
+ MicProfile string `json:"mic_profile,omitempty"`
+ MicProfiles []string `json:"mic_profiles,omitempty"`
ATUStatus string `json:"atu_status,omitempty"`
ATUMemories bool `json:"atu_memories"`
// Active RX slice DSP controls.
@@ -296,6 +301,8 @@ type FlexTXState struct {
NRLevel int `json:"nr_level"`
ANF bool `json:"anf"`
ANFLevel int `json:"anf_level"`
+ WNB bool `json:"wnb"`
+ WNBLevel int `json:"wnb_level"`
// CW / mode-specific controls.
Mode string `json:"mode,omitempty"` // active slice mode (CW/USB/LSB/DIGU…)
CWSpeed int `json:"cw_speed"`
@@ -344,6 +351,8 @@ type FlexController interface {
SetMon(bool) error
SetMonLevel(int) error
SetMic(int) error
+ SetTXFilter(low, high int) error // transmit-audio bandwidth (Hz)
+ SetMicProfile(string) error // load a SmartSDR mic profile by name
ATUStart() error
ATUBypass() error
SetATUMemories(bool) error
@@ -365,6 +374,8 @@ type FlexController interface {
SetANFLevel(int) error
SetAPF(bool) error
SetAPFLevel(int) error
+ SetWNB(bool) error
+ SetWNBLevel(int) error
// CW keyer + mode-specific controls.
SetCWSpeed(int) error
SetCWPitch(int) error
diff --git a/internal/cat/flex.go b/internal/cat/flex.go
index de47c08..85ba838 100644
--- a/internal/cat/flex.go
+++ b/internal/cat/flex.go
@@ -38,6 +38,8 @@ type Flex struct {
slices map[int]*flexSlice
tx flexTX // transmit/ATU state pushed by the radio (FlexRadio tab)
amp flexAmp // external amplifier (PowerGenius XL) state
+ micProfiles []string // available mic profiles (SmartSDR "profile mic list")
+ micProfile string // currently loaded mic profile
txSetAt map[string]time.Time // status field → when WE last set it (ignore the radio's lagging echo briefly)
lastStateSig string // last logged derived-state signature (log only on change)
boundClientID string // GUI client (SmartSDR) we bound to; "" until bound. Binding lets this non-GUI client receive GUI-tied data (CW pitch/speed, break-in delay, RF power).
@@ -85,6 +87,8 @@ type flexSlice struct {
anfLevel int
apf bool // CW audio peaking filter
apfLevel int
+ wnb bool // wideband noise blanker
+ wnbLevel int
filterLo int // slice filter low cut (Hz)
filterHi int // slice filter high cut (Hz)
rxAnt string // selected RX antenna (e.g. ANT1, ANT2, RX_A)
@@ -108,6 +112,8 @@ type flexTX struct {
mon bool
monLevel int
micLevel int
+ filterLow int // TX filter low cut (Hz)
+ filterHigh int // TX filter high cut (Hz)
atuStatus string
atuMemories bool
// CW keyer params (set via the top-level "cw" commands).
@@ -209,6 +215,8 @@ func (f *Flex) Connect() error {
f.send("sub amplifier all") // external amplifier (PowerGenius XL) operate/standby
f.send("sub radio all") // radio-wide incl. interlock (TX/RX state)
f.send("sub cwx all") // CWX: the LIVE CW speed/pitch/break-in (transmit holds only a static default)
+ f.send("sub profile all") // mic/global/tx profiles (for the mic-profile dropdown)
+ f.send("profile mic info") // request the current mic profile list + selection
f.send("sub client all") // learn the GUI client (SmartSDR) so we can bind to it (below)
f.startMeters(conn) // open the UDP VITA-49 stream for live meters
if f.spotsEnabled {
@@ -445,6 +453,12 @@ func (f *Flex) handleStatus(payload string) {
f.tx.cwBreakInDelay = atoiDefault(val, f.tx.cwBreakInDelay)
case "mic_level", "miclevel":
f.tx.micLevel = atoiDefault(val, f.tx.micLevel)
+ // TX filter: the transmit STATUS reports the passband as lo/hi (the
+ // SET command is filter_low/filter_high — a SmartSDR quirk).
+ case "lo", "filter_low":
+ f.tx.filterLow = atoiDefault(val, f.tx.filterLow)
+ case "hi", "filter_high":
+ f.tx.filterHigh = atoiDefault(val, f.tx.filterHigh)
}
}
f.mu.Unlock()
@@ -526,6 +540,35 @@ func (f *Flex) handleStatus(payload string) {
}
f.mu.Unlock()
}
+ // Mic-profile object — "profile mic list=A^B^C" (available profiles) and
+ // "profile mic current=" (loaded one). Names can contain spaces, so
+ // values are taken from the raw payload after the key. Logged once so the
+ // exact field names are confirmable on real hardware.
+ if len(fields) >= 2 && fields[0] == "profile" && fields[1] == "mic" {
+ debugLog.Printf("Flex: profile status: %s", payload)
+ if i := strings.Index(payload, "list="); i >= 0 {
+ var profs []string
+ for _, p := range strings.Split(payload[i+len("list="):], "^") {
+ if p = strings.TrimSpace(p); p != "" {
+ profs = append(profs, p)
+ }
+ }
+ f.mu.Lock()
+ f.micProfiles = profs
+ f.mu.Unlock()
+ }
+ // The loaded profile arrives as current= (some firmwares:
+ // selection=); accept either.
+ for _, key := range []string{"current=", "selection="} {
+ if i := strings.Index(payload, key); i >= 0 {
+ cur := strings.TrimSpace(payload[i+len(key):])
+ f.mu.Lock()
+ f.micProfile = cur
+ f.mu.Unlock()
+ break
+ }
+ }
+ }
// Interlock object — transmit state (RECEIVE / TRANSMITTING / …).
if len(fields) >= 1 && fields[0] == "interlock" {
f.mu.Lock()
@@ -744,6 +787,10 @@ func (f *Flex) handleStatus(payload string) {
s.apf = val == "1"
case "apf_level":
s.apfLevel = atoiDefault(val, s.apfLevel)
+ case "wnb":
+ s.wnb = val == "1"
+ case "wnb_level":
+ s.wnbLevel = atoiDefault(val, s.wnbLevel)
case "filter_lo":
s.filterLo = atoiDefault(val, s.filterLo)
case "filter_hi":
@@ -1164,6 +1211,10 @@ func (f *Flex) FlexState() FlexTXState {
Mon: f.tx.mon,
MonLevel: f.tx.monLevel,
MicLevel: f.tx.micLevel,
+ TXFilterLow: f.tx.filterLow,
+ TXFilterHigh: f.tx.filterHigh,
+ MicProfile: f.micProfile,
+ MicProfiles: f.micProfiles,
ATUStatus: f.tx.atuStatus,
ATUMemories: f.tx.atuMemories,
// CW keyer (defaults applied so the sliders show sane values pre-read).
@@ -1214,6 +1265,8 @@ func (f *Flex) FlexState() FlexTXState {
st.ANFLevel = rx.anfLevel
st.APF = rx.apf
st.APFLevel = rx.apfLevel
+ st.WNB = rx.wnb
+ st.WNBLevel = rx.wnbLevel
st.FilterLo = rx.filterLo
st.FilterHi = rx.filterHi
st.RXAnt = rx.rxAnt
@@ -1391,6 +1444,8 @@ func (f *Flex) SetANF(on bool) error { return f.sendSlice("anf", boolFlex(on)
func (f *Flex) SetANFLevel(l int) error { return f.sendSlice("anf_level", clampLevel(l)) }
func (f *Flex) SetAPF(on bool) error { return f.sendSlice("apf", boolFlex(on)) }
func (f *Flex) SetAPFLevel(l int) error { return f.sendSlice("apf_level", clampLevel(l)) }
+func (f *Flex) SetWNB(on bool) error { return f.sendSlice("wnb", boolFlex(on)) }
+func (f *Flex) SetWNBLevel(l int) error { return f.sendSlice("wnb_level", clampLevel(l)) }
// ── CW keyer controls (top-level "cw" commands) ──
@@ -1608,6 +1663,45 @@ func (f *Flex) SetMic(l int) error {
return f.txSet(fmt.Sprintf("transmit set miclevel=%d", l), "mic_level", func(t *flexTX) { t.micLevel = l })
}
+// SetTXFilter sets the transmit-audio bandwidth (low + high cut, in Hz). SmartSDR
+// clamps to legal values per mode; we just pass them through in one command so
+// both edges move together.
+func (f *Flex) SetTXFilter(low, high int) error {
+ if low < 0 {
+ low = 0
+ }
+ if high < low {
+ high = low
+ }
+ // Guard both status field names (the echo comes back as lo/hi, not
+ // filter_low/high) so the radio's lagging echo doesn't snap the inputs back.
+ f.mu.Lock()
+ f.txSetAt["hi"] = time.Now()
+ f.mu.Unlock()
+ return f.txSet(fmt.Sprintf("transmit set filter_low=%d filter_high=%d", low, high), "lo",
+ func(t *flexTX) { t.filterLow, t.filterHigh = low, high })
+}
+
+// SetMicProfile loads a SmartSDR mic profile by name (quoted, as names may hold
+// spaces). Optimistically caches the selection.
+func (f *Flex) SetMicProfile(name string) error {
+ name = strings.TrimSpace(name)
+ if name == "" {
+ return fmt.Errorf("flex: empty mic profile")
+ }
+ f.mu.Lock()
+ connected := f.conn != nil
+ if connected {
+ f.micProfile = name
+ }
+ f.mu.Unlock()
+ if !connected {
+ return fmt.Errorf("flex: not connected")
+ }
+ f.send(fmt.Sprintf("profile mic load \"%s\"", name))
+ return nil
+}
+
func (f *Flex) ATUStart() error {
if !f.connected() {
return fmt.Errorf("flex: not connected")
diff --git a/telemetry.go b/telemetry.go
index 4c527fb..e9a0faa 100644
--- a/telemetry.go
+++ b/telemetry.go
@@ -21,7 +21,7 @@ import (
const (
// appVersion is stamped on every heartbeat (and could feed the About box).
- appVersion = "0.19.1"
+ appVersion = "0.19.2"
// posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
// to https://us.i.posthog.com for a US project.
diff --git a/wiki/Amplifiers-and-Switches.md b/wiki/Amplifiers-and-Switches.md
new file mode 100644
index 0000000..5ede70a
--- /dev/null
+++ b/wiki/Amplifiers-and-Switches.md
@@ -0,0 +1,23 @@
+# Amplifiers and Switches
+
+## PowerGenius XL (4O3A) amplifier
+
+Direct TCP connection (Settings → PowerGenius):
+
+- **Operate / Standby** and **fault** display.
+- **Fan-mode** selector (Standard / Contest / Broadcast).
+- When a Flex is connected, the amp controls + its **FWD / ID / TEMP** meters
+ appear in the FlexRadio panel's Amplifier card. See [[FlexRadio]].
+
+## Antenna Genius (4O3A) antenna switch
+
+Over TCP / GSCP — a docked **A/B antenna-switch** widget:
+
+- One row per configured antenna, with a **Port A** and **Port B** button.
+- Colours: green = selected on port A, blue = selected on port B, red (pulsing) =
+ that port is transmitting. Clicking a selected port deselects it (→ None).
+- **Band filter** (funnel icon in the header): shows only the antennas configured
+ for the current band, like the native app. Toggle it off to see all antennas.
+ Antennas with an unknown band mask are always shown.
+
+Configure the host / password in Settings → Antenna Genius.
diff --git a/wiki/Audio-and-Keyers.md b/wiki/Audio-and-Keyers.md
new file mode 100644
index 0000000..a4f7f89
--- /dev/null
+++ b/wiki/Audio-and-Keyers.md
@@ -0,0 +1,36 @@
+# Audio and Keyers
+
+## CW keyer
+
+A CW keyer with **macros** and F-key macros. The keyer **engine** is selectable
+(Settings → CW Keyer):
+
+- **WinKeyer** — K1EL WK1/2/3 over a COM port.
+- **Icom** — the radio's own keyer over CI-V, no extra hardware. Works over the
+ remote link too. (Requires the Icom CAT backend; set break-in to SEMI/FULL or
+ the rig keys the sidetone but stays in RX.)
+- **TCI** — for TCI backends.
+
+## Digital Voice Keyer (DVK)
+
+Record **F1–F6** voice messages and transmit them. Set the audio devices, PTT
+method and gains in Settings → Audio.
+
+- **PTT** method: CAT, RTS, DTR, or none (VOX).
+- **Test PTT** keys the rig briefly. On CAT/OmniRig the key is held ~1.5 s so the
+ transmit command is actually sent (OmniRig is asynchronous and coalesces rapid
+ writes).
+
+## QSO audio recording
+
+Continuous rolling capture. On **Log QSO** the contact is saved to a per-QSO WAV
+(`CALL_YYYYMMDD_HHMMSS.wav`), mixing RX + mic. Configure the recordings folder,
+format (WAV / MP3), pre-roll and gains in Settings → Audio.
+
+## RX audio monitor (USB rigs)
+
+Settings → Audio → **Listen to radio** pipes the rig's received audio (its "USB
+Audio CODEC" input) through your speakers, so you hear the radio inside OpsLog.
+**Talk to radio** keys PTT and pipes your mic to the rig's audio input. (Network
+audio for remote Icom is a work in progress — see
+[[Remote Icom over the Internet]].)
diff --git a/wiki/Awards.md b/wiki/Awards.md
new file mode 100644
index 0000000..0b557ba
--- /dev/null
+++ b/wiki/Awards.md
@@ -0,0 +1,47 @@
+# Awards
+
+OpsLog has a flexible awards engine. Award **definitions** and reference lists are
+shared **globally** across profiles.
+
+## Built-in awards
+
+DXCC, WAS / WAZ / WAC, WPX, IOTA / POTA / SOTA / WWFF, **DDFM** (French
+departments), and more — tracked **worked / confirmed / validated** by band and
+mode.
+
+## How matching works
+
+An award scans a **QSO field** for a reference, by one of:
+
+- **code** — the field value is the reference (e.g. state = `NY`).
+- **description** — the reference's name appears in the field (substring, case-
+ insensitive but **space-sensitive** — "Hongkong" won't match "Hong Kong").
+- **pattern** — a per-reference regex is tested against the field.
+
+Plus **OR rules** (also match if any of these hit), a **leading/trailing** strip,
+a **prefix** added to found references, and a **dynamic** mode (any value counts,
+like POTA).
+
+> **Tip — Hong Kong / Macau style mismatches:** if the data says "Hong Kong" but
+> the reference description is "Hongkong", either fix the description to match, or
+> set that OR-search's *Match by* to **pattern** so the reference's own regex
+> (e.g. `\bHong Kong\b`) is used. In *description* mode the per-reference pattern
+> is ignored.
+
+## Live detection & manual refs
+
+- References are detected **live** as you enter a callsign.
+- You can **manually assign** a reference to a QSO (stored in an ADIF extra so it
+ survives export/import — see [[Import and Export ADIF]]).
+
+## Reference lists & display
+
+- **Import reference lists** for totals and names.
+- Per award, choose what the Recent-QSOs column shows: **reference**, **name**,
+ or **both**. Award columns are opt-in per the Columns picker
+ ([[Recent QSOs and Filters]]).
+
+## Rescan
+
+**Rescan** re-pulls the logbook and recomputes — it picks up fresh LoTW / QRZ /
+eQSL confirmations (see [[QSL Management]]).
diff --git a/wiki/CAT-Control.md b/wiki/CAT-Control.md
new file mode 100644
index 0000000..2370366
--- /dev/null
+++ b/wiki/CAT-Control.md
@@ -0,0 +1,49 @@
+# CAT Control
+
+OpsLog has **four native CAT backends** (Settings → CAT). Each auto-reconnects
+and connects *non-blocking*, so a powered-off radio never freezes the app.
+
+| Backend | Use it for |
+|---------|-----------|
+| **OmniRig** | Any OmniRig-supported rig (Rig 1 / Rig 2, hot-swap). |
+| **FlexRadio (SmartSDR)** | FlexRadio 6000/8000 over the TCP API. → [[FlexRadio]] |
+| **Icom CI-V** | Any Icom over **USB** *or* over the internet via its **LAN server**. → [[Icom]] · [[Remote Icom over the Internet]] |
+| **TCI** | SunSDR / ExpertSDR2 and any TCI (WebSocket) server. |
+
+Once connected, frequency / band / mode follow the radio, clicking a cluster spot
+tunes the rig, and (for Flex) cluster spots can be pushed to the panadapter.
+
+## OmniRig
+
+Pick **Rig 1** or **Rig 2** (the two OmniRig slots). Configure the actual COM
+port / baud in OmniRig's own settings. Works with anything OmniRig supports; PTT
+keying via CAT is supported where the rig's OmniRig profile exposes it.
+
+## FlexRadio
+
+Enter the radio's IP (UDP discovery assists). You get a full SmartSDR-style
+control tab — see **[[FlexRadio]]**.
+
+## Icom CI-V (USB)
+
+- **Rig model** — pick your model from the dropdown; it sets the correct **CI-V
+ address** automatically (or choose *Other* and type the hex address).
+- **COM port** + **baud** must match the radio (set *CI-V USB Echo Back* **OFF**
+ on the rig).
+- The model choice also tailors the console (e.g. attenuator steps: 20 dB on an
+ IC-7300, 6/12/18 dB on an IC-7610).
+
+Full console details: **[[Icom]]**.
+
+## TCI
+
+Enter the TCI host + port (default 40001). Gives freq / mode / PTT / split and,
+optionally, panorama spots.
+
+## Notes
+
+- The **digital sub-mode** (FT4 vs FT8) is inferred from the frequency.
+- **Per-band Flex RX/TX antennas** can be configured and are applied
+ automatically on band change (Settings → FlexRadio).
+- Split, RIT/XIT and other rig-specific features are exposed on each backend's
+ control tab where supported.
diff --git a/wiki/Contest-Logging.md b/wiki/Contest-Logging.md
new file mode 100644
index 0000000..c30ceae
--- /dev/null
+++ b/wiki/Contest-Logging.md
@@ -0,0 +1,27 @@
+# Contest Logging
+
+**Contest tab.** Pick a contest and an exchange, and OpsLog handles the rest.
+
+## Setup
+
+- **Contest** — from the built-in ADIF `CONTEST_ID` list.
+- **Exchange** — a **running serial** (auto-incrementing) or a **fixed exchange**.
+- **Window** — start / end times; QSOs outside are flagged.
+
+## While operating
+
+- OpsLog auto-fills `CONTEST_ID` and the **sent / received serials** (`STX` /
+ `SRX`).
+- **Dupe** detection flags a call already worked in the contest.
+- A live **scoreboard** tracks your progress.
+
+## Export
+
+Export the contest log to **Cabrillo** for submission (Recent QSOs → export), or
+to ADIF. See [[Import and Export ADIF]].
+
+## Tip
+
+Use **Bulk edit field** (right-click in Recent QSOs) to fix a contest field
+across many QSOs at once — e.g. set `CONTEST_ID` or an exchange string on an
+imported batch.
diff --git a/wiki/DX-Cluster-and-Spots.md b/wiki/DX-Cluster-and-Spots.md
new file mode 100644
index 0000000..82567f4
--- /dev/null
+++ b/wiki/DX-Cluster-and-Spots.md
@@ -0,0 +1,40 @@
+# DX Cluster and Spots
+
+## Servers
+
+Add multiple cluster servers (Settings). They auto-reconnect; one is the
+**master** used for sending commands. The Cluster tab and the Main-view cluster
+pane share the same live feed.
+
+## Filter sidebar
+
+A show/hide **filter sidebar** (shared by the Cluster tab and the Main pane):
+
+- **Callsign** search, **hide worked**, **group duplicates**.
+- Filter by **band / mode / status / source**.
+
+## Per-spot status and tuning
+
+Each spot is coloured by **status** relative to your log:
+
+- **new** (entity never worked), **new-band**, **new-slot**, **worked**.
+
+Click a spot to **tune the rig** (fills the callsign into the entry strip). A
+multi-band **Band Map** shows panadapter-style strips per band.
+
+**POTA** spots are tagged with their park reference (via `api.pota.app`).
+
+## Sending a spot
+
+Use the **Send Spot** window to announce a DX spot on the master cluster:
+callsign + frequency + a message. The frequency defaults to the **live CAT
+frequency** (full resolution, including sub-kHz).
+
+## Spot alerts
+
+**Tools → Alert management** — Log4OM-style alert rules on **call / country /
+band / mode / spotter**, with **sound**, **visual** and **e-mail** notification.
+Active alerts appear as a discreet bell in the entry strip; click it for the
+list. Alerts are kept briefly and only shown when active.
+
+See also: [[Maps and Antennas]] for turning the beam to a spot.
diff --git a/wiki/FlexRadio.md b/wiki/FlexRadio.md
new file mode 100644
index 0000000..a2775c5
--- /dev/null
+++ b/wiki/FlexRadio.md
@@ -0,0 +1,54 @@
+# FlexRadio
+
+When the CAT backend is a **FlexRadio (SmartSDR)**, OpsLog shows a full
+SmartSDR-style control panel (as a Main-view pane or the FlexRadio tab). It talks
+the radio's TCP API directly and streams live meters over UDP (VITA-49).
+
+## Slices (multi-slice)
+
+FlexRadios can run several receiver slices (A/B/C/D…). OpsLog shows **every
+in-use slice** as a row of buttons under the header:
+
+- Click a slice to make it the **active** slice — the main frequency, mode, DSP
+ and spot-clicks all follow it.
+- Click a slice's **TX** badge to move the transmitter onto it.
+- **Split** is detected only when the TX slice and a same-band RX slice form a
+ genuine split; independent slices on other bands are not shown as split.
+
+Everything you do targets the **active** slice, so a second slice monitoring
+another band never hijacks your main frequency.
+
+## Header
+
+Model + RX/TX state, plus compact **PA voltage** and **PA temperature** readouts
+(temperature turns amber/red as it climbs).
+
+## Transmit
+
+RF power, tune power, **TUNE**, **MOX**, **SPLIT**, speech processor (NOR / DX /
+DX+), VOX (+ level + delay), monitor (+ level), mic gain, **TX filter** low/high
+cut, and the **mic profile** selector. On CW the panel switches to keyer controls
+(speed / pitch / break-in / sidetone / filter).
+
+## Receive (active slice)
+
+RX/TX antenna, AGC mode + threshold, audio level, and the noise/notch filters:
+**NB**, **WNB** (wideband noise blanker), **NR**, **ANF**, and **APF** on CW —
+each a toggle plus level. Filter-width presets per mode.
+
+## Amplifier (PowerGenius XL)
+
+If a PowerGenius XL is detected, its **OPERATE / STANDBY**, **fan mode** and
+**fault** show in an Amplifier card, with the amp's FWD / ID / TEMP meters
+inline. See [[Amplifiers and Switches]].
+
+## Meters
+
+Live UDP meters under the slices: **S-meter** (S-units, click → fill RST),
+**forward power** (W), **SWR**, **mic** level (−40…+10 dB, red = overdrive) and
+**compression**. PA voltage and temperature are in the header.
+
+## Antennas per band
+
+Configure Flex **RX/TX antennas per band** (Settings → FlexRadio); OpsLog applies
+them automatically on band change.
diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md
new file mode 100644
index 0000000..fe204ea
--- /dev/null
+++ b/wiki/Getting-Started.md
@@ -0,0 +1,59 @@
+# Getting Started
+
+This walks you from a fresh install to your first logged QSO.
+
+## 1. Set your station
+
+**Settings → Station**: enter your **callsign**, **grid locator** and **name**.
+These feed callsign lookups, the map, awards, the QSL card and the "my station"
+ADIF fields on every QSO.
+
+## 2. (Optional) Connect a radio
+
+**Settings → CAT**, pick a backend and fill its fields — see [[CAT Control]].
+Once connected, the frequency, band and mode fill in automatically as you tune,
+and clicking a cluster spot tunes the rig.
+
+You can log perfectly well **without** a radio — just type the band, mode and
+frequency.
+
+## 3. The entry strip
+
+The strip across the top is the heart of OpsLog (Log4OM-style):
+
+- **Callsign** — type it and the contacted entity's **flag** appears next to the
+ RST fields; the country, CQ/ITU zones and continent resolve offline from
+ `cty.dat`. If QRZ/HamQTH lookup is configured, name / QTH / grid auto-fill and
+ a photo shows in the F2 tab.
+- **RST sent / rcvd**, **Name**, **QTH**, **Grid**.
+- **Band / Mode / Frequency** (TX and, for split, RX).
+- **Start / End time**, **Comment / Note**.
+
+Press **Enter** in any text field to **log** the QSO. The contact drops into
+**Recent QSOs** below.
+
+> Mode comes from the radio; the digital sub-mode (FT4 vs FT8) is inferred from
+> the frequency.
+
+## 4. Look around
+
+- **Recent QSOs** — your log; double-click a row to edit every field.
+ See [[Recent QSOs and Filters]].
+- **Worked-before matrix** — as you type a call, see if/when you worked that
+ entity, per band/mode slot.
+- **Main view** — two configurable panes (Settings → General → *Main view*): a
+ great-circle map, the cluster, the FlexRadio or Icom console, Net control, and
+ more. See [[Maps and Antennas]].
+
+## 5. Profiles
+
+Every setting is **per-profile**. A profile can log to the local SQLite file or
+a shared **MySQL** database (multi-operator). Create one per call / station /
+event. See [[Profiles and Databases]].
+
+## Next steps
+
+- Chase spots → [[DX Cluster and Spots]]
+- Track awards → [[Awards]]
+- Send confirmations → [[QSL Management]]
+- Design a card → [[QSL Card Designer]]
diff --git a/wiki/Home.md b/wiki/Home.md
new file mode 100644
index 0000000..e412a3f
--- /dev/null
+++ b/wiki/Home.md
@@ -0,0 +1,41 @@
+# OpsLog
+
+A modern, fast ham-radio logger for **Windows** — Log4OM-style entry, real-time
+CAT for **OmniRig**, native **FlexRadio/SmartSDR**, native **Icom CI-V** (USB
+*and* remote-over-internet, replacing RS-BA1) and **TCI** (SunSDR / Expert
+Electronics), a DX cluster with spot alerts, awards tracking, maps, contest
+logging, QSL management and a QSL-card designer.
+
+Built with **Wails v2** (Go backend + React/TypeScript frontend), **pure Go**
+(no CGO). SQLite holds the configuration; the logbook can be the local SQLite
+file *or* a **shared MySQL** database so several operators run one log. Fully
+themeable and bilingual (English / French).
+
+Developed by **F4BPO**.
+
+## New here? Read these first
+
+1. **[[Installation]]** — download / build and first launch.
+2. **[[Getting Started]]** — the entry strip, logging your first QSO, choosing a
+ profile.
+3. **[[CAT Control]]** — connect your radio (OmniRig, FlexRadio, Icom, TCI).
+
+## What OpsLog does
+
+| Area | Pages |
+|------|-------|
+| Enter and manage QSOs | [[Logging Basics]] · [[Recent QSOs and Filters]] · [[Import and Export ADIF]] |
+| Control your radio | [[CAT Control]] · [[FlexRadio]] · [[Icom]] · [[Remote Icom over the Internet]] |
+| Chase DX | [[DX Cluster and Spots]] · [[Maps and Antennas]] |
+| Station hardware | [[Amplifiers and Switches]] · [[Audio and Keyers]] |
+| Events & contests | [[Contest Logging]] · [[Net Control]] · [[Multi-Operator Live Status]] |
+| Confirmations | [[Awards]] · [[QSL Management]] · [[QSL Card Designer]] |
+| Configuration | [[Settings and Data]] · [[Security]] · [[Profiles and Databases]] |
+| Something wrong? | [[Troubleshooting]] |
+
+## Design in one line
+
+Everything you configure (settings, profiles, rigs, cluster nodes, award lists,
+QSL templates, lookup cache) lives in a **local SQLite** file so the UI is
+instant. Only the **QSOs** live where the active profile points them — local
+SQLite or a shared MySQL. See [[Profiles and Databases]].
diff --git a/wiki/Icom.md b/wiki/Icom.md
new file mode 100644
index 0000000..3d386f8
--- /dev/null
+++ b/wiki/Icom.md
@@ -0,0 +1,55 @@
+# Icom
+
+When the CAT backend is **Icom CI-V** (USB or network), OpsLog shows a full
+RS-BA1-style console — the same panel whether you're on USB or remote.
+
+For setup, see [[CAT Control]]; for the internet link, see
+[[Remote Icom over the Internet]].
+
+## Choosing your model
+
+Pick your model from the **Rig model** dropdown in Settings → CAT — it sets the
+CI-V address and tailors the console to the radio (e.g. attenuator steps).
+The model is also auto-detected from the radio's CI-V ID.
+
+## Console
+
+- **Twin VFO readout** (MAIN / SUB): big tabular frequency, mode badge, band, and
+ RIT/ΔTX offset, with a **mode-button row** (SSB / CW / RTTY / PSK / AM / FM).
+- **Spectrum scope + waterfall**: ON/OFF, CTR/FIX, double-click to tune, and
+ **◀ ⊙ ▶** to centre the scope on the current frequency (±50 kHz) and pan.
+- **Live meters** always visible: S-meter (click → fill RST), power in watts, SWR.
+- **Receive DSP:** AF / RF gain, squelch, AGC, preamp, **attenuator** (steps match
+ the model — e.g. 20 dB on an IC-7300, 6/12/18 dB on an IC-7610), filter
+ (FIL1/2/3), NB, NR, ANF, and — **on CW only** — the **APF** (audio peak filter).
+- **Passband / notch:** Twin PBT (inner / outer) and manual notch + position.
+- **Transmit:** RF power, MOX, TUNE, **split with automatic offset** (+5 kHz SSB /
+ +1 kHz CW), monitor. On **voice modes only**: mic gain, compressor, VOX (+ gain
+ + anti-VOX). Controls that don't apply to the current mode are hidden.
+- **Bands & antenna:** one-touch band buttons, ANT1/ANT2.
+- **Clarifiers:** RIT and ΔTX, wheel / ± tuning (Ctrl + ← / → nudges RIT).
+
+## Power ON / OFF
+
+- **Network:** ON and OFF buttons are shown — the radio's LAN server answers even
+ in standby, so ON works.
+- **USB:** the buttons are **hidden**. Over USB the CI-V interface is unpowered
+ while the rig is off, so a power-ON command can't reach it — turn the radio on
+ physically.
+
+The app never wakes the rig on connect (manual by design).
+
+## CW keying
+
+CW can run through the **radio's own keyer** over CI-V — no extra hardware, and
+it works over the remote link too. See [[Audio and Keyers]].
+
+## Model-specific notes
+
+- **IC-7300 / IC-705 / IC-9100:** single-receiver, single-step attenuator
+ (20 dB); the IC-9100 is USB-only (no LAN server).
+- **IC-7610 / IC-9700:** dual scope (main/sub); stepped attenuator; LAN server
+ for remote.
+- If the **scope is blank on a single-scope rig run at a non-default CI-V
+ address**, OpsLog now detects the model from the CI-V ID, not the configured
+ address, so it decodes single-scope frames correctly.
diff --git a/wiki/Import-and-Export-ADIF.md b/wiki/Import-and-Export-ADIF.md
new file mode 100644
index 0000000..118c142
--- /dev/null
+++ b/wiki/Import-and-Export-ADIF.md
@@ -0,0 +1,32 @@
+# Import and Export ADIF
+
+OpsLog is **ADIF 3.1.7 compliant**: a full field dictionary, 30 promoted columns,
+a generic "extra fields" editor for anything not promoted, and standard/all
+export modes.
+
+## Import
+
+Import an `.adi` / `.adif` file into the active profile's logbook. On import,
+OpsLog parses every field, maps standard fields to columns and keeps unknown but
+valid ADIF fields in a per-QSO **extras** store (editable later). Country / zones
+are enriched from `cty.dat` where missing.
+
+## Export
+
+- **Whole log**, **selected rows**, or the **filtered view** (no limit) — see
+ [[Recent QSOs and Filters]].
+- **Standard** mode exports the common promoted fields; **All** mode also exports
+ every extra field.
+- **Cabrillo** export for contest submission.
+
+## Round-trip safety
+
+Award references you assign manually are stored in an ADIF extra field
+(`APP_OPSLOG_AWARDREFS`), so exporting your full log and re-importing it does not
+lose your award-reference assignments.
+
+## Tips
+
+- To move a batch of QSOs between logs, export selected → import into the other
+ profile.
+- Use **Find duplicates** (Tools) after importing to catch overlaps.
diff --git a/wiki/Installation.md b/wiki/Installation.md
new file mode 100644
index 0000000..f9cafd7
--- /dev/null
+++ b/wiki/Installation.md
@@ -0,0 +1,44 @@
+# Installation
+
+OpsLog is a single Windows executable — no installer, no runtime to add. Pure Go
++ an embedded WebView2 (present on Windows 10/11 by default).
+
+## Download and run
+
+1. Grab the latest `OpsLog.exe` from the project's **Releases** page.
+2. Put it in its own folder (it will create a `data/` folder next to itself).
+3. Double-click to run. On first launch you pick a **language** (English /
+ French) and OpsLog creates its local database.
+
+> **Where is my data?** A `data/` folder is created next to the executable,
+> holding the SQLite config + local logbook, QSL templates, recordings and
+> backups. Keep the exe and `data/` together, or back up `data/`. See
+> [[Settings and Data]].
+
+## Building from source (developers)
+
+OpsLog is built with **Wails v2.11**.
+
+```bash
+# Dev — Vite hot-reload; Go methods reachable at http://localhost:34115
+wails dev
+
+# Production build → build/bin/OpsLog.exe
+wails build
+
+# Regenerate Go↔TS bindings after changing exported App methods
+wails generate module
+```
+
+Prerequisites: Go, Node.js, and the Wails CLI (`~/go/bin/wails.exe`). No CGO /
+C toolchain is needed.
+
+## First-run checklist
+
+- **Settings → General** — language, theme, and the two Main-view panes.
+- **Settings → Station** — your callsign, grid, name (used for lookups, awards,
+ QSL cards and the map).
+- **Settings → CAT** — connect your radio ([[CAT Control]]).
+- **Settings → Lookup** — QRZ.com / HamQTH credentials for callsign lookup.
+
+Continue with **[[Getting Started]]**.
diff --git a/wiki/Logging-Basics.md b/wiki/Logging-Basics.md
new file mode 100644
index 0000000..7bd4bc3
--- /dev/null
+++ b/wiki/Logging-Basics.md
@@ -0,0 +1,45 @@
+# Logging Basics
+
+## The entry strip
+
+The entry strip logs a QSO from any `` inside it — press **Enter** to
+save. Fields: callsign, RST tx/rx, name, QTH, grid, band, mode, TX/RX frequency
+(split), start/end time, comment and note.
+
+- **Callsign** drives everything: the **flag**, offline DXCC resolution (country,
+ CQ/ITU zones, continent) from `cty.dat` including `/MM` `/AM` `/B` and call-area
+ handling (`/8`, `/W6`), plus **ClubLog** DXpedition date overrides.
+- **Callsign lookup** (QRZ.com / HamQTH) fills name / QTH / grid and shows the
+ photo and a QRZ.com tab. Configure it in Settings → Lookup.
+- **ESC** clears the callsign (and stops CW).
+
+## Editing a QSO
+
+Double-click any row in **Recent QSOs** (or Worked-before) to open the full
+editor — every ADIF field across tabs: QSO info, contacted station's details,
+award refs, QSL info, contest, sat/prop, my station, and a generic **extra
+fields** editor for any ADIF field not promoted to a column.
+
+## Bulk operations
+
+Select rows in Recent QSOs, right-click:
+
+- **Fix country & zones** from `cty.dat`, or **update from QRZ / ClubLog**.
+- **Bulk edit field** — set one field across many QSOs (QSL/upload status, my
+ station, contest, propagation, contacted-station refs, comment…). Great for
+ flipping a batch to *Requested* before an upload.
+- **Send to** a QSL service, **export** selected / filtered to ADIF or Cabrillo,
+ **delete**.
+
+## Find duplicates
+
+**Tools → Find duplicates** groups QSOs by same **call + band + mode** (optionally
+same day or same minute) and lets you pick which to delete.
+
+## Notes
+
+- **Worked-before matrix** shows prior contacts with the entity per band/mode
+ slot as you type.
+- Awards references are detected **live** as you enter a call (see [[Awards]]).
+
+See also: [[Recent QSOs and Filters]] · [[Import and Export ADIF]]
diff --git a/wiki/Maps-and-Antennas.md b/wiki/Maps-and-Antennas.md
new file mode 100644
index 0000000..4d76ae3
--- /dev/null
+++ b/wiki/Maps-and-Antennas.md
@@ -0,0 +1,38 @@
+# Maps and Antennas
+
+## Main view
+
+The Main view is **two configurable panes** (per profile, Settings → General →
+*Main view*). Choose from: great-circle map, locator (street) map, the cluster
+grid, the worked-before grid, recent QSOs, the **FlexRadio** controls, the
+**Icom** console, or the **Net control** panel.
+
+## Great-circle map
+
+- Short/long-path **distance & azimuth** to the worked station.
+- Selectable, **key-free** basemaps: Light / Voyager / Street / Satellite (all
+ labelled).
+- The **antenna beam lobe(s)** are drawn from the rotor azimuth.
+
+## Locator (street) map
+
+An OSM street map centred on the contacted grid square — handy for local /
+portable contacts. No API key.
+
+## Rotor compass
+
+An azimuthal-equidistant **click-to-turn** compass driven by **PstRotator**. The
+current heading and target are shown; click a bearing to turn the rotor.
+
+## Ultrabeam
+
+Ultrabeam antennas are supported with three modes — **Normal**, **180° reverse**,
+**Bidirectional**:
+
+- The **radiating direction** is shown in green and the **mechanical boom** in
+ grey, on both the compass and the map, so you always know where the antenna
+ actually points.
+- The antenna **follows the rig frequency** (band change → retune), and retunes
+ immediately when you click a spot.
+
+Related hardware: [[Amplifiers and Switches]] (Antenna Genius switch).
diff --git a/wiki/Multi-Operator-Live-Status.md b/wiki/Multi-Operator-Live-Status.md
new file mode 100644
index 0000000..85b5c78
--- /dev/null
+++ b/wiki/Multi-Operator-Live-Status.md
@@ -0,0 +1,42 @@
+# Multi-Operator Live Status
+
+For a **multi-op special-event call** on a shared MySQL logbook (e.g. TM74TFR),
+OpsLog can publish who is on the air, on which band/mode/frequency, as a live
+page you embed on the station's **QRZ.com** bio.
+
+## Enable it
+
+1. Point the profile's logbook at a **shared MySQL** database
+ ([[Profiles and Databases]]).
+2. **Settings → General → Publish live operator status.**
+
+Each OpsLog instance then heartbeats its current activity (operator call, band,
+frequency, mode) into a `live_status` table every ~15 s. OpsLog only **writes**
+to the database — it is not a web server.
+
+## The web page
+
+A small PHP renderer, `docs/livestatus/tm74-status.php`, reads that table and
+produces a live page/image. Put it on **your own** web server (the one reachable
+from the internet):
+
+1. Edit the credentials at the top: `$DB_HOST`, `$DB_PORT`, `$DB_NAME`,
+ `$DB_USER`, `$DB_PASS` — pointing at the **same** MySQL as OpsLog's logbook.
+2. Embed it on QRZ (QRZ strips `