OscarWatch controls radios for satellite doppler tracking via CAT. Each radio protocol implements IRigDriver. Doppler policy, VFO layout, and pass setup live in RigController; the driver is a thin serial/protocol layer.
HamLib works well for many HF rigs. Satellite tracking needs more than generic frequency and VFO calls: satellite mode, Main/Sub or SAT RX/TX layout, split or exchange, uplink CTCSS, and doppler policy (TX-fixed vs RX-fixed) with per-rig behaviour. HamLib’s model does not map cleanly to that, and satellite features in the field are often thin or wrong.
OscarWatch keeps protocol details in IRigDriver implementations and pass behaviour in RigController, so each supported radio can follow how operators actually run a pass (including quirks such as Kenwood SATL or dual-radio FT-817 setups). A HamLib layer is not planned; new rigs should add a native driver. See also the README section Why not HamLib?
flowchart LR
UI[MainViewModel / Frequency overlay]
RigC[RigController worker thread]
Fact[RigDriverFactory]
Drv[IRigDriver]
HW[Radio CI-V or CAT]
UI --> RigC
RigC --> Fact
Fact --> Drv
Drv --> HW
RigController(OscarWatch/Rig/RigController.cs) — background thread (~100 ms loop): satellite mode, Main/Sub VFO selection, doppler frequency writes, CTCSS, split, FM companion leg, dial-change detection. Linear USB/LSB/CW uses interactive tuning: pause CAT while Main moves, resume after eight stable dial samples (~800 ms), defer Sub uplink writes for 2.5 s after dial activity, andRestoreOperatorVfo()to keep receive VFO selected on ICOM-style rigs.IRigDriver— open port, read/set frequency, VFO, mode, satellite mode, tones.RigSettings(OscarWatch.Core/Models/RigSettings.cs) — port, baud rate, CI-V address, doppler thresholds, CAT delay.IcomCivCodec(OscarWatch.Core/Radio/IcomCivCodec.cs) — encode/decode CI-V frames (Core, no serial I/O).
public interface IRigDriver : IDisposable
{
bool IsConnected { get; }
RigType RigType { get; }
void Open();
long? ReadFrequencyHz(RigVfo vfo);
bool SetFrequencyHz(long hz);
void SelectVfo(RigVfo vfo, bool force = false);
void SetMode(string mode);
void SetSplitOn(bool on);
void SetSatelliteMode(bool on);
void ExchangeVfos();
void SetToneOn(bool on);
void SetToneSquelchOn(bool on);
void SetToneHz(double hz, bool squelchTone);
bool SupportsTracking { get; }
bool SupportsVfoExchange => true;
}| Member | Expectations |
|---|---|
SupportsTracking |
If false, RigController will not run doppler updates (use for rigs that only support a subset of features). |
Open |
Open serial (or other) link. On failure, leave IsConnected false. |
ReadFrequencyHz |
Select VFO if needed, return Hz or null. May cache last good value when offline (see Icom base). |
SetFrequencyHz |
Set frequency on currently selected VFO. Return true if accepted. Validate satellite band in driver or rely on codec. |
SelectVfo |
RigVfo: VfoA, VfoB, Main, Sub — Icom satellite stack uses Main/Sub. |
SetSatelliteMode |
Rig-specific satellite/SAT menu (required for tracking). |
SetMode |
"FM", "USB", etc. — Icom uses CI-V mode bytes. |
SetSplitOn / ExchangeVfos |
Satellite split operation. |
| Tone methods | Sub uplink CTCSS for FM satellites. |
RigController passes RigTrackingContext (from the frequency overlay) with uplink/downlink offsets and database mode; the driver does not compute doppler. Use EffectiveUplinkMode / EffectiveDownlinkMode for SetMode — they apply the panel Voice/CW choice and RigSettings.CwKeepSidebandDownlink via TransponderOperatingModes in Core (drivers should not reimplement that logic).
Most satellite logic is shared in IcomCivDriverBase (OscarWatch/Rig/IcomCivDriverBase.cs):
- Owns
IcomSerialTransport(OscarWatch/Rig/IcomSerialTransport.cs) — framing, retries, read timeout - Implements frequency, VFO, mode, split, tone commands via
IcomCivCodec - Caches per-VFO frequencies when disconnected so UI can still show values
Per-radio subclasses only override what differs, usually SetSatelliteMode:
| Class | RigType |
Satellite mode CI-V |
|---|---|---|
IcomIc910Driver |
IcomIc910 |
1A 07 01 / 00 |
IcomIc9100Driver |
IcomIc9100 |
16 5A 01 / 00 (same as IC-9700) |
IcomIc9700Driver |
IcomIc9700 |
16 5A 01 / 00 |
IcomIc821hDriver |
IcomIc821h |
1A 07 01 / 00; inverted 07 D0/D1 in SAT; split no-op |
IcomIc705Driver |
IcomIc705 |
no-op (dual-radio VFO A only) |
IcomIc7300Driver |
IcomIc7300 |
no-op (dual-radio VFO A only) |
IcomIc905Driver |
IcomIc905 |
no-op (dual-radio VFO A only) |
IcomIc706SeriesDriver |
IcomIc706, IcomIc706Mkii, IcomIc706MkiiG |
no-op (dual-radio VFO A only) |
IC-9700 digital modes: database DATA-USB / DATA-LSB send base SSB (06 01 / 06 00) then DATA on with FIL1 (1A 06 01 01) — USB-D / LSB-D. Command 26 is unavailable in SAT mode; IC-910/9100 keep voice SSB only for DATA-* strings.
IC-910 FM narrow: database FMN sends 06 05 02 (FM + filter 2). Plain FM sends 06 05 01. Generic ICOM mode encoding still maps both to 06 05 (wide); only the IC-910 driver uses the filter byte (Hamlib / SatPC32).
Example new Icom model:
public sealed class IcomIc7600Driver : IcomCivDriverBase
{
public IcomIc7600Driver(string port, int baudRate, string civAddressHex)
: base(RigType.IcomIc7600, port, baudRate, civAddressHex) { }
public override bool SupportsTracking => true; // or false until validated
public override void SetSatelliteMode(bool on) =>
WriteWithRetry(on ? [/* model-specific bytes */] : [/* off */]);
}Confirm bytes against the radio’s CI-V reference manual. Add codec helpers in Core only if multiple rigs share the same frame format.
IcomCivCodecTests— encode/decode frequency and address parsingRecordingRigDriver(OscarWatch.Tests/RecordingRigDriver.cs) — recordsSetFrequencyHz, VFO, tonesRigControllertests — inject(_ => recordingDriver)viaRigControllerconstructor factory parameterDummyRigDriver(OscarWatch/Rig/DummyRigDriver.cs) — in-memory rig for UI/policy tests
For Yaesu, Kenwood, Elecraft, etc.:
- Implement
IRigDriverdirectly inOscarWatch/Rig/(or a subfolder). - Use the manufacturer’s CAT document for serial parameters and commands.
- Map OscarWatch’s
RigVfoto the radio’s VFO/receiver/transmitter semantics. - Set
SupportsTrackingaccurately; implementSetSatelliteModeif the radio has a satellite or split layout equivalent.
Keep protocol parsing in the app project; put only reusable math (frequency validation, doppler) in OscarWatch.Core/Radio/.
| Piece | Path |
|---|---|
| CAT codec | OscarWatch.Core/Radio/YaesuFt847CatCodec.cs |
| Serial transport | OscarWatch/Rig/YaesuCatTransport.cs — 8N2, five-byte frames |
| Driver | OscarWatch/Rig/YaesuFt847Driver.cs |
SetSatelliteMode→ CAT0x4e/0x8e;Main/Submap to SAT RX / SAT TX opcodes (0x11/0x21).SupportsVfoExchangeis false — band swaps need the front-panel A/B switch.- CAT frequency resolution is 10 Hz; CTCSS uses Hamlib’s 0.1 Hz tone table.
- Cross-check commands against Hamlib
ft847.c.
- Radio menu #37: CAT baud matches Settings (often 57600).
- CT-62 (or equivalent) on the CAT/LINEAR jack.
- Two-way CAT firmware (serial 8G05xxxx+).
- On a real pass: SAT mode engages, RX/TX doppler tracks, uplink CTCSS on SAT TX.
| Piece | Path |
|---|---|
| CAT codec | OscarWatch.Core/Radio/YaesuFt817CatCodec.cs |
| Serial transport | OscarWatch/Rig/YaesuCatTransport.cs — 8N2, five-byte frames |
| Driver | OscarWatch/Rig/YaesuFt817Driver.cs, YaesuFt818Driver.cs |
- Dual radio only (
RigSettings.DualRadioEnabled): FT-817/FT-818 are not offered in the single-radio driver list. Each endpoint is one physical radio and one VFO (RX on downlink, TX + CTCSS on uplink). No split CAT is used in this layout. - Band coverage: HF/6 m, 2 m, and 70 cm on both models. There is no software band gate — any satellite frequency the radio accepts is sent over CAT (including AO-07 Mode A on 10 m downlink).
SupportsVfoExchangeis false — VFO B is selected with CAT opcode0x81before TX commands on a single split radio; in dual mode uplink CTCSS stays on Main (VFO A) to match the TX frequency leg.- Cross-check against Hamlib
ft817.c. - CTCSS tone frequency (opcode
0x0B) uses BCD in bytes 1–2 (Hamlib / KA7OEI), not the FT-847 single-byte tone table. - CAT opcode 0x00 is dial lock on, 0x80 is lock off (not “CAT session on/off”).
Open()unlocks the panel;SetModelocks on FM/FMN only so linear USB/LSB/CW can still be spun for passband trim.
- Enable Settings → Radio → Dual radio; configure downlink COM + uplink COM (and rotator on a third port if used).
- Menu #14 CAT rate on each radio must match Settings for that leg (OscarWatch suggests 4800; 38400 also works).
- OscarWatch uses 8N2 Yaesu CAT. One main VFO per radio — downlink for RX, uplink for TX + CTCSS.
- FM: dial lock on via CAT while tracking. USB/LSB/CW: dial unlocked on downlink so you can scan the transponder; uplink doppler continues on the other radio.
- On a real pass: both legs get doppler; CTCSS on uplink only (USA: TSQL for ICOM and most rigs; TS-2000 always encode-only because CT mutes receive).
| Piece | Path |
|---|---|
| Driver | OscarWatch/Rig/IcomIc905Driver.cs |
- Dual radio only (
RigSettings.DualRadioEnabled): IC-905 is not offered in the single-radio driver list. Each endpoint is one physical radio on VFO A (RigController usesMain, mapped to VFO A in the driver). - No dedicated satellite mode —
SetSatelliteModeis a no-op; dual pass init sets mode and frequency directly. - Default CI-V address AC; default baud 115200 (must match radio Set mode).
- Frequency validation includes VHF/UHF/23 cm plus SHF (13 cm / 6 cm / 3 cm) for IC-905 microwave bands.
- Mixed pairs (e.g. IC-905 uplink + SDR downlink) need no special controller logic.
- Enable Settings → Radio → Dual radio; configure each leg (type, COM, baud, CI-V address for IC-905 legs).
- Match CI-V address and baud in the radio Set mode (defaults AC / 115200).
- One COM port per leg — use the USB CI-V serial port.
- On a real pass: both legs get doppler; CTCSS on uplink only.
| Piece | Path |
|---|---|
| Driver | OscarWatch/Rig/IcomIc705Driver.cs |
- Dual radio only (
RigSettings.DualRadioEnabled): IC-705 is not offered in the single-radio driver list. Each endpoint is one physical radio on VFO A (RigController usesMain, mapped to VFO A in the driver). - No dedicated satellite mode —
SetSatelliteModeis a no-op; dual pass init sets mode and frequency directly. - Default CI-V address A4; default baud 115200 (must match radio menu).
- Mixed pairs (e.g. IC-705 downlink + FT-818 uplink) need no special controller logic.
- Enable Settings → Radio → Dual radio; configure each leg (type, COM, baud, CI-V address for IC-705 legs).
- Connectors → CI-V → CI-V USB Port = Link to [CI-V] on each radio (not REMOTE).
- One COM port per leg — use the CI-V-labeled port when Windows shows two.
- On a real pass: both legs get doppler; CTCSS on uplink only.
| Piece | Path |
|---|---|
| Driver | OscarWatch/Rig/IcomIc7300Driver.cs |
- Dual radio only (
RigSettings.DualRadioEnabled): IC-7300 is not offered in the single-radio driver list. Each endpoint is one physical radio on VFO A (RigController usesMain, mapped to VFO A in the driver). - No dedicated satellite mode —
SetSatelliteModeis a no-op; dual pass init sets mode and frequency directly. - Default CI-V address 94; default baud 115200 (must match radio menu).
- HF and 6 m coverage (1.8–54 MHz). Typical use: downlink for AO-07 Mode A (10 m) paired with a 2 m-capable uplink radio (FT-817/818, IC-706, IC-705, etc.).
- Mixed pairs need no special controller logic.
- Enable Settings → Radio → Dual radio; configure each leg (type, COM, baud, CI-V address for IC-7300 legs).
- Match CI-V address and baud in the radio CI-V menu (defaults 94H / 115200).
- One COM port per leg — use the USB CI-V serial port.
- On a real pass: both legs get doppler; CTCSS on uplink only.
| Piece | Path |
|---|---|
| Driver | OscarWatch/Rig/IcomIc706SeriesDriver.cs |
One CI-V driver covers IC-706, IC-706MKII, and IC-706MKIIG as separate dual-radio leg types. OscarWatch uses the same VFO-A command set for all three; only the default CI-V address and band coverage differ. Out-of-band frequency writes are rejected in software (706/MKII: HF/6 m and 2 m; MKIIG adds 70 cm).
| Model | Default CI-V | Bands (satellite-relevant) |
|---|---|---|
| IC-706 | 48H |
HF/6 m and 2m |
| IC-706MKII | 4CH |
HF/6 m and 2m |
| IC-706MKIIG | 58H |
HF/6 m, 2m, and 70cm (AO-07 Mode A downlink on 10 m) |
- Dual radio only — not in the single-radio driver list. Each endpoint is one physical radio on VFO A.
- No dedicated satellite mode —
SetSatelliteModeis a no-op. - Default baud 19200 (must match radio CI-V menu).
- 23cm satellites are outside all three models' hardware.
- Enable Settings → Radio → Dual radio; pick the correct leg type so the default CI-V address matches your radio.
- CI-V via the REMOTE jack (or CT-17). IC-706 / MKII: Initial Set Mode (LOCK at power-on). MKIIG: menus 34–36 (ADDRES / BAUD / TRN On).
- One COM port per leg.
- On a real pass: both legs get doppler; CTCSS on uplink only.
| Piece | Path |
|---|---|
| CAT codec | OscarWatch.Core/Radio/YaesuFt991CatCodec.cs |
| Serial transport | OscarWatch/Rig/YaesuNewCatTransport.cs — 8N2, hardware RTS, semicolon ASCII |
| Driver | OscarWatch/Rig/YaesuFt991Driver.cs, YaesuFt991aDriver.cs |
- Dual radio only: FT-991/991A are not in the single-radio driver list. Downlink legs use VFO-A (
FA,MD0,LK0/1,CT0/CN0). Uplink legs use split (FT3;) and Doppler on VFO-B (FB) so TX frequency can update during keydown. - ASCII newcat commands (Hamlib-compatible subset); default baud 38400 (menu 031).
- FM tracking locks VFO-A dial via
LK1; linear modes useLK0so passband trim works on the downlink leg. - Cross-check against Hamlib
ft991.c.
- Enable Settings → Radio → Dual radio; configure each leg (type, COM, baud).
- Menu 031 CAT RATE must match Settings on each radio.
- Use the USB CAT virtual COM port; hardware RTS is required.
- On a real pass: both legs get doppler; CTCSS on uplink only.
| Piece | Path |
|---|---|
| CAT codec | OscarWatch.Core/Radio/YaesuFt991CatCodec.cs — shared newcat subset |
| Serial transport | OscarWatch/Rig/YaesuNewCatTransport.cs — 8N2, hardware RTS, semicolon ASCII |
| Driver | OscarWatch/Rig/YaesuFtx1Driver.cs |
Covers FTX-1 Field and FTX-1optima (same field head). Downlink uses VFO-A (FA, MD0, LK, CN/CT); uplink uses split and VFO-B (FB) for Doppler during transmit.
- Dual radio only — not in the single-radio driver list.
- Default baud 38400 on CAT-1 (menu CAT-1 RATE; 4800–115200 supported).
- Use the Enhanced COM port (CAT-1) for frequency/mode — not CAT-2 (PTT/CW/digital).
- Cross-check against Hamlib
ftx1.cand the FTX-1 CAT manual.
- Enable Settings → Radio → Dual radio; configure each leg (type, COM, baud).
- Match CAT-1 RATE in the radio menu to Settings on each field head.
- One CAT-1 COM port per leg (and a third port for rotator if used).
- On a real pass: both legs get doppler; CTCSS on uplink only; FM locks the MAIN dial via
LK1.
| Piece | Path |
|---|---|
| CAT codec | OscarWatch.Core/Radio/KenwoodCatCodec.cs |
| Serial transport | OscarWatch/Rig/KenwoodCatTransport.cs — 8N1, hardware RTS by default (Settings toggle), semicolon-terminated ASCII |
| Driver | OscarWatch/Rig/KenwoodTs2000Driver.cs |
- Cross-band SATL for the TS-2000 (
KenwoodTs2000_SatCatReference_A07.txtfield CAT capture):SA1010110;/SA1011110;for CTRL with TRACE on (default), orSA1010000;/SA1011000;when TRACE is off in Settings (noDCin SAT), 2×TO0;,FA;read,TS1;,AI2;, thenAI0;after init; pass programming and SATL doppler steps (FA/FB/SMcluster). While tracking, oneFA;link-hold poll about every second (SatPC32-style), not a burst per doppler step. Beacon / receive-only keeps SATL and updatesFAonly. Exit on quit/disconnect:RX;TO0;SA0010000;— also sent on driverDisposewhen tracking was active. Does not set RF power (PC). Silent set commands do not require a CAT echo;FA;reads wait up to ~450 ms. Main/Sub→FA/FB; noFR/FTorDCin SATL (Hamlib/Gpredict disableFRin SAT for the same reason).SupportsVfoExchangeis true — swapsFA/FBfrequencies in SATL when Main is on the wrong band (same logic as ICOMTryBandSwap).- CTCSS encode:
TN+TO; TSQL squelch:CN+CT(Hamlibts2000_ctcss_list, 1-based index). In SATL,SA1011110;selects Sub CTRL before uplinkMD/tone (notDC01;). After entry/pass setup, best-effortDC10;pins TX/PTT to SUB (CTRL MAIN); ignored if the radio rejectsDCin SATL. - If
SA;does not confirm SATL, OscarWatch still tracks onFA/FB(no split/FR fallback). - Consecutive failed
FA/FBwrites briefly suspend further Doppler CAT to avoid rejection-beep storms. - Cross-check against Hamlib
kenwood.candts2000.txt.
- On the radio: select SAT mode and turn memory mode off before OscarWatch tracking (manual steps — CAT alone is not enough).
- PC CAT port 57600 8N1 with hardware RTS by default (matches Settings; RTS must be asserted on full cables or the radio will not reply). Operators with cables that lack RTS/CTS can turn off Hardware RTS in Settings → Radio.
- TRACE / TRACE REV in SATL SA commands is on by default; turn off TRACE / TRACE REV in SATL in Settings when OscarWatch alone should manage Doppler.
- Close any front-panel menu before tracking; press SAT on the front panel and turn memory mode off (CAT
SAalone is not enough). CAT delay ~20–30 ms helps on the TS-2000. - On a real pass: RX/TX doppler on
FA/FB, uplink CTCSS on Sub.
| Piece | Path |
|---|---|
| Discovery parse | OscarWatch.Core/Radio/FlexDiscoveryCodec.cs |
| Command framing | OscarWatch.Core/Radio/FlexSmartSdrCodec.cs |
| TCP client | OscarWatch/Rig/FlexSmartSdrClient.cs |
| Discovery service | OscarWatch/Rig/FlexDiscoveryService.cs |
| Driver | OscarWatch/Rig/FlexRadioDriver.cs |
- Single-radio only — not a dual-radio endpoint. Settings lists discovered radios (UDP 4992) or accepts a manual host/port (TCP 4992).
SetSatelliteMode(true)→radio set full_duplex_enabled=1, ensure two slices, mark uplinktx=1.Main/Sub→ RX / TX slice tune + mode; CTCSS viafm_tone_mode/fm_tone_valueon the TX slice.SupportsVfoExchangeis false.- Hardware-less tests use
FlexSmartSdrStubServer(same idea as rigctl TCP stubs). - Do not take a FlexLib dependency — protocol subset only (AGPL-friendly, easy to stub).
- Dual-SCU radio on the LAN (e.g. 8600 / 8600M, or dual-SCU 6000-series); optional band→antenna port map in Settings (VHF/UHF RX and TX); otherwise radio ports stay as configured.
- OscarWatch uses the SmartSDR TCP/IP API to the radio (not automation of SmartSDR for Windows).
- Discovery or manual IP; Test SmartSDR connection succeeds.
- On a real pass: FDX on, RX/TX Doppler both move, FM uplink CTCSS correct.
OscarWatch.Core/Models/RigType.cs
Either extend IcomCivDriverBase or create a new class.
OscarWatch/Rig/RigDriverFactory.cs:
public static IRigDriver Create(RigSettings settings) => settings.Type switch
{
RigType.IcomIc910 => new IcomIc910Driver(settings.Port, settings.BaudRate, settings.CivAddress),
RigType.MyRadio => new MyRadioDriver(settings.Port, settings.BaudRate, /* ... */),
_ => new DummyRigDriver()
};In RigSettings.DefaultCivAddressFor if the rig has a non-60 factory address.
SettingsViewModel.cs — RigTypeChoices:
new(RigType.MyRadio, "My Radio Label")Radio tab in SettingsWindow.axaml binds type, port, baud, CI-V address.
- Driver unit tests with a fake transport or recording driver
- Golden tests for any new codec bytes in
OscarWatch.Tests RigControllerintegration tests for doppler threshold and VFO selection (seeRigPolicyTests.cs, rig controller tests)
- Open Settings → Radio, correct COM port and baud
- Enable rig, select satellite, confirm Main/Sub frequencies move with pass
- Toggle CAT pause and standby
- Confirm no COM conflict with rotator on the same port
You rarely call the driver from the UI. Typical sequence on the worker thread:
EnsureConnected→RigDriverFactory.Create→Open- New pass (
RunPassInit) — layout depends on mode (seeRigSatModeHelper.UseMainSubLayoutandSatelliteTransponderMode.IsBeaconOnly):- Cross-band (
downlinkanduplinkboth > 0, >10 MHz apart) →SetSatelliteMode(true),SetSplitOn(false), Main=RX / Sub=TX, optionalExchangeVfos, CTCSS on Sub - Beacon / receive-only (
uplink≤ 0) → ICOM:SetSatelliteMode(false); on IC-910 / IC-9100 / IC-9700 also clear tones on Main+Sub, ensure downlink band on Main (ExchangeVfosif needed), tune and doppler on Main only. Kenwood TS-2000: keep SATL, Doppler onFAonly. - Same-band (both freqs, ≤10 MHz apart) → IC-910/9100/9700: satellite mode off, split on, VFO A/B; IC-821H: satellite mode on, Main/Sub (no split CAT); Kenwood TS-2000: satellite mode off, split on
- Cross-band (
- Each context update →
SelectVfo+SetFrequencyHzwhen doppler delta exceeds threshold (_receiveVfomay be Main, Sub, VfoA, or VfoB) - CTCSS changes →
SetToneHz/ squelch on uplink VFO (skipped whenIsBeaconOnly) - Disconnect / disable → dispose driver
Respect RigSettings.CatDelayMs and thresholds in the controller; the driver should not sleep for doppler pacing unless the protocol requires it (Icom uses short delays inside ReadFrequencyHz).
-
IRigDriverwith correctRigTypeandSupportsTracking -
RigDriverFactorycase - Settings label (+ default CI-V address if Icom)
- Thread-safe serial access (one command at a time)
-
Open/Disposeidempotent and safe - Frequency read/write on Main/Sub or A/B as used by
RigController -
RecordingRigDriveror protocol tests - Manual pass test with real hardware
| File | Role |
|---|---|
OscarWatch.Core/Services/IRigController.cs |
UI-facing rig API |
OscarWatch.Core/Models/RigTrackingContext.cs |
Uplink/downlink offsets for doppler |
OscarWatch.Core/Radio/RigSatModeHelper.cs |
Main/Sub vs A/B layout |
OscarWatch.Core/Radio/DopplerFrequencyCalculator.cs |
Hz math (not serial) |
tools/generate_radio_fixtures.py |
Optional golden CAT fixtures |