diff --git a/README.md b/README.md index a0f8f5b..bddba3a 100644 --- a/README.md +++ b/README.md @@ -1,322 +1,77 @@ -# OpsLog +# Transceiver Control Interface - - Join our Discord - +## Introduction -A modern, fast ham-radio logger for Windows — single-strip 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), -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 for configuration, -optional **shared MySQL** for the logbook so several operators can run one log. -Fully themeable and bilingual (English / French). +TCI (Transceiver Control Interface) is a network interface for control, data transfer and +synchronization between transceiver/receiver, contest loggers, digital mode software, skimmers +and other software, as well as external power amplifiers, bandpass filter units, antenna switches, +radio controllers and other devices. -Developed by **F4BPO**. +TCI was created as a modern alternative to the outdated COM port and audio cable +interfaces, it uses a full duplex web socket protocol that runs on top of a TCP connection and +serves for server-client communications, providing cross-platform connectivity. Transceiver works +as a server, all other software and devices as clients. The server and clients can be inside the +same computer (program-server, hardware log, etc.-clients) and/or in separate physical devices +connected through the local network (classical transceiver, power amplifier, antenna switch, FFT +unit, etc.). ---- +The TCI interface contains basic transceiver control commands (analog of CAT system), +receives CW macros from clients and broadcasts them, outputs transceiver IQ stream to clients, +receives spots from skimmers and Internet clusters, receives/outputs audio signal to work in +digital modes. -## Building / developing +The TCI uses an extensible architecture and can be supplemented with new functions and +commands, while keeping the old ones operational. Thus, the TCI interface can be extended and +supplemented to meet the specific needs of any software manufacturer and/or device +manufacturer (receivers, transceivers, power amplifiers, switches, etc.). The presence of a device +identifier allows the manufacturers of transceivers and receivers to switch to the TCI interface +while maintaining the device model designation. The extensibility of the TCI interface allows you +to create an individual set of commands and functions for each device model, while maintaining +the basic command set inherent to all transceivers. -- **Dev:** `wails dev` (Vite hot-reload; Go methods reachable at http://localhost:34115). -- **Build:** `wails build` (use the project's wails v2.11 — `~/go/bin/wails.exe`). -- **Regenerate Go↔TS bindings** after changing exported `App` methods: - `wails generate module`. -- **Release:** `.vscode/release.ps1` (Ctrl+Shift+P → *Tasks: Run Task* → - *Release OpsLog*) — bumps the version, pushes source to Gitea, builds the exe - and publishes it to Gitea + GitHub releases. +Our company advocates universal unification of data exchange between devices and +software by creating the TCI interface for this purpose. Modern transceivers and software must +communicate using one protocol - the TCI protocol. ---- +## Interface description -## Logging +Any command represents an ASCII string that contains a command name and a list of +arguments corresponding to this command. There are reserved characters that cannot be +included in the command name and command arguments. -- **Single-strip entry:** callsign, RST tx/rx, name/QTH/grid, band/mode, - TX/RX frequency (split), start/end time, comment/note. The contacted entity's - **flag** is shown large next to the RST fields. -- **Callsign lookup** (QRZ.com / HamQTH) with photo, auto-fill of name/QTH/grid - and the QRZ.com tab. -- **Offline DXCC** resolution from `cty.dat` (country, CQ/ITU zones, continent), - with `/MM` `/AM` `/B` (beacon) and call-area (`/8`, `/W6`) handling, plus - ClubLog DXpedition date overrides. -- **Recent QSOs**, **Worked-before** matrix (per band/mode slot), bulk re-resolve - from cty/QRZ/ClubLog, bulk send to QSL services. A live **selection count**, a - **Select all / Unselect all** toggle, and a row limit that keeps the full log - fast **but is lifted while a filter is active** (every match is shown, not just - the first page). -- **Advanced QSO filter builder** (field / operator / value, AND / OR, saved - presets) with filtered- and selected-row **ADIF export**. -- **Find duplicates** (Tools) — groups QSOs by same call + band + mode (optionally - same day / minute) and lets you pick which to delete. -- **ADIF 3.1.7 compliant** import/export: a full field dictionary, 30 promoted - columns, a generic "extra fields" editor and standard/all export modes. -- **Profiles:** every setting is per-profile; each profile can point its logbook - at the local SQLite file or a **shared MySQL** database (multi-operator). +List of reserved characters: «:», «,», «;». -## Maps & antenna +Command structure: +1. Name of the command; +2. Separating character between command name and arguments «:»; +3. Separating character between arguments «,»; +4. End of the command character «;». -- **Main view = two configurable panes** (per profile, Settings → General → - *Main view*): 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** with short/long-path distance & azimuth, selectable - basemaps (Light / Voyager / Street / Satellite, all key-free and labelled) and - the **antenna beam lobe(s)** drawn from the rotor azimuth. -- **Rotor compass** (azimuthal-equidistant, click-to-turn) driven by - **PstRotator** (UDP), a **4O3A Rotator Genius** (native TCP) or a **microHAM - ARCO** controlled natively — no PstRotator needed — over the **LAN** or **USB** - using its Yaesu GS-232A protocol. -- **Ultrabeam** support (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 never lose track of where the antenna points. +If a command has no arguments, an end of command symbol is placed after the command +name. If the command is invalid, it is ignored. The case of letters does not matter. -## DX Cluster +The ExpertSDR3 program acts as a server, which can have several client connections at +the same time, they will be synchronized with each other by the server. When connecting to the +ExpertSDR3, the client receives the current status of the ExpertSDR3, first sending initialization +commands, then parameters to set the status, such as frequency, modulation, etc. -- Multiple cluster servers with auto-reconnect, a master for commands. -- **Filter sidebar** (callsign search, hide-worked, group duplicates, band / - mode / status / source) shared by the Cluster tab and the Main-view cluster - pane, with a show/hide toggle. -- Per-spot **status** (new / new-band / new-slot / worked), click-to-tune the - rig, and a multi-band **Band Map** (panadapter-style strips). Optionally, all - **digital modes count as one** (DXCC-style) for the new/new-slot colouring and - the worked-before matrix badges (Settings → General). -- **POTA** spots are tagged with their park reference (via `api.pota.app`). -- **Spot alerts:** rules on call / country / band / mode / - spotter, with sound, visual and e-mail notification (Tools → *Alert - management*). +When a parameter change occurs in the ExpertSDR3 (server) program, the server notifies +all connected clients, i.e., clients do not need to poll the server constantly, any change of state +will be sent in time to all clients. If the client sends a new state, the server will set it to itself, as +well as send it to all clients, that is, the server acts as a synchronizer. All clients connected to the +server will be automatically synchronized. This way of work allows to minimize network load, +reducing traffic. -## CAT control +The TCI protocol implements the transmission of receiver IQ stream to clients, which is +necessary for the work of special skimmer software, they automatically find the station and +decode it throughout the band, and it also allows you to record radio signals in the file. -Four native backends (Settings → CAT), each with auto-reconnect and a fast, -non-blocking connect so a powered-off radio never freezes the app: +TCI is also used to transmit audio signals of the receiver to clients and to receive audio +signals from clients, i.e., the client can transmit audio signals to ExpertSDR3 for radio +transmission. The audio stream exchange is designed to work with digital modes, where encoding +and decoding is performed by third-party software, as well as in voice modes, where audio macros +can be broadcasted, which is very much in demand in contest loggers. -- **OmniRig** (Rig 1/2, hot-swap) — works with any OmniRig-supported rig. -- **FlexRadio (SmartSDR)** over the radio's TCP API — real-time slice freq / - mode / split, UDP discovery, and **panadapter spots** (cluster spots pushed to - the Flex display; a click fills the call and **tunes the rig to the right - frequency AND mode**). -- **Icom CI-V** — native, over the radio's **USB** port *or* over the internet - via the radio's **built-in LAN server** (see *Remote Icom* below). No RS-BA1 or - Remote Utility needed. -- **TCI** (WebSocket) — SunSDR / ExpertSDR2 and any TCI-compatible server: - freq / mode / PTT / split, plus optional panorama spots. - -Mode is taken from the radio; 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. - -### FlexRadio control tab (SmartSDR-style) - -Shown only when the CAT backend is a FlexRadio: - -- **Transmit:** RF power, tune power, TUNE, MOX, speech processor (NOR/DX/DX+), - VOX (+ level + delay), monitor (+ level), mic gain. -- **Receive (active slice):** RX/TX **antenna** selectors and a **DAX** toggle - (TX audio through DAX, for WSJT-X & co), AGC mode/threshold, audio level, - NB / WNB / NR / ANF, and — on **SmartSDR v4** radios (8000 / Aurora series) — - the extra DSP tools **NRL, NRS, NRF** (with level) plus **RNN** (AI noise - reduction) and **ANFT** (FFT auto-notch), shown automatically when the radio - supports them. **RIT / XIT** with wheel / ± tuning. -- **Antenna tuner (ATU):** tune / bypass / memories. -- **Amplifier:** the amp card follows whichever amplifier is configured, with a - dropdown to pick it when **several amplifiers** are set up (e.g. two SPEs run - in parallel). See *Amplifiers & switches* below. -- **Live meters** over the UDP VITA-49 stream: S-meter (S-units), forward power - (W), SWR, ALC, PA temperature, voltage, plus the amplifier's meters. - -### Icom control tab - -Shown when the CAT backend is Icom (USB or network). A full RS-BA1-style console: - -- **Twin VFO readout** (MAIN / SUB) with the big tabular frequency, mode badge, - band and RIT/ΔTX offset, and a **mode-button row** (SSB / CW / RTTY / PSK / - AM / FM). -- **Spectrum scope + waterfall** (panadapter): ON/OFF, CTR/FIX, double-click to - tune, and **◀ ⊙ ▶** buttons to centre the scope on the current frequency - (±50 kHz) and pan left/right. -- **Live meters** always visible: S-meter (click → fill RST), power in watts, SWR. -- **Receive DSP:** AF / RF gain, squelch, AGC, preamp, attenuator, filter - (FIL1/2/3), NB, NR, ANF and — **on CW only** — the **APF** (audio peak filter). -- **Passband / notch:** Twin PBT (inner / outer), manual notch + position. -- **Transmit:** RF power, MOX, TUNE, **split with an automatic offset** - (+5 kHz on SSB, +1 kHz on CW), and monitor. On **voice modes only**: mic gain, - speech compressor, VOX (+ gain + anti-VOX). Controls that don't apply to the - current mode are hidden automatically. -- **Bands & antenna:** one-touch band buttons and ANT1/ANT2 selection. -- **Clarifiers:** RIT and ΔTX with wheel / ± tuning (Ctrl+←/→ nudges RIT). -- **Power ON / OFF** buttons (manual by design — the app never wakes the rig on - connect). -- **CW keying** can run through the radio's own keyer (see *Keyers* below). - -### Remote Icom (over the internet, no RS-BA1) - -OpsLog speaks the IC-7610's built-in network protocol directly — it **replaces -both the Icom Remote Utility and RS-BA1**. Enter the radio's IP, the Network -User1 name/password and the CI-V address, and the whole Icom console works over -the LAN/internet: login + token (auto-renewed), CI-V tunnel, receive-side -retransmit for a rock-solid link even with the panadapter streaming, and manual -power ON/OFF. (Audio is out of scope — use the radio in USB + a voice link such -as Mumble.) - -## Keyers & audio - -- **CW keyer** with macros and F-key macros. The keyer engine is selectable: - **WinKeyer** (K1EL WK1/2/3 over a COM port), **FlexRadio CWX** (the radio's - built-in keyer over the SmartSDR API — type-ahead and backspace, no WinKeyer or - SmartCAT needed), **Icom** (the radio's own keyer over CI-V — no extra hardware, - works over the remote link too) or **TCI**. -- **Digital Voice Keyer** (DVK): record F1–F6 voice messages and transmit them. -- **QSO audio recording:** continuous rolling capture; on *Log QSO* the contact - is saved to a per-QSO WAV (`CALL_YYYYMMDD_HHMMSS.wav`); mixes RX + mic. - -## Amplifiers & switches - -- **Amplifiers** — configure **one or several** amps (Settings → Amplifier is a - list; e.g. two SPEs run in parallel for more power). Each amp's control card - appears on the FlexRadio tab and in **Station Control**, with a dropdown to - choose which one it shows; the bottom status bar carries **one clickable chip - per amp** (green = OPERATE, orange = STANDBY, red = offline). Supported: - - **PowerGenius XL** (4O3A) over direct TCP — operate/standby, fan-mode - selector and fault display. - - **SPE Expert** (1.3K-FA / 1.5K-FA / 2K-FA) over **USB** (virtual COM) or the - **network** (RS232-to-Ethernet bridge) — operate/standby, ON/OFF, - Low / Mid / High output level, an output-power bar and live status (band, - SWR, PA current, temperature, warnings/alarms). - - **ACOM** (500S / 600S / 700S / 1200S / 2020S) over **USB** or the **network** - (RS232-to-Ethernet bridge) — operate/standby/off and live telemetry (forward - & reflected power, SWR, PA temperature, band, fan, faults). Power-ON works - over a serial cable that wires the DTR/RTS lines. -- **Antenna Genius** (4O3A) antenna switch over TCP/GSCP — a docked A/B - antenna-switch widget. -- **Station Control** panel (dockable, drag-to-reorder widgets): the **rotator**, - **Ultrabeam** element control and **relay boards** — WebSwitch 1216H, KMTronic, - **Denkovi** USB (FT245 D2XX bit-bang, 4 or 8 relays) and generic USB-serial - (CH340 / LCUS, A0 protocol) — for station power, antennas and accessories. -- **Relay auto-control** (Settings): switch Station-Control relays automatically - from the rig frequency / band (like PstRotator) — per relay, a frequency window - or a set of bands. - -## QSL & awards - -- **Awards engine:** built-in + custom award definitions (shared **globally** - across profiles) — DXCC, WAS / WAZ / WAC, WPX, IOTA / POTA / SOTA / WWFF, - **DDFM**, worked/confirmed/validated by band & mode, OR rules and manual - reference assignment, live reference detection on call entry, **reference-list - import** for totals/names, and a **Rescan** that re-pulls the logbook (picks up - fresh LoTW/QRZ confirmations). -- **QSL services:** ClubLog (batched ADIF upload), LoTW, QRZ.com, eQSL — upload - and **confirmation download** (which auto-refreshes the award stats). -- **QSL Card Designer** (see below). -- **E-mail eQSL:** right-click a QSO → *Send eQSL by e-mail* via the configured - SMTP account. (Outlook/Hotmail disable basic-auth SMTP — use Gmail with an app - password, or a Microsoft app password.) - -## Contest logging - -- **Contest tab:** pick a contest (built-in ADIF `CONTEST_ID` list) and an - exchange (running serial or a fixed exchange). OpsLog auto-fills `CONTEST_ID` - and the sent/received serials (`STX` / `SRX`), enforces a window start/end, - flags dupes and keeps a live scoreboard. - -## Statistics - -- **Logbook statistics dashboard:** headline tiles (QSOs, unique callsigns, DXCC - entities, continents, % confirmed) plus charts — QSOs **by mode**, a per-band - **CW / phone / data** split, **activity over time** (rolling day / 7-day / - 30-day / 12-month views), by operator and by continent. Date-range, per-operator - and per-contest filters, and a **Table** view that mirrors every chart. - -## Multi-operator live status (special events) - -For a multi-op special-event call on a shared MySQL logbook (e.g. **TM74TFR**), -publishing is **automatic** — no setting to turn on. Each OpsLog instance -heartbeats its current activity (operator call, band, frequency, mode) into a -`live_status` table every ~15 s, and drops back to *off air* automatically 5 min -after the last logged QSO. A small PHP renderer -([`docs/livestatus/tm74-status.php`](docs/livestatus/tm74-status.php)) on your -own web server reads that table and produces a live page/image you can embed on -the station's **QRZ.com** bio (``). OpsLog -only writes to the DB — it is not a web server. - -## Net control - -- **Directed-net logging** (Tools → Net): a global roster (`nets.json`) plus an - in-memory active session — check stations in, then log them individually or the - whole net at once (**Log everyone**) using the CAT frequency. **Drag & drop** - between the two lists (roster → on-air starts a QSO, on-air → roster logs it), - and after each log the next on-air station is selected automatically so you can - chain contacts. - -## Appearance & language - -- **Themes:** four complete themes (Warm light, Warm dark, Graphite dark, High - contrast) plus **Auto** (follows the OS light/dark preference), selectable in - Settings → General. Every panel and every AG-Grid table follows the theme. -- **Bilingual:** full **English / French** UI, with a first-run flag chooser and - a switcher in Settings → General. - -## Security - -- **Secret vault:** opt-in passphrase encryption of the stored passwords - (AES-GCM + PBKDF2). Encrypted values are portable; a single unlock prompt at - launch decrypts them for the session. - -## Integrations (outbound) - -- **UDP emitters:** push the current frequency to **PstRotator**, radio info in - **N1MM `RadioInfo`** format, or an **ADIF record on each logged QSO** — so - external tools (rotator control, digital apps, other loggers) stay in sync. - -## Other - -- **Autostart:** launch external programs (WSJT-X, JTAlert, rotator control…) at - OpsLog startup, skipping any already running. -- **Backup:** optional database + ADIF backup at shutdown. -- **Update check** at startup and every 5 minutes (and on opening Help → About), - with a toast (toggleable), plus a **What's new** dialog that shows the changelog - (English / French) on the first launch after an update — reopenable any time - from the Help menu. -- **Anonymous usage telemetry** (a once-a-day heartbeat: random install ID + - version + OS — no callsign or QSO data; opt-out in Preferences). - ---- - -## QSL Card Designer - -Tools → *QSL Card Designer…* turns a few photos into a polished eQSL card: - -1. Pick 1–6 photos (jpeg/png). OpsLog analyzes them offline (detail/luminance - grid) and proposes **3 designs** — callsign in the calmest zone of the best - photo, operator name, CQ/ITU zones + locator line, country flag, the other - photos as bordered inserts, and a per-QSO confirmation box. -2. Pick a proposal and fine-tune it: click an element to select, drag to move, - change font / style preset (gel gold, gel silver, classic white outline, - script, flat) and per-preset knobs in the right panel. -3. Save the template (photos are copied into `data/qsl/templates//`, so the - originals can move). One template can be the default per profile. - -Sending: right-click a QSO → *Send eQSL by e-mail*. The card is rendered with -that QSO's data, rasterized to a ≤ 800 KB JPEG, archived in `data/qsl/outbox/` -and sent through the configured SMTP account to the address found by the -QRZ/HamQTH lookup. On success the QSO is stamped `EQSL_SENT=Y` (ADIF). The -e-mail subject/body templates live in the designer -(`{CALL} {DATE} {BAND} {MODE} {MYCALL}` variables). - -Fonts: Archivo Black, Lilita One, Baloo 2, Oswald, Great Vibes, Allura (all -OFL, embedded — licenses in `internal/qslcard/assets/fonts/`); Cooper Black is -offered when MS Office installed it. Flags: flag-icons (MIT), embedded for the -commonly-worked DXCC entities. - ---- - -## Data & storage - -- **Config** (settings, profiles, rigs/antennas, cluster nodes, lookup cache, - award lists, QSL templates) always lives in the local SQLite file under - `data/` — instant even when the logbook is on a far-away MySQL. -- **Logbook** (QSOs) lives where the active profile points it: the local SQLite - file or a per-profile shared **MySQL** database. - ---- - -*A French version of this document is available in [README.fr.md](README.fr.md).* +When working in contests, it is important to record all on the air operation, for this purpose +the audio stream from the line-output is sent to all clients. The resulting audio stream can be +recorded to a file or played back with a PC sound card. diff --git a/app.go b/app.go index 1757ea4..0d643db 100644 --- a/app.go +++ b/app.go @@ -8498,6 +8498,11 @@ func (a *App) SaveAudioSettings(s AudioSettings) error { // anything else closes it. Done HERE rather than left to the next restart: // a device chosen in a dropdown that only takes effect after a relaunch // reads as a device that does not work. + // The transmit source follows the "To radio" device, and is applied now: + // the radio is told which input to use at the moment it is keyed, so the + // setting has to be right before the next message rather than after the + // next restart. + a.applyTCITXSource() if s.FromRadio == audio.NetworkDeviceID { a.startTCIRecording() } else if a.tciAudioAvailable() { diff --git a/app_tci_dvk.go b/app_tci_dvk.go index eab1075..1eb1f6e 100644 --- a/app_tci_dvk.go +++ b/app_tci_dvk.go @@ -45,6 +45,31 @@ func (a *App) tciTXPlayer(pcm []byte, rate, ch, bits int, stop <-chan struct{}) return player.PlayTXAudio(pcm, rate, ch, bits, stop) } +// applyTCITXSource tells the radio where to take its transmit audio from, +// following the "To radio" device. +// +// This is the setting an operator would otherwise have to find in ExpertSDR3 +// and set again for every mode, because it is remembered per mode there. It is +// the third argument of TRX, so OpsLog can simply say it each time it keys — +// and a radio told nothing keeps the operator's microphone, which is what +// every PTT that is not a voice message should do. +func (a *App) applyTCITXSource() { + if a.cat == nil { + return + } + cfg, _ := a.GetAudioSettings() + src := "" + if cfg.ToRadio == audio.NetworkDeviceID { + src = "tci" + } + _ = a.cat.TCIAudioDo(func(t cat.TCIAudioController) error { + if s, ok := t.(interface{ SetTXAudioSource(string) }); ok { + s.SetTXAudioSource(src) + } + return nil + }) +} + // installTCITXPlayer offers the radio as an audio output, or withdraws it. // // Withdrawing matters as much as offering: a radio that has gone away must stop @@ -56,5 +81,6 @@ func (a *App) installTCITXPlayer(on bool) { return } audio.SetNetworkPlayer(a.tciTXPlayer) + a.applyTCITXSource() applog.Printf("tci: the radio is available as an audio output — no virtual cable needed for the voice keyer") } diff --git a/changelog.json b/changelog.json index c3bb8c4..bf0815b 100644 --- a/changelog.json +++ b/changelog.json @@ -4,13 +4,13 @@ "date": "", "en": [ "SunSDR / ExpertSDR3: the radio's audio now travels over the TCI link itself. Pick 'Radio (TCI network audio)' as the From Radio or To Radio device and the QSO recorder and the voice keyer work with no virtual cable, no second sound card and nothing to set up in the Windows mixer.", - "For transmit, ExpertSDR3's own transmit audio source must be set to TCI rather than the microphone — it is remembered per mode, so setting it in SSB does not set it in DIGU. OpsLog says so within a fifth of a second rather than transmitting silence.", + "Transmitting over TCI needs no setting up in ExpertSDR3. The audio source is named each time OpsLog keys the radio, so it works in SSB as well as in the digital modes, and the microphone stays the source for every transmission that is not a voice message.", "Awards, RDA district comparison: the list stays where it was put. It was thrown back to the first row every three seconds, which made a long list of contacts to correct impossible to work through.", "Elecraft console: the SWR bar works. The radio answers SW; with three digits in tenths of a ratio — SW023 is 2.3:1 — and OpsLog was reading four, so every answer was discarded and the bar stayed empty." ], "fr": [ "SunSDR / ExpertSDR3 : l'audio de la radio passe désormais par la liaison TCI elle-même. Choisis « Radio (TCI network audio) » comme périphérique From Radio ou To Radio et l'enregistreur de QSO comme le manipulateur vocal fonctionnent sans câble virtuel, sans seconde carte son et sans rien à régler dans le mixeur Windows.", - "Pour l'émission, la source audio d'émission d'ExpertSDR3 doit être réglée sur TCI et non sur le micro — elle est mémorisée par mode, donc la régler en SSB ne la règle pas en DIGU. OpsLog le dit en deux dixièmes de seconde au lieu d'émettre du silence.", + "Émettre par TCI ne demande aucun réglage dans ExpertSDR3. La source audio est nommée à chaque fois qu'OpsLog passe en émission, donc cela fonctionne en SSB comme en numérique, et le micro reste la source pour toute émission qui n'est pas un message vocal.", "Diplômes, comparaison des districts RDA : la liste reste où on l'a laissée. Elle revenait à la première ligne toutes les trois secondes, ce qui rendait impraticable une longue liste de contacts à corriger.", "Console Elecraft : la barre de ROS fonctionne. La radio répond à SW; par trois chiffres en dixièmes de rapport — SW023 vaut 2,3:1 — et OpsLog en lisait quatre, si bien que chaque réponse était jetée et la barre restait vide." ] diff --git a/internal/cat/tci.go b/internal/cat/tci.go index f2f4927..b0b4351 100644 --- a/internal/cat/tci.go +++ b/internal/cat/tci.go @@ -67,6 +67,9 @@ type TCI struct { // never sends TX_ENABLE at all, from being treated as refusing: without a // word from the radio we key and let it decide. txAllowed bool + // txSource is the TRX third argument: "tci" while OpsLog has audio to send, + // empty for the operator's microphone. See SetPTT. + txSource string // drive is the radio's transmit drive, 0-100. Kept because a quiet // transmission has two possible causes — our level or the radio's — and a // log that names both settles it in one line instead of an evening. @@ -344,9 +347,49 @@ func (t *TCI) SetPTT(on bool) error { "check the frequency is inside a transmit band and that TX is enabled in ExpertSDR") } } + // THE THIRD ARGUMENT NAMES THE AUDIO SOURCE, and it is the whole answer to + // "why does the radio ignore what I send it". + // + // TCI 2.0 §TRX: "The signal for transmitting is always taken from the + // microphone selected in the ExpertSDR3. If a third-party software connected + // via TCI wants to transmit its audio signal, you must specify the third + // argument - TCI." Without it the radio never sends a single chrono frame, + // whatever the mode and whatever is configured in its window — which is + // exactly what a night of experiments showed and misread as "digital modes + // only". + // + // Sent only when a transmission is ours to feed. A plain trx keeps the + // operator's own microphone, which is what every other PTT in OpsLog means. + t.mu.Lock() + src := t.txSource + t.mu.Unlock() + if on && src != "" { + return t.send(fmt.Sprintf("trx:0,true,%s;", src)) + } return t.send(fmt.Sprintf("trx:0,%t;", on)) } +// SetTXAudioSource says where the radio should take its transmit audio from +// while OpsLog keys it: "tci" for the stream this program sends, "" for the +// microphone the operator chose in ExpertSDR3. +// +// Set from the audio settings — it follows the "To radio" device — so keying +// for a voice message and keying for anything else behave differently on +// purpose: only the first one takes the audio away from the microphone. +func (t *TCI) SetTXAudioSource(src string) { + t.mu.Lock() + changed := t.txSource != src + t.txSource = src + t.mu.Unlock() + if changed { + if src == "" { + debugLog.Printf("TCI: transmit audio will come from the radio's own microphone") + } else { + debugLog.Printf("TCI: transmit audio will be taken from %s when OpsLog keys the radio", src) + } + } +} + // send writes a command to the WebSocket (one writer at a time). func (t *TCI) send(cmd string) error { t.mu.Lock() diff --git a/internal/cat/tci_audio.go b/internal/cat/tci_audio.go index 9208201..662117f 100644 --- a/internal/cat/tci_audio.go +++ b/internal/cat/tci_audio.go @@ -136,6 +136,13 @@ func (t *TCI) StartTCIAudio(rx, rate int) error { if err := t.send(fmt.Sprintf("audio_samplerate:%d;", rate)); err != nil { return err } + // Said rather than assumed. float32 and two channels are the documented + // defaults and what this radio streams, but a default is a thing another + // program can have changed — they share the radio, not just the protocol — + // and a stream arriving in a format the decoder was not expecting is heard + // as noise, not as a mistake. + _ = t.send("audio_stream_sample_type:float32;") + _ = t.send("audio_stream_channels:2;") return t.send(fmt.Sprintf("audio_start:%d;", rx)) } diff --git a/internal/cat/tci_tx_probe.go b/internal/cat/tci_tx_probe.go index 34d55db..962636b 100644 --- a/internal/cat/tci_tx_probe.go +++ b/internal/cat/tci_tx_probe.go @@ -88,10 +88,13 @@ func (t *TCI) serveChrono(rate, samples int) { } payload := feed(samples) if payload == nil { + // Answered with silence rather than left unanswered. TCI 2.0 §3.4: "the + // client may not send a response or may send a signal with zero counts, + // which corresponds to no signal - this option is preferable." + payload = make([]byte, samples*4) t.audio.mu.Lock() t.audio.txShort++ t.audio.mu.Unlock() - return } if rate <= 0 { rate = 48000 diff --git a/tci.pdf b/tci.pdf new file mode 100644 index 0000000..bfb2982 Binary files /dev/null and b/tci.pdf differ