From 884daef2933e4dfb46ba0bf3111be3ebc20cbac8 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 11:44:16 -0400 Subject: [PATCH 01/17] Find my radio: settings handoff and config-file clues; dummy-rig console warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HandoffBuilder turns a verified rig into the exact fields for WSJT-X / JS8Call, Fldigi, and Winlink Express, one copyable line each, using the programs' own labels. ConfigClueService reads WSJT-X.ini and JS8Call.ini (Rig, CATSerialPort, CATSerialRate, PTTMethod): the configured port is probed first, the configured radio stands in when none is chosen in the Connection panel, its family and baud move to the front of the sweep, and after verification each program gets a one-line verdict — already matches, or which of Rig / Serial Port / Baud Rate to change. The raw console now prints a warning beside any command sent with Hamlib model 1, the dummy rig, so simulated replies cannot pass as real. 19 strings in five languages. Version 0.6.9. --- App.xaml.cs | 2 + BrandingInfo.cs | 2 +- CHANGELOG.md | 12 +++ Models/Models.cs | 18 +++++ README.md | 2 +- Resources/Strings.de.resx | 19 +++++ Resources/Strings.es.resx | 19 +++++ Resources/Strings.fr.resx | 19 +++++ Resources/Strings.ja.resx | 19 +++++ Resources/Strings.resx | 61 +++++++++++++++ RigCheck.csproj | 6 +- Services/ConfigClueService.cs | 138 ++++++++++++++++++++++++++++++++++ Services/DiscoveryEngine.cs | 31 +++++--- Services/HandoffBuilder.cs | 82 ++++++++++++++++++++ ViewModels/MainViewModel.cs | 27 ++++++- ViewModels/ViewModels.cs | 7 ++ docs/discovery-flow.md | 2 + 17 files changed, 451 insertions(+), 15 deletions(-) create mode 100644 Services/ConfigClueService.cs create mode 100644 Services/HandoffBuilder.cs diff --git a/App.xaml.cs b/App.xaml.cs index e6776ae..55e44cc 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -121,6 +121,8 @@ private static void RegisterServices(IServiceCollection services, SettingsServic services.AddTransient(); services.AddSingleton(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); // ViewModels diff --git a/BrandingInfo.cs b/BrandingInfo.cs index 4fd6021..ba4043b 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.8"; + public const string Version = "0.6.9"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 25870c3..d51e1b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.9] - 2026-09-21 + +Find my radio hands you the settings; Hamlib's dummy rig can no longer pass unnoticed. + +### Added +- Settings handoff after a verified find: the exact fields for WSJT-X / JS8Call (File › Settings › + Radio), Fldigi (Rig Control › Hamlib), and Winlink Express, each as one copyable line +- RigCheck reads what WSJT-X and JS8Call are configured to use (`WSJT-X.ini`, `JS8Call.ini`): + their port is probed first, their radio stands in when none is chosen, and after the sweep + each program gets a one-line verdict — already matches, or exactly which fields to change +- The raw console warns beside every command sent to Hamlib model 1, the dummy rig + ## [0.6.8] - 2026-09-21 Find my radio — the second half of the 0.7.0 milestone, first cut. diff --git a/Models/Models.cs b/Models/Models.cs index cc4ac42..0efa3f1 100644 --- a/Models/Models.cs +++ b/Models/Models.cs @@ -276,3 +276,21 @@ public record ProbeEvent( string Message, string? Bytes = null, // hex dump of what was sent or received DiscoveredRig? Rig = null); + +/// +/// What one of the operator's digital-mode programs is configured to use, +/// read from its own settings file. Values are the program's own strings +/// ("Eight", "Two", "Hardware"), not RigCheck's. +/// +public record ConfigClue( + string App, // "WSJT-X", "JS8Call" + string FilePath, + string RigName, // Hamlib display name, e.g. "Icom IC-7300" + int ModelId, // resolved Hamlib model, 0 if unknown + string Port, // "COM3", or "localhost:4532" for rigctl mode + int Baud, // 0 = not set + string DataBits, + string StopBits, + string Handshake, + string PttMethod, + bool UseRigctld); diff --git a/README.md b/README.md index 83fbbc0..c48ab04 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ model to pick, **Find my radio** works it out: frequency inside an amateur band is taken as its family's most likely model. 4. Every find is verified with the same Hamlib test suite as **Run Tests**, and the best verified one is written into the Connection panel. -5. A rigctld already listening on 4532 is reported as a working connection; Flrig on 12345 +6. A rigctld already listening on 4532 is reported as a working connection; Flrig on 12345 is noted. Everything sent and received is shown in the results panel, with a Copy button per line, and diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index 5ab924f..dcc14af 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -378,4 +378,23 @@ Fortfahren? {0} an {1}: {2} Test(s) fehlgeschlagen Verbindungseinstellungen auf {0} (Modell {1}), {2}, {3} Baud gesetzt — Tests ausführen verwendet diese jetzt PROTOKOLL „MEIN FUNKGERÄT FINDEN“ + Modell 1 ist das Dummy-Gerät von Hamlib — jede Antwort unten ist simuliert, nicht von einem Funkgerät + Einzugebende Einstellungen — Zeile für Ihr Programm kopieren + WSJT-X und JS8Call: File › Settings › Radio + Rig: {0} · Serial Port: {1} · Baud Rate: {2} · Data Bits: Eight · Stop Bits: {3} · Handshake: None · PTT Method: {4} + Rig: Hamlib NET rigctl · Network Server: {0} + Fldigi: Configure › Rig Control › Hamlib + Rig: {0} · Device: {1} · Baud rate: {2} · Stopbits: {3} · PTT via Hamlib command + Rig: Hamlib NET rigctl · Device: {0} + Winlink Express: Session › Radio Setup (eigene Geräteliste — das nächstliegende Modell wählen) + Radio: {0} · Serial Port: {1} · Baud: {2} · PTT: {3} + {0} ist für {1} an {2} mit {3} Baud konfiguriert + {0} ist auf rigctl unter {1} konfiguriert + {0} hat diese Einstellungen bereits + {0} ist auf {1} an {2} mit {3} Baud eingestellt — ändern Sie {4} + Rig auf {0} + Serial Port auf {0} + Baud Rate auf {0} + {0} verwendet rigctl ({1}) — das funktioniert, solange rigctld läuft + {0} ist auf {1} an {2} mit {3} Baud eingestellt — so lassen oder auf Hamlib NET rigctl umstellen, um das Funkgerät über rigctld zu teilen diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index 18514bb..435ece0 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -378,4 +378,23 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel {0} en {1}: {2} prueba(s) fallaron Panel de Conexión establecido en {0} (modelo {1}), {2}, {3} baudios — Ejecutar pruebas usa estos valores ahora TRANSCRIPCIÓN DE BUSCAR MI RADIO + El modelo 1 es la radio ficticia de Hamlib — todas las respuestas de abajo son simuladas, no de una radio + Ajustes que debe introducir — copie la línea de su programa + WSJT-X y JS8Call: File › Settings › Radio + Rig: {0} · Serial Port: {1} · Baud Rate: {2} · Data Bits: Eight · Stop Bits: {3} · Handshake: None · PTT Method: {4} + Rig: Hamlib NET rigctl · Network Server: {0} + Fldigi: Configure › Rig Control › Hamlib + Rig: {0} · Device: {1} · Baud rate: {2} · Stopbits: {3} · PTT via Hamlib command + Rig: Hamlib NET rigctl · Device: {0} + Winlink Express: Session › Radio Setup (tiene su propia lista de radios — elija el modelo más cercano) + Radio: {0} · Serial Port: {1} · Baud: {2} · PTT: {3} + {0} está configurado para {1} en {2} a {3} baudios + {0} está configurado para usar rigctl en {1} + {0} ya tiene estos ajustes + {0} está configurado con {1} en {2} a {3} baudios — cambie {4} + Rig a {0} + Serial Port a {0} + Baud Rate a {0} + {0} usa rigctl ({1}) — eso funciona siempre que rigctld esté en ejecución + {0} está configurado con {1} en {2} a {3} baudios — déjelo así, o cámbielo a Hamlib NET rigctl para compartir la radio a través de rigctld diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index d718c9b..3692cdb 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -378,4 +378,23 @@ Continuer ? {0} sur {1} : {2} test(s) échoué(s) Panneau Connexion réglé sur {0} (modèle {1}), {2}, {3} bauds — Lancer les tests utilise ces réglages maintenant TRANSCRIPTION DE TROUVER MA RADIO + Le modèle 1 est la radio factice de Hamlib — toutes les réponses ci-dessous sont simulées, pas issues d'une radio + Réglages à saisir — copiez la ligne de votre programme + WSJT-X et JS8Call : File › Settings › Radio + Rig: {0} · Serial Port: {1} · Baud Rate: {2} · Data Bits: Eight · Stop Bits: {3} · Handshake: None · PTT Method: {4} + Rig: Hamlib NET rigctl · Network Server: {0} + Fldigi : Configure › Rig Control › Hamlib + Rig: {0} · Device: {1} · Baud rate: {2} · Stopbits: {3} · PTT via Hamlib command + Rig: Hamlib NET rigctl · Device: {0} + Winlink Express : Session › Radio Setup (sa propre liste de radios — choisissez le modèle le plus proche) + Radio: {0} · Serial Port: {1} · Baud: {2} · PTT: {3} + {0} est configuré pour {1} sur {2} à {3} bauds + {0} est configuré pour utiliser rigctl à {1} + {0} a déjà ces réglages + {0} est réglé sur {1} sur {2} à {3} bauds — changez {4} + Rig en {0} + Serial Port en {0} + Baud Rate en {0} + {0} utilise rigctl ({1}) — cela fonctionne tant que rigctld tourne + {0} est réglé sur {1} sur {2} à {3} bauds — gardez cela, ou passez à Hamlib NET rigctl pour partager la radio via rigctld diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index c01581a..fbc4bfa 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -378,4 +378,23 @@ {1} の {0}: {2} 件のテストが失敗 「接続」パネルを {0}(モデル {1})、{2}、{3} ボーに設定しました — 「テストを実行」はこの設定を使います 「無線機を探す」の記録 + モデル 1 は Hamlib のダミーリグです — 以下の応答はすべてシミュレーションで、無線機からのものではありません + 入力する設定 — お使いのプログラムの行をコピーしてください + WSJT-X と JS8Call: File › Settings › Radio + Rig: {0} · Serial Port: {1} · Baud Rate: {2} · Data Bits: Eight · Stop Bits: {3} · Handshake: None · PTT Method: {4} + Rig: Hamlib NET rigctl · Network Server: {0} + Fldigi: Configure › Rig Control › Hamlib + Rig: {0} · Device: {1} · Baud rate: {2} · Stopbits: {3} · PTT via Hamlib command + Rig: Hamlib NET rigctl · Device: {0} + Winlink Express: Session › Radio Setup(独自の無線機一覧 — 最も近い機種を選択) + Radio: {0} · Serial Port: {1} · Baud: {2} · PTT: {3} + {0} は {2} の {1}({3} ボー)に設定されています + {0} は {1} の rigctl を使う設定です + {0} はすでにこの設定です + {0} は {2} の {1}({3} ボー)に設定されています — {4} に変更してください + Rig を {0} + Serial Port を {0} + Baud Rate を {0} + {0} は rigctl({1})を使用します — rigctld が動作していれば動きます + {0} は {2} の {1}({3} ボー)に設定されています — そのままにするか、rigctld 経由で無線機を共有するために Hamlib NET rigctl に切り替えてください diff --git a/Resources/Strings.resx b/Resources/Strings.resx index e286e78..f065519 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -1213,4 +1213,65 @@ Continue? FIND MY RADIO TRANSCRIPT + + Model 1 is Hamlib's dummy rig — every reply below is simulated, not from a radio + Shown in the raw console whenever a command is sent with Hamlib model 1. + + + Settings to enter — copy the line for your program + + + WSJT-X and JS8Call: File › Settings › Radio + + + Rig: {0} · Serial Port: {1} · Baud Rate: {2} · Data Bits: Eight · Stop Bits: {3} · Handshake: None · PTT Method: {4} + Field labels as they appear in WSJT-X. Translate only if WSJT-X is translated in your language. {0} rig name, {1} port, {2} baud, {3} stop bits, {4} PTT method. + + + Rig: Hamlib NET rigctl · Network Server: {0} + + + Fldigi: Configure › Rig Control › Hamlib + + + Rig: {0} · Device: {1} · Baud rate: {2} · Stopbits: {3} · PTT via Hamlib command + Field labels as they appear in Fldigi. {0} rig, {1} device, {2} baud, {3} stop bits. + + + Rig: Hamlib NET rigctl · Device: {0} + + + Winlink Express: Session › Radio Setup (its own radio list — pick the nearest model) + + + Radio: {0} · Serial Port: {1} · Baud: {2} · PTT: {3} + + + {0} is configured for {1} on {2} at {3} baud + + + {0} is configured to use rigctl at {1} + + + {0} already has these settings + + + {0} is set to {1} on {2} at {3} baud — change {4} + {0} program, {1} its configured rig, {2} its port, {3} its baud, {4} comma list built from Handoff_DiffRig/Port/Baud. + + + Rig to {0} + + + Serial Port to {0} + + + Baud Rate to {0} + + + {0} uses rigctl ({1}) — that works whenever rigctld is running + + + {0} is set to {1} on {2} at {3} baud — keep that, or switch it to Hamlib NET rigctl to share the radio through rigctld + diff --git a/RigCheck.csproj b/RigCheck.csproj index b3c5b3d..5110896 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.8 - 0.6.8.0 - 0.6.8.0 + 0.6.9 + 0.6.9.0 + 0.6.9.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/ConfigClueService.cs b/Services/ConfigClueService.cs new file mode 100644 index 0000000..700f23a --- /dev/null +++ b/Services/ConfigClueService.cs @@ -0,0 +1,138 @@ +using RigCheck.Models; +using Serilog; +using System.IO; + +namespace RigCheck.Services; + +/// +/// Reads what the operator's own digital-mode programs are already +/// configured to use. This is the best clue Find my radio has: most of the +/// time the model is right and only the port or the speed is wrong, and +/// "WSJT-X says COM5 at 9600, but the radio answered on COM3 at 19200" is +/// exactly the sentence the operator needs. +/// +/// Read-only, file-based, no process interaction. Handles WSJT-X and +/// JS8Call (same INI layout). Fldigi stores baud as a list index and the +/// model as its own numbering, so it is left for a later pass. +/// +public class ConfigClueService +{ + private readonly RadioPresetsService _presets; + private readonly DiscoveryDataService _data; + + public ConfigClueService(RadioPresetsService presets, DiscoveryDataService data) + { + _presets = presets; + _data = data; + } + + /// Every configuration RigCheck could read, in the order it looked. + public IReadOnlyList ReadAll() + { + var local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var clues = new List(); + + foreach (var (app, path) in new[] + { + ("WSJT-X", Path.Combine(local, "WSJT-X", "WSJT-X.ini")), + ("JS8Call", Path.Combine(local, "JS8Call", "JS8Call.ini")), + }) + { + var clue = ReadWsjtxStyle(app, path); + if (clue is not null) clues.Add(clue); + } + return clues; + } + + // ── WSJT-X / JS8Call ────────────────────────────────────────────────── + // [Configuration] + // Rig=Icom IC-7300 Hamlib's rig name, or "Hamlib NET rigctl" + // CATSerialPort=COM3 + // CATSerialRate=19200 + // CATDataBits=Default|Seven|Eight + // CATStopBits=Default|One|Two + // CATHandshake=Default|None|XonXoff|Hardware + // PTTMethod=CAT|DTR|RTS|VOX + // NetworkServer=localhost:4532 (rigctl mode) + + private ConfigClue? ReadWsjtxStyle(string app, string path) + { + try + { + if (!File.Exists(path)) return null; + + var cfg = ReadIniSection(path, "Configuration"); + if (cfg.Count == 0) return null; + + var rigName = cfg.GetValueOrDefault("Rig", string.Empty); + if (string.IsNullOrEmpty(rigName) || rigName == "None") return null; + + var useRigctld = rigName.Contains("NET rigctl", StringComparison.OrdinalIgnoreCase); + int.TryParse(cfg.GetValueOrDefault("CATSerialRate", "0"), out var baud); + + return new ConfigClue( + App: app, + FilePath: path, + RigName: rigName, + ModelId: useRigctld ? 2 : ResolveModel(rigName), + Port: useRigctld ? cfg.GetValueOrDefault("NetworkServer", "localhost:4532") + : cfg.GetValueOrDefault("CATSerialPort", string.Empty), + Baud: baud, + DataBits: cfg.GetValueOrDefault("CATDataBits", "Default"), + StopBits: cfg.GetValueOrDefault("CATStopBits", "Default"), + Handshake: cfg.GetValueOrDefault("CATHandshake", "Default"), + PttMethod: cfg.GetValueOrDefault("PTTMethod", "CAT"), + UseRigctld: useRigctld); + } + catch (Exception ex) + { + Log.Debug(ex, "Could not read {App} configuration at {Path}", app, path); + return null; + } + } + + // WSJT-X stores Hamlib's display name ("Icom IC-7300"). Presets and the + // identity table use the same convention, so a name match is usually + // exact; fall back to a loose contains-match on the model part. + private int ResolveModel(string rigName) + { + var preset = _presets.Presets.FirstOrDefault(p => p.Name.Equals(rigName, StringComparison.OrdinalIgnoreCase)) + ?? _presets.Presets.FirstOrDefault(p => rigName.Contains(ModelPart(p.Name), StringComparison.OrdinalIgnoreCase)); + if (preset is not null) return preset.HamlibModelId; + + var id = _data.Ids.FirstOrDefault(i => i.Name.Equals(rigName, StringComparison.OrdinalIgnoreCase)) + ?? _data.Ids.FirstOrDefault(i => rigName.Contains(ModelPart(i.Name), StringComparison.OrdinalIgnoreCase)); + return id?.HamlibModelId ?? 0; + } + + // "Icom IC-7300" → "IC-7300" + private static string ModelPart(string name) + { + var space = name.IndexOf(' '); + return space > 0 ? name[(space + 1)..] : name; + } + + private static Dictionary ReadIniSection(string path, string section) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + var inScope = false; + + foreach (var raw in File.ReadLines(path)) + { + var line = raw.Trim(); + if (line.Length == 0 || line[0] is ';' or '#') continue; + + if (line[0] == '[') + { + inScope = line.Equals($"[{section}]", StringComparison.OrdinalIgnoreCase); + continue; + } + if (!inScope) continue; + + var eq = line.IndexOf('='); + if (eq <= 0) continue; + result[line[..eq].Trim()] = line[(eq + 1)..].Trim(); + } + return result; + } +} diff --git a/Services/DiscoveryEngine.cs b/Services/DiscoveryEngine.cs index f2ee9f3..e1e1398 100644 --- a/Services/DiscoveryEngine.cs +++ b/Services/DiscoveryEngine.cs @@ -11,7 +11,11 @@ public record DiscoveryRequest( IReadOnlyList Ports, // already ticked by the operator int PreferredModelId = 0, // from the Connection panel, 0 = none int PreferredBaud = 0, // 0 = radio default / unknown - bool CheckNetwork = true); // rigctld and Flrig ports + bool CheckNetwork = true, // rigctld and Flrig ports + IReadOnlyList Clues = null!) // what WSJT-X etc. are configured for +{ + public IReadOnlyList Clues { get; init; } = Clues ?? []; +} /// /// Find my radio, stages 0–2: inventory the ticked ports, rank protocol @@ -121,17 +125,25 @@ private static IEnumerable CheckNetworkTransports() // ── Stage 1: ranking ────────────────────────────────────────────────── // Family order: the family of the radio already chosen in the Connection - // panel, then the family the cable's VID/PID hints at, then by weight. - // Baud order is the family's list, with the operator's chosen baud - // moved to the front for that family. + // panel, then the family WSJT-X / JS8Call are configured for, then the + // family the cable's VID/PID hints at, then by weight. Baud order is the + // family's list, with the operator's chosen baud and any configured + // baud moved to the front for their families — the common failure is + // "right radio, wrong port or speed", so the configured values are the + // best first guess and the configured port is probed first by the caller. private List<(RigFamily Family, int Baud)> RankCandidates(ComPortInfo port, DiscoveryRequest request) { var preferred = _data.FamilyForModel(request.PreferredModelId); var hinted = _data.FamilyForVendor(port.RadioFamily); + var clueFamilies = request.Clues + .Select(c => (Family: _data.FamilyForModel(c.ModelId), c.Baud)) + .Where(x => x.Family is not null) + .ToList(); var families = _data.Families - .OrderByDescending(f => f == preferred ? 2 : 0) + .OrderByDescending(f => f == preferred ? 3 : 0) + .ThenByDescending(f => clueFamilies.Any(c => c.Family == f) ? 2 : 0) .ThenByDescending(f => f == hinted ? 1 : 0) .ThenByDescending(f => f.Weight) .ToList(); @@ -139,10 +151,11 @@ private static IEnumerable CheckNetworkTransports() var list = new List<(RigFamily, int)>(); foreach (var family in families) { - IEnumerable bauds = family.Bauds; - if (family == preferred && request.PreferredBaud > 0) - bauds = new[] { request.PreferredBaud }.Concat(family.Bauds.Where(b => b != request.PreferredBaud)); - foreach (var baud in bauds) + var first = new List(); + if (family == preferred && request.PreferredBaud > 0) first.Add(request.PreferredBaud); + first.AddRange(clueFamilies.Where(c => c.Family == family && c.Baud > 0).Select(c => c.Baud)); + + foreach (var baud in first.Distinct().Concat(family.Bauds.Where(b => !first.Contains(b)))) list.Add((family, baud)); } return list; diff --git a/Services/HandoffBuilder.cs b/Services/HandoffBuilder.cs new file mode 100644 index 0000000..a9dc780 --- /dev/null +++ b/Services/HandoffBuilder.cs @@ -0,0 +1,82 @@ +using RigCheck.Localization; +using RigCheck.Models; + +namespace RigCheck.Services; + +/// +/// Stage 4 of Find my radio — the deliverable. Turns a verified rig into +/// the exact fields to enter in WSJT-X / JS8Call, Fldigi, and Winlink +/// Express, and compares them with what those programs are configured to +/// use today so the operator sees precisely what to change. +/// +/// Field names are the programs' own labels. They are resources so a +/// translator can match a localized WSJT-X, but they are not RigCheck's +/// words to change. +/// +public class HandoffBuilder +{ + private readonly DiscoveryDataService _data; + + public HandoffBuilder(DiscoveryDataService data) => _data = data; + + public IReadOnlyList Build(DiscoveredRig rig, IReadOnlyList clues) + { + var lines = new List(); + var family = _data.Family(rig.FamilyId); + var stopBits = family?.HandoffStopBits ?? rig.HandoffStopBits; + var ptt = clues.FirstOrDefault(c => !string.IsNullOrEmpty(c.PttMethod))?.PttMethod ?? "CAT"; + + lines.Add(new(TranscriptKind.Found, Strings.Get("Handoff_Heading"))); + + if (rig.UseRigctld) + { + lines.Add(new(TranscriptKind.Note, Strings.Get("Handoff_WsjtxPath"))); + lines.Add(new(TranscriptKind.Command, Strings.Format("Handoff_WsjtxNet", rig.Port))); + lines.Add(new(TranscriptKind.Note, Strings.Get("Handoff_FldigiPath"))); + lines.Add(new(TranscriptKind.Command, Strings.Format("Handoff_FldigiNet", rig.Port))); + } + else + { + lines.Add(new(TranscriptKind.Note, Strings.Get("Handoff_WsjtxPath"))); + lines.Add(new(TranscriptKind.Command, Strings.Format("Handoff_Wsjtx", rig.ModelName, rig.Port, rig.Baud, stopBits, ptt))); + lines.Add(new(TranscriptKind.Note, Strings.Get("Handoff_FldigiPath"))); + lines.Add(new(TranscriptKind.Command, Strings.Format("Handoff_Fldigi", rig.ModelName, rig.Port, rig.Baud, stopBits))); + lines.Add(new(TranscriptKind.Note, Strings.Get("Handoff_WinlinkPath"))); + lines.Add(new(TranscriptKind.Command, Strings.Format("Handoff_Winlink", rig.ModelName, rig.Port, rig.Baud, ptt))); + } + + // ── Configured vs. what actually works ──────────────────────────── + foreach (var clue in clues) + lines.Add(new(TranscriptKind.Note, Compare(clue, rig))); + + return lines; + } + + /// One sentence per program: matches, works via rigctl, or what to change. + private static string Compare(ConfigClue clue, DiscoveredRig rig) + { + if (clue.UseRigctld) + return Strings.Format("Handoff_DiffRigctl", clue.App, clue.RigName); + + if (rig.UseRigctld) + return Strings.Format("Handoff_DiffSerialVsRigctld", clue.App, clue.RigName, clue.Port, clue.Baud); + + var changes = new List(); + if (clue.ModelId != rig.HamlibModelId) + changes.Add(Strings.Format("Handoff_DiffRig", rig.ModelName)); + if (!clue.Port.Equals(rig.Port, StringComparison.OrdinalIgnoreCase)) + changes.Add(Strings.Format("Handoff_DiffPort", rig.Port)); + if (clue.Baud != rig.Baud) + changes.Add(Strings.Format("Handoff_DiffBaud", rig.Baud)); + + return changes.Count == 0 + ? Strings.Format("Handoff_DiffMatch", clue.App) + : Strings.Format("Handoff_DiffChange", clue.App, clue.RigName, clue.Port, clue.Baud, string.Join(", ", changes)); + } + + /// The opening line for each program whose settings were read, shown before the sweep. + public static string Describe(ConfigClue clue) => + clue.UseRigctld + ? Strings.Format("Handoff_ClueRigctl", clue.App, clue.Port) + : Strings.Format("Handoff_Clue", clue.App, clue.RigName, clue.Port, clue.Baud); +} diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 43907ea..052720f 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -19,6 +19,8 @@ public partial class MainViewModel : ObservableObject private readonly TestRunnerService _testRunner; private readonly EnvironmentCheckService _envCheck; private readonly DiscoveryEngine _discovery; + private readonly ConfigClueService _clues; + private readonly HandoffBuilder _handoff; private readonly LogExportService _logExport; private readonly HamlibLocatorService _hamlib; private readonly SettingsService _settings; @@ -66,6 +68,8 @@ public MainViewModel( TestRunnerService testRunner, EnvironmentCheckService envCheck, DiscoveryEngine discovery, + ConfigClueService clues, + HandoffBuilder handoff, LogExportService logExport, HamlibLocatorService hamlib, SettingsService settings, @@ -77,6 +81,8 @@ public MainViewModel( _testRunner = testRunner; _envCheck = envCheck; _discovery = discovery; + _clues = clues; + _handoff = handoff; _logExport = logExport; _hamlib = hamlib; _settings = settings; @@ -216,7 +222,21 @@ private async Task FindRadioAsync(IReadOnlyList ports) try { using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); - var request = new DiscoveryRequest(ports, Connection.ModelId, Connection.BaudRate); + // What the operator's own programs are set to is the best first + // guess: their port is probed first, and their radio stands in + // for the Connection panel when nothing is chosen there. + var clues = _clues.ReadAll(); + foreach (var clue in clues) + Results.AddTranscript(TranscriptKind.Note, HandoffBuilder.Describe(clue)); + + var orderedPorts = ports + .OrderByDescending(p => clues.Any(c => c.Port.Equals(p.PortName, StringComparison.OrdinalIgnoreCase))) + .ToList(); + var preferredModel = Connection.ModelId > 1 + ? Connection.ModelId + : clues.FirstOrDefault(c => c.ModelId > 1)?.ModelId ?? 0; + + var request = new DiscoveryRequest(orderedPorts, preferredModel, Connection.BaudRate, Clues: clues); Results.AddTranscript(TranscriptKind.Note, Strings.Format("Disc_Start", ports.Count)); @@ -266,6 +286,11 @@ private async Task FindRadioAsync(IReadOnlyList ports) Results.SetSuiteResult(best.Suite); Results.AddTranscript(TranscriptKind.Found, Strings.Format("Disc_Applied", best.Rig.ModelName, best.Rig.HamlibModelId, best.Rig.Port, best.Rig.Baud)); + + // Stage 4: the settings to type into each program, and how + // they differ from what those programs use today. + foreach (var line in _handoff.Build(best.Rig, clues)) + Results.AddTranscript(line.Kind, line.Text); StatusMessage = verified.Count == 1 ? Strings.Format("Status_DiscoveredOne", best.Rig.ModelName, best.Rig.Port) : Strings.Format("Status_DiscoveredMany", verified.Count, best.Rig.ModelName, best.Rig.Port); diff --git a/ViewModels/ViewModels.cs b/ViewModels/ViewModels.cs index b44b91a..607127a 100644 --- a/ViewModels/ViewModels.cs +++ b/ViewModels/ViewModels.cs @@ -402,6 +402,13 @@ private async Task SendCommandAsync(ConnectionConfig cfg) var cmd = RigctlCommandBuilder.RawCommand(cfg, input); AddEntry(ConsoleEntryKind.Command, cmd.DisplayCommand); + // The test suite refuses model 1, but the console lets an operator + // type anything. Model 1 is Hamlib's dummy rig: every reply is + // simulated, so say so beside each command rather than let a + // convincing-looking answer stand. + if (cfg.ModelId == 1 && !cfg.UseRigctld) + AddEntry(ConsoleEntryKind.Error, Strings.Get("Console_DummyRig")); + IsRunning = true; try { diff --git a/docs/discovery-flow.md b/docs/discovery-flow.md index 8c3f85e..b951c12 100644 --- a/docs/discovery-flow.md +++ b/docs/discovery-flow.md @@ -182,6 +182,8 @@ reproduce it with a terminal program. ## Not in scope for the first version - OmniRig and LAN-connected rigs (inventory only — no probe). +- Fldigi's own configuration as a clue (it stores baud as a list index and + models in its own numbering); WSJT-X and JS8Call are read. - Telemetry-weighted priors (needs volume first). - Aggressive probing (see below). From 756fe2786ed9c4ee82627bca4929135cd1854d5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:44:32 +0000 Subject: [PATCH 02/17] docs: update translation coverage --- TRANSLATING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TRANSLATING.md b/TRANSLATING.md index d53c433..7b62b92 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -16,11 +16,11 @@ starting from nothing, and it is the most useful thing you can do. | Language | File | Coverage | Status | | --- | --- | --- | --- | -| English (source) | `Strings.resx` | 100% (343 of 343) | Source | -| German (`de`) | `Strings.de.resx` | 100% (343 of 343) | Machine translated — review wanted | -| Spanish (`es`) | `Strings.es.resx` | 100% (343 of 343) | Machine translated — review wanted | -| French (`fr`) | `Strings.fr.resx` | 100% (343 of 343) | Machine translated — review wanted | -| Japanese (`ja`) | `Strings.ja.resx` | 100% (343 of 343) | Machine translated — review wanted | +| English (source) | `Strings.resx` | 100% (362 of 362) | Source | +| German (`de`) | `Strings.de.resx` | 100% (362 of 362) | Machine translated — review wanted | +| Spanish (`es`) | `Strings.es.resx` | 100% (362 of 362) | Machine translated — review wanted | +| French (`fr`) | `Strings.fr.resx` | 100% (362 of 362) | Machine translated — review wanted | +| Japanese (`ja`) | `Strings.ja.resx` | 100% (362 of 362) | Machine translated — review wanted | --- From 6caa99af110e2160562e7fb408c22ee326aceff4 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:44:21 -0400 Subject: [PATCH 03/17] Find my radio: address CI-V rigs directly; verify by probe when Hamlib is absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A real IC-7300 answered the broadcast read-ID with only its Echo Back of our own frame: CI-V rigs act on address 00 but never reply to it. After the broadcast attempt the engine now sends the read-ID to each CI-V address rig_ids.json knows for the family (the selected radio's first, 150 ms each) and classifies an echo-only reply as Echo — a rig is listening at this speed — rather than garbage. Quiet window raised to 60 ms so echo and answer are read together. Icom bauds reordered 115200 first; IC-7300 preset uses 115200. Without Hamlib the verify stage could not run and every find was reported unverified. A rig that named itself (score >= 0.8) now counts as verified by the exchange itself; the Connection panel is filled and the handoff shown, with a note to install Hamlib for the full tests. Status bar explains why Run Tests needs Hamlib. Version 0.6.10. --- Assets/radio_presets.json | 4 ++-- Assets/rig_families.json | 4 ++-- BrandingInfo.cs | 2 +- CHANGELOG.md | 24 +++++++++++++++++++ Resources/Strings.de.resx | 5 +++- Resources/Strings.es.resx | 5 +++- Resources/Strings.fr.resx | 5 +++- Resources/Strings.ja.resx | 5 +++- Resources/Strings.resx | 11 ++++++++- RigCheck.csproj | 6 ++--- Services/DiscoveryEngine.cs | 48 +++++++++++++++++++++++++++++++++---- Services/ProbeOperations.cs | 26 +++++++++++++------- ViewModels/MainViewModel.cs | 18 ++++++++++++++ docs/discovery-flow.md | 4 ++-- 14 files changed, 139 insertions(+), 28 deletions(-) diff --git a/Assets/radio_presets.json b/Assets/radio_presets.json index e88e72e..96c00c4 100644 --- a/Assets/radio_presets.json +++ b/Assets/radio_presets.json @@ -6,10 +6,10 @@ "Name": "Icom IC-7300", "Manufacturer": "Icom", "HamlibModelId": 3073, - "BaudRate": 19200, + "BaudRate": 115200, "PttMethod": "CAT", "Popularity": 100, - "Notes": "CI-V address default 0x94. USB cable to the radio's USB port; driver is Silicon Labs CP210x." + "Notes": "CI-V address default 0x94. USB cable to the radio's USB port; driver is Silicon Labs CP210x. CI-V USB baud rate is 115200 unless changed in the radio's menu (Set > Connectors > CI-V > CI-V USB Baud Rate); Echo Back is on by default." }, { "Name": "Icom IC-705", diff --git a/Assets/rig_families.json b/Assets/rig_families.json index eb957b5..c00374c 100644 --- a/Assets/rig_families.json +++ b/Assets/rig_families.json @@ -1,6 +1,6 @@ [ { - "_comment": "RigCheck by ShackDesk — protocol families for Find my radio. Each family names ONE probe operation from RigCheck's fixed, compiled, query-only set (KenwoodId, IcomReadId, IcomReadFreq, YaesuLegacyReadFreq) and the baud rates to sweep, most likely first. This file can never contain command bytes; it only chooses which built-in query to send. Weight orders families when nothing else hints; HamlibBackend is the thousands digit of the Hamlib model number (1 Yaesu, 2 Kenwood/Elecraft, 3 Icom). HandoffStopBits is what the operator should enter in WSJT-X — the probe itself always uses 8N1. Validate at jsonlint.com before submitting." + "_comment": "RigCheck by ShackDesk — protocol families for Find my radio. Each family names ONE probe operation from RigCheck's fixed, compiled, query-only set (KenwoodId, IcomReadId, IcomReadFreq, YaesuLegacyReadFreq) and the baud rates to sweep, most likely first. This file can never contain command bytes; it only chooses which built-in query to send. For Icom, the CI-V addresses tried after the broadcast attempt come from rig_ids.json. Weight orders families when nothing else hints; HamlibBackend is the thousands digit of the Hamlib model number (1 Yaesu, 2 Kenwood/Elecraft, 3 Icom). HandoffStopBits is what the operator should enter in WSJT-X — the probe itself always uses 8N1. Validate at jsonlint.com before submitting." }, { "Id": "Kenwood", @@ -18,7 +18,7 @@ "Name": "Icom CI-V", "Probe": "IcomReadId", "FallbackProbe": "IcomReadFreq", - "Bauds": [ 19200, 9600, 115200, 4800 ], + "Bauds": [ 115200, 19200, 9600, 4800 ], "HamlibBackends": [ 3 ], "VendorHints": [ "Icom" ], "HandoffStopBits": 1, diff --git a/BrandingInfo.cs b/BrandingInfo.cs index ba4043b..ea883a0 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.9"; + public const string Version = "0.6.10"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index d51e1b9..5cca2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.10] - 2026-09-21 + +First lessons from a real IC-7300. + +### Fixed +- Find my radio never got an answer from an Icom: the read-ID was sent to CI-V address 00 + (broadcast), which rigs act on but by design never answer. When only our own echo comes + back (Echo Back is on by default on the IC-7300) or nothing at all, RigCheck now addresses + each CI-V address in `rig_ids.json` in turn, the selected radio's first +- Our own frame echoed back is now reported as what it is — a CI-V radio listening at that + speed — instead of "nothing recognisable" +- Without Hamlib installed, Find my radio reported a found radio as unverified because the + rigctl tests could not run. A radio that named itself over the serial port now counts as + verified, the Connection panel is filled in, and the handoff is shown; the note says to + install WSJT-X or Hamlib for the full tests +- The status bar now explains that Run Tests needs Hamlib because the tests are Hamlib + commands, and that Find my radio and Scan PC work without it + +### Changed +- Icom baud order is now 115200 first (the IC-7300's USB default), then 19200, 9600, 4800; the + IC-7300 preset uses 115200 +- The reply window waits a little longer for silence so an echo and the answer behind it are + read as one exchange + ## [0.6.9] - 2026-09-21 Find my radio hands you the settings; Hamlib's dummy rig can no longer pass unnoticed. diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index dcc14af..6ccd0c6 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -127,7 +127,7 @@ Details wurden in das Protokoll geschrieben. Falls dies wiederholt auftritt, mel Hamlib gefunden über {0} Hamlib nicht gefunden — WSJT-X installieren oder Hamlib herunterladen - Hamlib nicht gefunden. „Tests starten“ ist erst verfügbar, wenn Hamlib installiert ist. + Hamlib nicht gefunden — „Tests starten“ braucht es (die Tests sind Hamlib-Befehle). „Mein Funkgerät finden“ und „PC prüfen“ funktionieren auch ohne. Tests laufen… Alle {0} Tests bestanden. {0} Test(s) fehlgeschlagen — Details in den Ergebnissen. @@ -397,4 +397,7 @@ Fortfahren? Baud Rate auf {0} {0} verwendet rigctl ({1}) — das funktioniert, solange rigctld läuft {0} ist auf {1} an {2} mit {3} Baud eingestellt — so lassen oder auf Hamlib NET rigctl umstellen, um das Funkgerät über rigctld zu teilen + Hamlib ist nicht installiert, daher können die vollständigen rigctl-Tests nicht laufen — ein Funkgerät, das sich über die serielle Schnittstelle identifiziert hat, gilt als geprüft. Installieren Sie WSJT-X oder Hamlib, um die Tests auszuführen. + {0} an {1} durch direkte Abfrage geprüft + unser eigener Rahmen kam zurück — ein CI-V-Funkgerät mit eingeschaltetem Echo Back hört mit {0} Baud zu; jede bekannte CI-V-Adresse wird nun der Reihe nach abgefragt diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index 435ece0..479cc8e 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -127,7 +127,7 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Hamlib encontrado mediante {0} Hamlib no encontrado — instale WSJT-X o descargue Hamlib - Hamlib no encontrado. «Ejecutar pruebas» no estará disponible hasta que Hamlib esté instalado. + Hamlib no encontrado — «Ejecutar pruebas» lo necesita (las pruebas son comandos de Hamlib). «Buscar mi radio» y «Analizar PC» funcionan sin él. Ejecutando pruebas… Las {0} pruebas se superaron. {0} prueba(s) fallaron — vea los resultados para más detalles. @@ -397,4 +397,7 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Baud Rate a {0} {0} usa rigctl ({1}) — eso funciona siempre que rigctld esté en ejecución {0} está configurado con {1} en {2} a {3} baudios — déjelo así, o cámbielo a Hamlib NET rigctl para compartir la radio a través de rigctld + Hamlib no está instalado, así que las pruebas completas de rigctl no pueden ejecutarse — una radio que se identificó por el puerto serie cuenta como verificada. Instale WSJT-X o Hamlib para ejecutar las pruebas. + {0} en {1} verificada por consulta directa + volvió nuestra propia trama — una radio CI-V con Echo Back activado está escuchando a {0} baudios; preguntando a cada dirección CI-V conocida por turno diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index 3692cdb..d38052d 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -127,7 +127,7 @@ Les détails ont été écrits dans le journal. Si cela se reproduit, merci de l Hamlib trouvé via {0} Hamlib introuvable — installez WSJT-X ou téléchargez Hamlib - Hamlib introuvable. « Lancer les tests » ne sera disponible qu'une fois Hamlib installé. + Hamlib introuvable — « Lancer les tests » en a besoin (les tests sont des commandes Hamlib). « Trouver ma radio » et « Analyser le PC » fonctionnent sans. Tests en cours… Les {0} tests ont réussi. {0} test(s) échoué(s) — voir les résultats pour les détails. @@ -397,4 +397,7 @@ Continuer ? Baud Rate en {0} {0} utilise rigctl ({1}) — cela fonctionne tant que rigctld tourne {0} est réglé sur {1} sur {2} à {3} bauds — gardez cela, ou passez à Hamlib NET rigctl pour partager la radio via rigctld + Hamlib n'est pas installé, les tests rigctl complets ne peuvent donc pas s'exécuter — une radio qui s'est identifiée sur le port série compte comme vérifiée. Installez WSJT-X ou Hamlib pour lancer les tests. + {0} sur {1} vérifiée par requête directe + notre propre trame est revenue — une radio CI-V avec Echo Back activé écoute à {0} bauds ; interrogation de chaque adresse CI-V connue à tour de rôle diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index fbc4bfa..0cd18d6 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -127,7 +127,7 @@ Hamlib を {0} 経由で検出 Hamlib が見つかりません — WSJT-X をインストールするか Hamlib をダウンロードしてください - Hamlib が見つかりません。Hamlib をインストールするまで「テストを実行」は使用できません。 + Hamlib が見つかりません — 「テストを実行」には必要です(テストは Hamlib のコマンドです)。「無線機を探す」と「PC をスキャン」は Hamlib なしで動作します。 テストを実行中… {0} 件のテストがすべて合格しました。 {0} 件のテストが失敗しました — 詳細は結果を確認してください。 @@ -397,4 +397,7 @@ Baud Rate を {0} {0} は rigctl({1})を使用します — rigctld が動作していれば動きます {0} は {2} の {1}({3} ボー)に設定されています — そのままにするか、rigctld 経由で無線機を共有するために Hamlib NET rigctl に切り替えてください + Hamlib がインストールされていないため、rigctl の完全なテストは実行できません — シリアルポートで自ら名乗った無線機は検証済みとみなします。テストを実行するには WSJT-X または Hamlib をインストールしてください。 + {1} の {0} は直接問い合わせで検証済み + 自分のフレームがそのまま返ってきました — Echo Back が有効な CI-V 無線機が {0} ボーで受信しています。既知の CI-V アドレスを順に問い合わせます diff --git a/Resources/Strings.resx b/Resources/Strings.resx index f065519..1f9aad6 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -429,7 +429,7 @@ Details have been written to the log. If this keeps happening, please report it Hamlib not found — install WSJT-X or download Hamlib - Hamlib not found. Run Tests will be unavailable until Hamlib is installed. + Hamlib not found — Run Tests needs it (the tests are Hamlib commands). Find my radio and Scan PC work without it. Running tests… @@ -1274,4 +1274,13 @@ Continue? {0} is set to {1} on {2} at {3} baud — keep that, or switch it to Hamlib NET rigctl to share the radio through rigctld + + Hamlib is not installed, so the full rigctl tests cannot run — a radio that named itself over the serial port counts as verified. Install WSJT-X or Hamlib to run the tests. + + + {0} on {1} verified by direct query + + + our own frame came back — a CI-V radio with Echo Back on is listening at {0} baud; asking each known CI-V address in turn + diff --git a/RigCheck.csproj b/RigCheck.csproj index 5110896..746475a 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.9 - 0.6.9.0 - 0.6.9.0 + 0.6.10 + 0.6.10.0 + 0.6.10.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/DiscoveryEngine.cs b/Services/DiscoveryEngine.cs index e1e1398..c1600be 100644 --- a/Services/DiscoveryEngine.cs +++ b/Services/DiscoveryEngine.cs @@ -29,7 +29,8 @@ public record DiscoveryRequest( public class DiscoveryEngine { private const int ReplyTimeoutMs = 300; // how long to wait for the first byte - private const int QuietMs = 40; // reply is complete after this much silence + private const int QuietMs = 60; // reply is complete after this much silence (echo and answer arrive as two frames) + private const int AddressedTimeoutMs = 150; // CI-V answers within a few ms; the address sweep must stay quick private readonly DiscoveryDataService _data; private readonly RadioPresetsService _presets; @@ -172,9 +173,27 @@ private static IEnumerable CheckNetworkTransports() ProbeOperations.TryParse(family.Probe, out var op); var (_, parsed) = await ExchangeAsync(probe, port.PortName, op, events, ct); + // CI-V: the first frame goes to the broadcast address, which rigs act + // on but never answer. If only our own echo came back (Echo Back is + // on and the speed is right) — or nothing at all, since Echo Back can + // be off — address each known rig in turn. Addresses come from + // rig_ids.json; the frame layout is fixed here. + if (op == ProbeOperation.IcomReadId && parsed.Kind is not (ProbeReplyKind.Identity or ProbeReplyKind.Frequency)) + { + if (parsed.Kind == ProbeReplyKind.Echo) + events.Add(new(ProbeEventKind.Note, port.PortName, Strings.Format("Disc_Echo", baud))); + + foreach (var address in CivAddresses(family, request)) + { + ct.ThrowIfCancellationRequested(); + (_, parsed) = await ExchangeAsync(probe, port.PortName, op, events, ct, address, AddressedTimeoutMs); + if (parsed.Kind is ProbeReplyKind.Identity or ProbeReplyKind.Frequency) break; + } + } + // Icom rigs that predate the read-ID command answer NG or nothing; // a plain read-frequency still proves the family and the baud. - if (parsed.Kind is (ProbeReplyKind.None or ProbeReplyKind.Acknowledged) + if (parsed.Kind is (ProbeReplyKind.None or ProbeReplyKind.Acknowledged or ProbeReplyKind.Echo) && family.FallbackProbe is not null && ProbeOperations.TryParse(family.FallbackProbe, out var fallback)) { @@ -192,13 +211,32 @@ private static IEnumerable CheckNetworkTransports() return (rig, events); } + // Every CI-V address rig_ids.json knows for this family, the address of + // the radio chosen in the Connection panel first. + private IEnumerable CivAddresses(RigFamily family, DiscoveryRequest request) + { + var entries = _data.Ids.Where(i => i.Family.Equals(family.Id, StringComparison.OrdinalIgnoreCase)).ToList(); + var ordered = entries + .OrderByDescending(i => i.HamlibModelId == request.PreferredModelId) + .ThenByDescending(i => request.Clues.Any(c => c.ModelId == i.HamlibModelId)) + .Select(i => i.Reply); + + var seen = new HashSet(); + foreach (var reply in ordered) + { + if (byte.TryParse(reply, System.Globalization.NumberStyles.HexNumber, null, out var address) && seen.Add(address)) + yield return address; + } + } + private static async Task<(byte[] Reply, ProbeReply Parsed)> ExchangeAsync( - SerialProbe probe, string portName, ProbeOperation op, List events, CancellationToken ct) + SerialProbe probe, string portName, ProbeOperation op, List events, CancellationToken ct, + byte civAddress = 0x00, int timeoutMs = ReplyTimeoutMs) { - var request = ProbeOperations.Bytes(op); + var request = ProbeOperations.Bytes(op, civAddress); events.Add(new(ProbeEventKind.Sent, portName, op.ToString(), ProbeOperations.Dump(request))); - var reply = await probe.ExchangeAsync(request, ReplyTimeoutMs, QuietMs, ct); + var reply = await probe.ExchangeAsync(request, timeoutMs, QuietMs, ct); if (reply.Length > 0) events.Add(new(ProbeEventKind.Received, portName, string.Empty, ProbeOperations.Dump(reply))); else diff --git a/Services/ProbeOperations.cs b/Services/ProbeOperations.cs index ed75624..d011a0e 100644 --- a/Services/ProbeOperations.cs +++ b/Services/ProbeOperations.cs @@ -29,6 +29,8 @@ public enum ProbeReplyKind None, /// Bytes came back but nothing recognisable — often the wrong baud rate. Garbage, + /// Only our own frame came back: a CI-V rig with Echo Back on is listening at this speed but was not addressed. + Echo, /// The rig answered in the family's protocol but did not name itself (e.g. Kenwood "?;", CI-V NG). Acknowledged, /// The rig named itself; Id holds the identity string as it appears in rig_ids.json. @@ -48,12 +50,17 @@ public static class ProbeOperations public static bool TryParse(string name, out ProbeOperation op) => Enum.TryParse(name, ignoreCase: true, out op); - /// The exact bytes sent for an operation. - public static byte[] Bytes(ProbeOperation op) => op switch + /// + /// The exact bytes sent for an operation. For CI-V, + /// is the rig address the frame is sent to. 00 is broadcast: rigs act on + /// it but by design never answer, so identification has to address the + /// rig directly — the addresses come from rig_ids.json. + /// + public static byte[] Bytes(ProbeOperation op, byte civAddress = CivBroadcast) => op switch { ProbeOperation.KenwoodId => "ID;"u8.ToArray(), - ProbeOperation.IcomReadId => [0xFE, 0xFE, CivBroadcast, CivController, 0x19, 0x00, 0xFD], - ProbeOperation.IcomReadFreq => [0xFE, 0xFE, CivBroadcast, CivController, 0x03, 0xFD], + ProbeOperation.IcomReadId => [0xFE, 0xFE, civAddress, CivController, 0x19, 0x00, 0xFD], + ProbeOperation.IcomReadFreq => [0xFE, 0xFE, civAddress, CivController, 0x03, 0xFD], ProbeOperation.YaesuLegacyReadFreq => [0x00, 0x00, 0x00, 0x00, 0x03], _ => throw new ArgumentOutOfRangeException(nameof(op)), }; @@ -119,10 +126,13 @@ private static ProbeReply ParseCiv(byte[] reply, byte expectCmd) return new(ProbeReplyKind.Frequency, FrequencyHz: BcdLittleEndian(frame, 5, 5)); } - // Any well-formed frame from a non-controller address means CI-V at this baud. - return CivFrames(reply).Any(f => f.Length >= 5 && f[3] != CivController) - ? new(ProbeReplyKind.Acknowledged) - : new(ProbeReplyKind.Garbage); + // Any well-formed frame from a non-controller address means CI-V at + // this baud. Only our own frame back means Echo Back is on and the rig + // is listening at this speed — it just was not addressed. + var frames = CivFrames(reply).Where(f => f.Length >= 5).ToList(); + if (frames.Any(f => f[3] != CivController)) return new(ProbeReplyKind.Acknowledged); + if (frames.Count > 0) return new(ProbeReplyKind.Echo); + return new(ProbeReplyKind.Garbage); } private static IEnumerable CivFrames(byte[] buf) diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 052720f..2b80cf0 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -256,8 +256,26 @@ private async Task FindRadioAsync(IReadOnlyList ports) } // ── Stage 3: verify every find with the real test suite ─────── + // Without Hamlib there is no rigctl to verify with, but the probe + // already had a real exchange with the radio — a rig that named + // itself, or answered with a frequency inside a ham band, is + // verified by that exchange. Say so, rather than fail a step + // that cannot run. + var hamlibMissing = !_hamlib.IsAvailable; + if (hamlibMissing && found.Count > 0) + Results.AddTranscript(TranscriptKind.Note, Strings.Get("Disc_NoHamlibVerify")); + foreach (var rig in found) { + if (hamlibMissing) + { + if (rig.UseRigctld || rig.Score < 0.8) continue; + verified.Add((rig, new TestSuiteResult([], Connection.BuildConfig()))); + Results.AddTranscript(TranscriptKind.Found, + Strings.Format("Disc_VerifiedByProbe", rig.ModelName, rig.Port)); + continue; + } + Results.AddTranscript(TranscriptKind.Command, Strings.Format("Disc_Verifying", rig.ModelName, rig.Port, rig.Baud)); diff --git a/docs/discovery-flow.md b/docs/discovery-flow.md index b951c12..411d994 100644 --- a/docs/discovery-flow.md +++ b/docs/discovery-flow.md @@ -118,8 +118,8 @@ The data file chooses among these by name. Nothing else can be sent. | Name | Family | Bytes sent | Expected reply | Notes | |---|---|---|---|---| | `KenwoodId` | Kenwood, Elecraft, Yaesu CAT | `ID;` | `IDnnn;` | One exchange identifies the model. Elecraft K3 and KX3 both answer `ID017;`; `OM;` tells them apart. | -| `IcomReadId` | Icom CI-V | `FE FE 00 E0 19 00 FD` | `FE FE E0 aa 19 00 aa FD` | `00` is the broadcast address, so the rig's own CI-V address `aa` comes back. Skip the echo frame — CI-V transceive echoes our bytes first. | -| `IcomReadFreq` | Icom CI-V | `FE FE 00 E0 03 FD` | `FE FE E0 aa 03 FD` | Fallback for older rigs that do not answer `19 00`. | +| `IcomReadId` | Icom CI-V | `FE FE aa E0 19 00 FD` | `FE FE E0 aa 19 00 aa FD` | First sent to `00` (broadcast). Rigs act on broadcast but **never answer it**, so if only our own echo comes back (Echo Back on — the IC-7300 default) or nothing at all, the frame is re-sent to each CI-V address in `rig_ids.json`, the selected radio's first. The echo frame is skipped. Learned on a real IC-7300, 2026-09-21. | +| `IcomReadFreq` | Icom CI-V | `FE FE aa E0 03 FD` | `FE FE E0 aa 03 FD` | Fallback for older rigs that do not answer `19 00`. | | `YaesuLegacyReadFreq` | Yaesu FT-817/857/897 | `00 00 00 00 03` | 5 bytes: BCD freq + mode | Fixed 5-byte protocol, 4800/9600/38400 only. | | `TenTecReadFreq` | Ten-Tec | `?A` + CR | `A` + 4 bytes | Rare; last in the order. | From f1ef4f50ee74bd426566b5ea33001e7b5c2332bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:44:37 +0000 Subject: [PATCH 04/17] docs: update translation coverage --- TRANSLATING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TRANSLATING.md b/TRANSLATING.md index 7b62b92..3b0045d 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -16,11 +16,11 @@ starting from nothing, and it is the most useful thing you can do. | Language | File | Coverage | Status | | --- | --- | --- | --- | -| English (source) | `Strings.resx` | 100% (362 of 362) | Source | -| German (`de`) | `Strings.de.resx` | 100% (362 of 362) | Machine translated — review wanted | -| Spanish (`es`) | `Strings.es.resx` | 100% (362 of 362) | Machine translated — review wanted | -| French (`fr`) | `Strings.fr.resx` | 100% (362 of 362) | Machine translated — review wanted | -| Japanese (`ja`) | `Strings.ja.resx` | 100% (362 of 362) | Machine translated — review wanted | +| English (source) | `Strings.resx` | 100% (365 of 365) | Source | +| German (`de`) | `Strings.de.resx` | 100% (365 of 365) | Machine translated — review wanted | +| Spanish (`es`) | `Strings.es.resx` | 100% (365 of 365) | Machine translated — review wanted | +| French (`fr`) | `Strings.fr.resx` | 100% (365 of 365) | Machine translated — review wanted | +| Japanese (`ja`) | `Strings.ja.resx` | 100% (365 of 365) | Machine translated — review wanted | --- From b2536369af76af0f53c12cc612b4e06b5d7b1674 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:50:45 -0400 Subject: [PATCH 05/17] Find my radio: state the answer in one plain sentence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Your radio: Icom IC-7300 on COM3 — 115200 baud, 8 data bits, no parity, 1 stop bit" as a bold line before the handoff block and as the status-bar message. Version 0.6.11. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 6 ++++++ Resources/Strings.de.resx | 2 ++ Resources/Strings.es.resx | 2 ++ Resources/Strings.fr.resx | 2 ++ Resources/Strings.ja.resx | 2 ++ Resources/Strings.resx | 6 ++++++ RigCheck.csproj | 6 +++--- ViewModels/MainViewModel.cs | 12 ++++++++++-- 9 files changed, 34 insertions(+), 6 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index ea883a0..ef40636 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.10"; + public const string Version = "0.6.11"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cca2ca..8d3d0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.11] - 2026-09-21 + +### Added +- Find my radio ends with the sentence the operator came for — "Your radio: Icom IC-7300 on COM3 + — 115200 baud, 8 data bits, no parity, 1 stop bit" — in the results and in the status bar + ## [0.6.10] - 2026-09-21 First lessons from a real IC-7300. diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index 6ccd0c6..10f0a25 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -400,4 +400,6 @@ Fortfahren? Hamlib ist nicht installiert, daher können die vollständigen rigctl-Tests nicht laufen — ein Funkgerät, das sich über die serielle Schnittstelle identifiziert hat, gilt als geprüft. Installieren Sie WSJT-X oder Hamlib, um die Tests auszuführen. {0} an {1} durch direkte Abfrage geprüft unser eigener Rahmen kam zurück — ein CI-V-Funkgerät mit eingeschaltetem Echo Back hört mit {0} Baud zu; jede bekannte CI-V-Adresse wird nun der Reihe nach abgefragt + Ihr Funkgerät: {0} an {1} — {2} Baud, 8 Datenbits, keine Parität, {3} Stoppbit(s) + Ihr Funkgerät ist über rigctld unter {0} erreichbar diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index 479cc8e..ebd54b6 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -400,4 +400,6 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Hamlib no está instalado, así que las pruebas completas de rigctl no pueden ejecutarse — una radio que se identificó por el puerto serie cuenta como verificada. Instale WSJT-X o Hamlib para ejecutar las pruebas. {0} en {1} verificada por consulta directa volvió nuestra propia trama — una radio CI-V con Echo Back activado está escuchando a {0} baudios; preguntando a cada dirección CI-V conocida por turno + Su radio: {0} en {1} — {2} baudios, 8 bits de datos, sin paridad, {3} bit(s) de parada + Su radio es accesible a través de rigctld en {0} diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index d38052d..03caedc 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -400,4 +400,6 @@ Continuer ? Hamlib n'est pas installé, les tests rigctl complets ne peuvent donc pas s'exécuter — une radio qui s'est identifiée sur le port série compte comme vérifiée. Installez WSJT-X ou Hamlib pour lancer les tests. {0} sur {1} vérifiée par requête directe notre propre trame est revenue — une radio CI-V avec Echo Back activé écoute à {0} bauds ; interrogation de chaque adresse CI-V connue à tour de rôle + Votre radio : {0} sur {1} — {2} bauds, 8 bits de données, sans parité, {3} bit(s) de stop + Votre radio est joignable via rigctld à {0} diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index 0cd18d6..3bfb1ec 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -400,4 +400,6 @@ Hamlib がインストールされていないため、rigctl の完全なテストは実行できません — シリアルポートで自ら名乗った無線機は検証済みとみなします。テストを実行するには WSJT-X または Hamlib をインストールしてください。 {1} の {0} は直接問い合わせで検証済み 自分のフレームがそのまま返ってきました — Echo Back が有効な CI-V 無線機が {0} ボーで受信しています。既知の CI-V アドレスを順に問い合わせます + あなたの無線機: {1} の {0} — {2} ボー、データ 8 ビット、パリティなし、ストップ {3} ビット + あなたの無線機は {0} の rigctld 経由で利用できます diff --git a/Resources/Strings.resx b/Resources/Strings.resx index 1f9aad6..e525212 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -1283,4 +1283,10 @@ Continue? our own frame came back — a CI-V radio with Echo Back on is listening at {0} baud; asking each known CI-V address in turn + + Your radio: {0} on {1} — {2} baud, 8 data bits, no parity, {3} stop bit(s) + + + Your radio is reachable through rigctld at {0} + diff --git a/RigCheck.csproj b/RigCheck.csproj index 746475a..2f4001e 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.10 - 0.6.10.0 - 0.6.10.0 + 0.6.11 + 0.6.11.0 + 0.6.11.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 2b80cf0..c10d0b2 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -302,7 +302,15 @@ private async Task FindRadioAsync(IReadOnlyList ports) var best = verified.OrderByDescending(v => v.Rig.Score).First(); Connection.ApplyDiscovered(best.Rig); Results.SetSuiteResult(best.Suite); - Results.AddTranscript(TranscriptKind.Found, + + // The one sentence the operator came for. The probe itself + // used 8N1; the stop bits shown are what the radio's family + // expects from the digital-mode programs. + var summary = best.Rig.UseRigctld + ? Strings.Format("Disc_SummaryRigctld", best.Rig.Port) + : Strings.Format("Disc_Summary", best.Rig.ModelName, best.Rig.Port, best.Rig.Baud, best.Rig.HandoffStopBits); + Results.AddTranscript(TranscriptKind.Found, summary); + Results.AddTranscript(TranscriptKind.Note, Strings.Format("Disc_Applied", best.Rig.ModelName, best.Rig.HamlibModelId, best.Rig.Port, best.Rig.Baud)); // Stage 4: the settings to type into each program, and how @@ -310,7 +318,7 @@ private async Task FindRadioAsync(IReadOnlyList ports) foreach (var line in _handoff.Build(best.Rig, clues)) Results.AddTranscript(line.Kind, line.Text); StatusMessage = verified.Count == 1 - ? Strings.Format("Status_DiscoveredOne", best.Rig.ModelName, best.Rig.Port) + ? summary : Strings.Format("Status_DiscoveredMany", verified.Count, best.Rig.ModelName, best.Rig.Port); } else From bc4723be637219d87ab79ec5423f64dac3211257 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:51:13 +0000 Subject: [PATCH 06/17] docs: update translation coverage --- TRANSLATING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TRANSLATING.md b/TRANSLATING.md index 3b0045d..8aa9e9e 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -16,11 +16,11 @@ starting from nothing, and it is the most useful thing you can do. | Language | File | Coverage | Status | | --- | --- | --- | --- | -| English (source) | `Strings.resx` | 100% (365 of 365) | Source | -| German (`de`) | `Strings.de.resx` | 100% (365 of 365) | Machine translated — review wanted | -| Spanish (`es`) | `Strings.es.resx` | 100% (365 of 365) | Machine translated — review wanted | -| French (`fr`) | `Strings.fr.resx` | 100% (365 of 365) | Machine translated — review wanted | -| Japanese (`ja`) | `Strings.ja.resx` | 100% (365 of 365) | Machine translated — review wanted | +| English (source) | `Strings.resx` | 100% (367 of 367) | Source | +| German (`de`) | `Strings.de.resx` | 100% (367 of 367) | Machine translated — review wanted | +| Spanish (`es`) | `Strings.es.resx` | 100% (367 of 367) | Machine translated — review wanted | +| French (`fr`) | `Strings.fr.resx` | 100% (367 of 367) | Machine translated — review wanted | +| Japanese (`ja`) | `Strings.ja.resx` | 100% (367 of 367) | Machine translated — review wanted | --- From 5a6ce21ab97bf37979dfd5711cd625468fa85bb6 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:30:52 -0400 Subject: [PATCH 07/17] Scan PC: show the winget line for WSJT-X; tidy summary wording and copied text The Hamlib-not-found diagnosis now carries winget install JoeTaylor.WSJT-x as its fix command (package ID verified; RigCheck shows it, never runs it). Summary line uses the 8N1 form. Copy from the results panel trims the trailing space the inline Copy buttons leave. Version 0.6.12. --- BrandingInfo.cs | 9 ++++++++- CHANGELOG.md | 10 ++++++++++ Resources/Strings.de.resx | 4 ++-- Resources/Strings.es.resx | 4 ++-- Resources/Strings.fr.resx | 4 ++-- Resources/Strings.ja.resx | 4 ++-- Resources/Strings.resx | 4 ++-- RigCheck.csproj | 6 +++--- Services/EnvironmentCheckService.cs | 2 +- Views/MainWindow.xaml.cs | 7 ++++++- 10 files changed, 38 insertions(+), 16 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index ef40636..797464e 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.11"; + public const string Version = "0.6.12"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── @@ -48,6 +48,13 @@ public static class BrandingInfo // ── Hamlib / rigctl resources ───────────────────────────────── public const string HamlibDownloadUrl = "https://github.com/Hamlib/Hamlib/releases"; + + /// + /// winget command that installs WSJT-X, which bundles Hamlib. Shown to the + /// operator as a copyable line; RigCheck never runs it. Package ID + /// verified 2026-09-21 (installs WSJT-X 3.0.2). + /// + public const string WsjtxWingetCommand = "winget install JoeTaylor.WSJT-x"; public const string WsjtxDownloadUrl = "https://wsjt.sourceforge.io/wsjtx.html"; public const string HamlibDocsUrl = "https://hamlib.github.io"; diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3d0df..ac51dad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.12] - 2026-09-21 + +### Added +- When Scan PC finds no Hamlib, the diagnosis shows the one-line winget command that installs + WSJT-X (`winget install JoeTaylor.WSJT-x`) as a copyable fix — RigCheck never runs it + +### Fixed +- Summary line reads "115200 baud, 8N1 (8 data bits, no parity, 1 stop)" instead of "1 stop bit(s)" +- Text copied from the results panel no longer carries a trailing space where a Copy button sat + ## [0.6.11] - 2026-09-21 ### Added diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index 10f0a25..fe60be7 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -287,7 +287,7 @@ Fortfahren? {0} {1} (Build {2}, {3}) Nicht gefunden Hamlib ist nicht installiert oder nicht dort, wo RigCheck sucht. - Installieren Sie WSJT-X, Fldigi oder das eigenständige Hamlib für Windows — jedes enthält rigctl.exe + Installieren Sie WSJT-X, Fldigi oder das eigenständige Hamlib für Windows — jedes enthält rigctl.exe. Der Befehl unten installiert WSJT-X mit winget RigCheck sucht in den Installationsordnern von WSJT-X, Fldigi und Hamlib sowie im PATH Verwendet {0}: {1} {0} Kopien gefunden — verwendet {1}: {2}. Außerdem: {3} @@ -400,6 +400,6 @@ Fortfahren? Hamlib ist nicht installiert, daher können die vollständigen rigctl-Tests nicht laufen — ein Funkgerät, das sich über die serielle Schnittstelle identifiziert hat, gilt als geprüft. Installieren Sie WSJT-X oder Hamlib, um die Tests auszuführen. {0} an {1} durch direkte Abfrage geprüft unser eigener Rahmen kam zurück — ein CI-V-Funkgerät mit eingeschaltetem Echo Back hört mit {0} Baud zu; jede bekannte CI-V-Adresse wird nun der Reihe nach abgefragt - Ihr Funkgerät: {0} an {1} — {2} Baud, 8 Datenbits, keine Parität, {3} Stoppbit(s) + Ihr Funkgerät: {0} an {1} — {2} Baud, 8N{3} (8 Datenbits, keine Parität, {3} Stopp) Ihr Funkgerät ist über rigctld unter {0} erreichbar diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index ebd54b6..d9c231b 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -287,7 +287,7 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel {0} {1} (compilación {2}, {3}) No encontrado Hamlib no está instalado, o no está donde RigCheck lo busca. - Instale WSJT-X, Fldigi o el Hamlib independiente para Windows — cada uno incluye rigctl.exe + Instale WSJT-X, Fldigi o el Hamlib independiente para Windows — cada uno incluye rigctl.exe. El comando de abajo instala WSJT-X con winget RigCheck busca en las carpetas de instalación de WSJT-X, Fldigi y Hamlib y en PATH Usando {0}: {1} {0} copias encontradas — usando {1}: {2}. También: {3} @@ -400,6 +400,6 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Hamlib no está instalado, así que las pruebas completas de rigctl no pueden ejecutarse — una radio que se identificó por el puerto serie cuenta como verificada. Instale WSJT-X o Hamlib para ejecutar las pruebas. {0} en {1} verificada por consulta directa volvió nuestra propia trama — una radio CI-V con Echo Back activado está escuchando a {0} baudios; preguntando a cada dirección CI-V conocida por turno - Su radio: {0} en {1} — {2} baudios, 8 bits de datos, sin paridad, {3} bit(s) de parada + Su radio: {0} en {1} — {2} baudios, 8N{3} (8 bits de datos, sin paridad, {3} de parada) Su radio es accesible a través de rigctld en {0} diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index 03caedc..bec74a7 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -287,7 +287,7 @@ Continuer ? {0} {1} (build {2}, {3}) Introuvable Hamlib n'est pas installé, ou pas là où RigCheck le cherche. - Installez WSJT-X, Fldigi ou le Hamlib autonome pour Windows — chacun inclut rigctl.exe + Installez WSJT-X, Fldigi ou le Hamlib autonome pour Windows — chacun inclut rigctl.exe. La commande ci-dessous installe WSJT-X avec winget RigCheck cherche dans les dossiers d'installation de WSJT-X, Fldigi et Hamlib ainsi que dans le PATH Utilise {0} : {1} {0} copies trouvées — utilise {1} : {2}. Également : {3} @@ -400,6 +400,6 @@ Continuer ? Hamlib n'est pas installé, les tests rigctl complets ne peuvent donc pas s'exécuter — une radio qui s'est identifiée sur le port série compte comme vérifiée. Installez WSJT-X ou Hamlib pour lancer les tests. {0} sur {1} vérifiée par requête directe notre propre trame est revenue — une radio CI-V avec Echo Back activé écoute à {0} bauds ; interrogation de chaque adresse CI-V connue à tour de rôle - Votre radio : {0} sur {1} — {2} bauds, 8 bits de données, sans parité, {3} bit(s) de stop + Votre radio : {0} sur {1} — {2} bauds, 8N{3} (8 bits de données, sans parité, {3} stop) Votre radio est joignable via rigctld à {0} diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index 3bfb1ec..b489118 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -287,7 +287,7 @@ {0} {1} (ビルド {2}、{3}) 見つかりません Hamlib がインストールされていないか、RigCheck が探す場所にありません。 - WSJT-X、Fldigi、または Windows 用の単体 Hamlib をインストールしてください — いずれにも rigctl.exe が含まれています + WSJT-X、Fldigi、または Windows 用の単体 Hamlib をインストールしてください — いずれにも rigctl.exe が含まれています。下のコマンドは winget で WSJT-X をインストールします RigCheck は WSJT-X、Fldigi、Hamlib のインストールフォルダーと PATH を探します 使用中: {0}: {1} {0} 個見つかりました — 使用中: {1}: {2}。その他: {3} @@ -400,6 +400,6 @@ Hamlib がインストールされていないため、rigctl の完全なテストは実行できません — シリアルポートで自ら名乗った無線機は検証済みとみなします。テストを実行するには WSJT-X または Hamlib をインストールしてください。 {1} の {0} は直接問い合わせで検証済み 自分のフレームがそのまま返ってきました — Echo Back が有効な CI-V 無線機が {0} ボーで受信しています。既知の CI-V アドレスを順に問い合わせます - あなたの無線機: {1} の {0} — {2} ボー、データ 8 ビット、パリティなし、ストップ {3} ビット + あなたの無線機: {1} の {0} — {2} ボー、8N{3}(データ 8 ビット、パリティなし、ストップ {3} ビット) あなたの無線機は {0} の rigctld 経由で利用できます diff --git a/Resources/Strings.resx b/Resources/Strings.resx index e525212..fa5855a 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -932,7 +932,7 @@ Continue? Hamlib is not installed, or not where RigCheck looks. - Install WSJT-X, Fldigi, or the standalone Hamlib for Windows — each includes rigctl.exe + Install WSJT-X, Fldigi, or the standalone Hamlib for Windows — each includes rigctl.exe. The command below installs WSJT-X with winget RigCheck looks in the WSJT-X, Fldigi, and Hamlib install folders and on PATH @@ -1284,7 +1284,7 @@ Continue? our own frame came back — a CI-V radio with Echo Back on is listening at {0} baud; asking each known CI-V address in turn - Your radio: {0} on {1} — {2} baud, 8 data bits, no parity, {3} stop bit(s) + Your radio: {0} on {1} — {2} baud, 8N{3} (8 data bits, no parity, {3} stop) Your radio is reachable through rigctld at {0} diff --git a/RigCheck.csproj b/RigCheck.csproj index 2f4001e..98b5917 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.11 - 0.6.11.0 - 0.6.11.0 + 0.6.12 + 0.6.12.0 + 0.6.12.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/EnvironmentCheckService.cs b/Services/EnvironmentCheckService.cs index 64da3e9..6a683a8 100644 --- a/Services/EnvironmentCheckService.cs +++ b/Services/EnvironmentCheckService.cs @@ -111,7 +111,7 @@ private TestResult CheckHamlib() if (copies.Count == 0) { return TestResult.Fail(TestId.EnvHamlib, Strings.Get("Env_HamlibNone"), string.Empty, - Diag("Env_HamlibNone", 2, learnMore: BrandingInfo.HamlibDownloadUrl)); + Diag("Env_HamlibNone", 2, fixCommand: BrandingInfo.WsjtxWingetCommand, learnMore: BrandingInfo.HamlibDownloadUrl)); } var (path, via) = copies[0]; diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs index dbd85a7..f056e7b 100644 --- a/Views/MainWindow.xaml.cs +++ b/Views/MainWindow.xaml.cs @@ -57,7 +57,12 @@ private void ResultsCopy_Executed(object sender, ExecutedRoutedEventArgs e) { var text = ResultsViewer.Selection?.Text; if (!string.IsNullOrEmpty(text)) - Clipboard.SetText(text); + { + // The inline Copy buttons come through as a space at the end of + // their line; trim so pasted command lines are exact. + var lines = text.Split('\n').Select(l => l.TrimEnd(' ', '\r')); + Clipboard.SetText(string.Join(Environment.NewLine, lines)); + } e.Handled = true; } From ac6532b63a74aba59449d0f68f55adf69b9f0c2f Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:42:37 -0400 Subject: [PATCH 08/17] Find WSJT-X's Hamlib: rigctl-wsjtx.exe, Uninstall-key install folder WSJT-X 3.0.2 (winget, C:\WSJT\wsjtx) ships Hamlib as rigctl-wsjtx.exe and rigctld-wsjtx.exe; the locator only asked for rigctl.exe and reported Hamlib missing on a PC that had it. Both names are tried in the WSJT-X folders and on PATH, and the install folder is also taken from the Uninstall key's InstallLocation or uninstaller path. RigctlCommandBuilder.ExeName carries the found exe's name so copyable commands read "rigctl-wsjtx ..." when that is what exists. Scan PC now re-runs the search and the ViewModel refreshes the banner and the Run Tests gate afterwards. Version 0.6.13. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 12 +++++ RigCheck.csproj | 6 +-- Services/EnvironmentCheckService.cs | 3 ++ Services/HamlibLocatorService.cs | 81 +++++++++++++++++++++++++---- Services/RigctlCommandBuilder.cs | 24 ++++++--- ViewModels/MainViewModel.cs | 7 +++ 7 files changed, 113 insertions(+), 22 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index 797464e..f58dd49 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.12"; + public const string Version = "0.6.13"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index ac51dad..0cfcdad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.13] - 2026-09-21 + +WSJT-X's Hamlib is found. + +### Fixed +- Hamlib was reported missing on a PC with WSJT-X installed: WSJT-X ships its copy as + `rigctl-wsjtx.exe` (and `rigctld-wsjtx.exe`), not `rigctl.exe`. Both names are now tried, the + install folder is also read from the Uninstall registry key, and either name is accepted on PATH +- Copyable commands start with the name that exists on the PC (`rigctl-wsjtx …` for WSJT-X users) +- Scan PC re-searches for Hamlib, so installing WSJT-X while RigCheck is open updates the banner + and enables Run Tests without a restart + ## [0.6.12] - 2026-09-21 ### Added diff --git a/RigCheck.csproj b/RigCheck.csproj index 98b5917..36b99c9 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.12 - 0.6.12.0 - 0.6.12.0 + 0.6.13 + 0.6.13.0 + 0.6.13.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/EnvironmentCheckService.cs b/Services/EnvironmentCheckService.cs index 6a683a8..a6e1f12 100644 --- a/Services/EnvironmentCheckService.cs +++ b/Services/EnvironmentCheckService.cs @@ -107,6 +107,9 @@ private static TestResult CheckWindows() private TestResult CheckHamlib() { + // Re-run the search: the operator may have installed WSJT-X since + // RigCheck started, and the cached answer must not stay stale. + _locator.Find(); var copies = _locator.FindAll(); if (copies.Count == 0) { diff --git a/Services/HamlibLocatorService.cs b/Services/HamlibLocatorService.cs index fa8797c..3575eac 100644 --- a/Services/HamlibLocatorService.cs +++ b/Services/HamlibLocatorService.cs @@ -49,7 +49,8 @@ public void Find() { _cachedPath = path; FoundVia = via; - Log.Information("rigctl.exe found via {Via}: {Path}", via, path); + RigctlCommandBuilder.ExeName = Path.GetFileNameWithoutExtension(path); + Log.Information("rigctl found via {Via}: {Path}", via, path); return; } } @@ -74,20 +75,28 @@ public void Find() public IEnumerable<(string Path, string Via)> CandidatePaths() { // ── 1. WSJT-X bundled Hamlib ───────────────────────────────────── - // WSJT-X ships rigctl.exe in its own bin directory. - var wsjtxDirs = new[] + // WSJT-X ships Hamlib in its bin folder under its OWN names: + // rigctl-wsjtx.exe and rigctld-wsjtx.exe (confirmed on 3.0.2, + // installed by winget to C:\WSJT\wsjtx). Older builds used plain + // rigctl.exe, so both are tried. The install folder also comes from + // the Uninstall key, which covers a non-default install path. + var wsjtxDirs = new List { @"C:\WSJT\wsjtx\bin", @"C:\Program Files\WSJT-X\bin", @"C:\Program Files (x86)\WSJT-X\bin", }; - // Also check registry for WSJT-X install location var wsjtxReg = RegistryInstallPath(@"SOFTWARE\WSJT-X", "InstallDir"); if (wsjtxReg is not null) - wsjtxDirs = [..wsjtxDirs, Path.Combine(wsjtxReg, "bin")]; + wsjtxDirs.Add(Path.Combine(wsjtxReg, "bin")); + foreach (var dir in UninstallKeyInstallDirs("wsjtx", "WSJT-X")) + wsjtxDirs.Add(Path.Combine(dir, "bin")); - foreach (var d in wsjtxDirs) - yield return (Path.Combine(d, "rigctl.exe"), "WSJT-X"); + foreach (var d in wsjtxDirs.Distinct(StringComparer.OrdinalIgnoreCase)) + { + yield return (Path.Combine(d, "rigctl-wsjtx.exe"), "WSJT-X"); + yield return (Path.Combine(d, "rigctl.exe"), "WSJT-X"); + } // ── 2. Fldigi bundled Hamlib ────────────────────────────────────── var fldigiDirs = new[] @@ -115,9 +124,14 @@ public void Find() yield return (Path.Combine(d, "rigctl.exe"), "Hamlib standalone"); // ── 4. PATH ─────────────────────────────────────────────────────── - var fromPath = FindInPath("rigctl.exe"); - if (fromPath is not null) - yield return (fromPath, "PATH"); + // Either name: a WSJT-X user who added its bin folder to PATH has + // rigctl-wsjtx, not rigctl. + foreach (var exe in new[] { "rigctl.exe", "rigctl-wsjtx.exe" }) + { + var fromPath = FindInPath(exe); + if (fromPath is not null) + yield return (fromPath, "PATH"); + } } // ── Helpers ────────────────────────────────────────────────────────── @@ -134,6 +148,53 @@ public void Find() return null; } + /// + /// Install folders of programs whose Uninstall entry matches any of the + /// given name fragments: InstallLocation when set, else the folder of + /// the uninstaller (NSIS installers such as WSJT-X's set only that). + /// + private static IEnumerable UninstallKeyInstallDirs(params string[] nameFragments) + { + var found = new List(); + var roots = new (RegistryKey Root, string Sub)[] + { + (Registry.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"), + (Registry.LocalMachine, @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"), + (Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"), + }; + + foreach (var (root, sub) in roots) + { + try + { + using var key = root.OpenSubKey(sub); + if (key is null) continue; + foreach (var name in key.GetSubKeyNames()) + { + using var app = key.OpenSubKey(name); + var display = app?.GetValue("DisplayName") as string ?? string.Empty; + if (!nameFragments.Any(f => name.Contains(f, StringComparison.OrdinalIgnoreCase) + || display.Contains(f, StringComparison.OrdinalIgnoreCase))) + continue; + + var location = app?.GetValue("InstallLocation") as string; + if (string.IsNullOrWhiteSpace(location)) + { + var uninstaller = (app?.GetValue("UninstallString") as string ?? string.Empty).Trim('"'); + location = Path.GetDirectoryName(uninstaller.Split(".exe", StringSplitOptions.None)[0] + ".exe"); + } + if (!string.IsNullOrWhiteSpace(location)) + found.Add(location.TrimEnd('\\')); + } + } + catch (Exception ex) + { + Log.Debug(ex, "Uninstall key scan failed under {Root}\\{Sub}", root.Name, sub); + } + } + return found; + } + private static string? RegistryInstallPath(string subKey, string valueName) { try diff --git a/Services/RigctlCommandBuilder.cs b/Services/RigctlCommandBuilder.cs index 633f4ad..6936646 100644 --- a/Services/RigctlCommandBuilder.cs +++ b/Services/RigctlCommandBuilder.cs @@ -17,6 +17,14 @@ namespace RigCheck.Services; /// public class RigctlCommandBuilder { + /// + /// The name the copyable commands start with. "rigctl" for standalone + /// Hamlib; WSJT-X ships its copy as "rigctl-wsjtx", and a command the + /// operator pastes must use the name that actually exists on their PC. + /// Set by HamlibLocatorService when it finds the exe. + /// + public static string ExeName { get; set; } = "rigctl"; + // ── Connection argument builders ───────────────────────────────────── /// @@ -53,7 +61,7 @@ public static RigctlCommand NetworkArgs(ConnectionConfig cfg) => public static RigctlCommand TestConnection(ConnectionConfig cfg) { var conn = ConnArgs(cfg); - return conn with { DisplayCommand = $"rigctl {conn.ConnectionLabel}" }; + return conn with { DisplayCommand = $"{ExeName} {conn.ConnectionLabel}" }; } /// Test 2: Get frequency — 'f' subcommand. @@ -63,7 +71,7 @@ public static RigctlCommand GetFrequency(ConnectionConfig cfg) return conn with { Args = [..conn.Args, "f"], - DisplayCommand = $"rigctl {conn.ConnectionLabel} f" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} f" }; } @@ -74,7 +82,7 @@ public static RigctlCommand GetMode(ConnectionConfig cfg) return conn with { Args = [..conn.Args, "m"], - DisplayCommand = $"rigctl {conn.ConnectionLabel} m" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} m" }; } @@ -85,7 +93,7 @@ public static RigctlCommand GetPtt(ConnectionConfig cfg) return conn with { Args = [..conn.Args, "t"], - DisplayCommand = $"rigctl {conn.ConnectionLabel} t" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} t" }; } @@ -96,7 +104,7 @@ public static RigctlCommand GetSmeter(ConnectionConfig cfg) return conn with { Args = [..conn.Args, "l", "STRENGTH"], - DisplayCommand = $"rigctl {conn.ConnectionLabel} l STRENGTH" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} l STRENGTH" }; } @@ -107,7 +115,7 @@ public static RigctlCommand GetVfo(ConnectionConfig cfg) return conn with { Args = [..conn.Args, "v"], - DisplayCommand = $"rigctl {conn.ConnectionLabel} v" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} v" }; } @@ -118,7 +126,7 @@ public static RigctlCommand SetFrequency(ConnectionConfig cfg, long frequencyHz) return conn with { Args = [..conn.Args, "F", frequencyHz.ToString()], - DisplayCommand = $"rigctl {conn.ConnectionLabel} F {frequencyHz}" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} F {frequencyHz}" }; } @@ -136,7 +144,7 @@ public static RigctlCommand RawCommand(ConnectionConfig cfg, string rawInput) return conn with { Args = [..conn.Args, ..tokens], - DisplayCommand = $"rigctl {conn.ConnectionLabel} {rawInput.Trim()}" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} {rawInput.Trim()}" }; } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index c10d0b2..c618602 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -181,6 +181,13 @@ private async Task ScanEnvironmentAsync() using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var suite = await _envCheck.RunAllAsync(Connection.BuildConfig(), progress, cts.Token); + // The scan re-searched for Hamlib; reflect the answer in the + // banner and the Run Tests gate without a restart. + CheckHamlib(); + OnPropertyChanged(nameof(IsHamlibAvailable)); + OnPropertyChanged(nameof(IsHamlibMissing)); + RunTestsCommand.NotifyCanExecuteChanged(); + Results.SetSuiteResult(suite); TaskCompleted?.Invoke(suite.AllPassed); StatusMessage = Strings.Format("Status_ScanDone", suite.WarningCount + suite.FailCount); From 74ebe798d2c37bfcae96a9646441214e1ce67ba5 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:52:46 -0400 Subject: [PATCH 09/17] Open-connection test must send a command; decode WSJT-X Qt variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rigctl with no subcommand does not connect and exit — it enters interactive mode and blocks on stdin. Test 1 therefore always timed out and skipped the suite, while rigctl-wsjtx from a command window read the IC-7300's frequency in 7 ms. The test now sends "f", and HamlibRunnerService closes rigctl's stdin immediately after start. ConfigClueService decodes @Variant(...) values (unescape \0 and \xHH, take the last NUL-delimited token, strip the enum prefixes) so PTTMethod, stop bits, and handshake come through as words. Version 0.6.14. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 13 ++++++++++ RigCheck.csproj | 6 ++--- Services/ConfigClueService.cs | 42 +++++++++++++++++++++++++++++++- Services/HamlibRunnerService.cs | 2 ++ Services/RigctlCommandBuilder.cs | 14 +++++++++-- 6 files changed, 72 insertions(+), 7 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index f58dd49..fbd34ad 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.13"; + public const string Version = "0.6.14"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cfcdad..116fb73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.14] - 2026-09-21 + +Run Tests works against a real radio. + +### Fixed +- The "Open connection" test ran rigctl with no command, which does not connect-and-exit — it + enters rigctl's interactive mode and waits for keyboard input until RigCheck's timeout, so + every run reported "the radio did not respond" and skipped the other six tests, even though + Hamlib could talk to the radio in 7 ms. The test now reads the frequency, which proves the + round trip; rigctl's input is also closed at launch so it can never wait on a keyboard again +- WSJT-X stores several settings as Qt variant blobs (`(...PTT_method_VOX...)`); the + configuration reader now decodes them instead of passing the blob through to the handoff + ## [0.6.13] - 2026-09-21 WSJT-X's Hamlib is found. diff --git a/RigCheck.csproj b/RigCheck.csproj index 36b99c9..1fb7921 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.13 - 0.6.13.0 - 0.6.13.0 + 0.6.14 + 0.6.14.0 + 0.6.14.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/ConfigClueService.cs b/Services/ConfigClueService.cs index 700f23a..1d6aca9 100644 --- a/Services/ConfigClueService.cs +++ b/Services/ConfigClueService.cs @@ -131,8 +131,48 @@ private static Dictionary ReadIniSection(string path, string sec var eq = line.IndexOf('='); if (eq <= 0) continue; - result[line[..eq].Trim()] = line[(eq + 1)..].Trim(); + result[line[..eq].Trim()] = DecodeQtValue(line[(eq + 1)..].Trim()); } return result; } + + // WSJT-X writes enum settings as Qt variants, e.g. + // @Variant(\0\0\0\x7f\0\0\0\x1eTransceiverFactory::PTTMethod\0\0\0\0\xfPTT_method_VOX\0) + // @Variant(...TransceiverFactory::StopBits\0\0\0\0\x12\x64\x65\x66\x61ult_stop_bits\0) + // Unescape \0 and \xHH (Qt escapes any hex-looking character that + // follows an escape, so a greedy hex read is safe), split on NUL, and + // take the last segment minus its length-prefix byte. Then strip the + // type prefixes/suffixes so callers see "VOX", "two", "none", "default". + private static string DecodeQtValue(string raw) + { + if (!raw.StartsWith("@Variant(", StringComparison.Ordinal)) return raw; + + var inner = raw[9..].TrimEnd(')'); + var sb = new System.Text.StringBuilder(); + for (int i = 0; i < inner.Length; i++) + { + if (inner[i] == '\\' && i + 1 < inner.Length) + { + if (inner[i + 1] == '0') { sb.Append('\0'); i++; continue; } + if (inner[i + 1] == 'x') + { + int j = i + 2; var hex = string.Empty; + while (j < inner.Length && hex.Length < 4 && Uri.IsHexDigit(inner[j])) hex += inner[j++]; + if (hex.Length > 0) { sb.Append((char)Convert.ToInt32(hex, 16)); i = j - 1; continue; } + } + } + sb.Append(inner[i]); + } + + var segments = sb.ToString().Split('\0', StringSplitOptions.RemoveEmptyEntries); + var last = segments.Length > 0 ? segments[^1] : string.Empty; + last = new string(last.SkipWhile(c => !char.IsLetter(c) && c != '_').ToArray()); + + return last + .Replace("PTT_method_", string.Empty) + .Replace("handshake_", string.Empty) + .Replace("_stop_bits", string.Empty) + .Replace("_stop_bit", string.Empty) + .Replace("_data_bits", string.Empty); + } } diff --git a/Services/HamlibRunnerService.cs b/Services/HamlibRunnerService.cs index d2c4074..8fc1386 100644 --- a/Services/HamlibRunnerService.cs +++ b/Services/HamlibRunnerService.cs @@ -52,6 +52,7 @@ public async Task RunAsync(RigctlCommand command, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, + RedirectStandardInput = true, // closed right after start: rigctl must never wait for a keyboard CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, @@ -75,6 +76,7 @@ public async Task RunAsync(RigctlCommand command, }; process.Start(); + process.StandardInput.Close(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); diff --git a/Services/RigctlCommandBuilder.cs b/Services/RigctlCommandBuilder.cs index 6936646..056f50d 100644 --- a/Services/RigctlCommandBuilder.cs +++ b/Services/RigctlCommandBuilder.cs @@ -57,11 +57,21 @@ public static RigctlCommand NetworkArgs(ConnectionConfig cfg) => // ── Test commands ──────────────────────────────────────────────────── - /// Test 1: Open connection — just connect and disconnect (no subcommand). + /// + /// Test 1: Open connection. Asks for the frequency: rigctl with no + /// subcommand does not "just connect", it enters interactive mode and + /// waits on stdin forever — which read as a radio timeout on a working + /// IC-7300. Hamlib's open already exchanges with the rig; the reply to + /// "f" proves the round trip. + /// public static RigctlCommand TestConnection(ConnectionConfig cfg) { var conn = ConnArgs(cfg); - return conn with { DisplayCommand = $"{ExeName} {conn.ConnectionLabel}" }; + return conn with + { + Args = [..conn.Args, "f"], + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} f", + }; } /// Test 2: Get frequency — 'f' subcommand. From 4390f95c8f0930c6d83d020af9669fa763b01610 Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:59:39 -0400 Subject: [PATCH 10/17] Run rigctl with -vv so failures carry a cause; name the port holder Hamlib 4.7.1 exits 2 with empty stdout and stderr when it cannot open a port or reach rigctld (observed on a real PC), leaving nothing to classify. HamlibRunnerService now passes -vv, strips the "Opened rig model" banner from stdout, and ClassifyError recognises "already open", "does not exist", and "failed to connect". The port-in-use diagnosis checks which known CAT programs are running at that moment and names them. The rigctld fix command uses rigctld-wsjtx when WSJT-X's copy is in use and omits -s at baud 0. LoadFrom re-selects the saved preset without re-applying its defaults. Version 0.6.15. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 16 +++++++++++ Resources/Strings.de.resx | 1 + Resources/Strings.es.resx | 1 + Resources/Strings.fr.resx | 1 + Resources/Strings.ja.resx | 1 + Resources/Strings.resx | 3 +++ RigCheck.csproj | 6 ++--- Services/DiagnosisEngine.cs | 48 +++++++++++++++++++++++++++------ Services/HamlibRunnerService.cs | 29 ++++++++++++++++---- ViewModels/ViewModels.cs | 5 ++++ 11 files changed, 96 insertions(+), 17 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index fbd34ad..69c5ac5 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.14"; + public const string Version = "0.6.15"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 116fb73..16c196b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.15] - 2026-09-21 + +Failures now say why. + +### Fixed +- Hamlib 4.7.1 prints nothing when rigctl fails at its default verbosity, so a port held by + WSJT-X, a missing rigctld, and a radio that is off all came out as "an unexpected error". + rigctl now runs with `-vv`, its banner line is stripped from the output, and its actual + messages ("serial port COM3 is already open", "does not exist", "failed to connect") drive the + diagnosis +- The "port in use" diagnosis names the program that is running right now (WSJT-X, Fldigi, + JS8Call, Flrig, rigctld, Winlink Express, VARA) — checked only when that failure occurs +- The suggested rigctld command uses the name that exists on the PC (`rigctld-wsjtx`) and omits + `-s` when the baud is Radio default +- The Quick preset picker shows the saved radio on startup instead of "Choose your radio…" + ## [0.6.14] - 2026-09-21 Run Tests works against a real radio. diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index fe60be7..8076368 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -402,4 +402,5 @@ Fortfahren? unser eigener Rahmen kam zurück — ein CI-V-Funkgerät mit eingeschaltetem Echo Back hört mit {0} Baud zu; jede bekannte CI-V-Adresse wird nun der Reihe nach abgefragt Ihr Funkgerät: {0} an {1} — {2} Baud, 8N{3} (8 Datenbits, keine Parität, {3} Stopp) Ihr Funkgerät ist über rigctld unter {0} erreichbar + Läuft gerade: {0} — mit großer Wahrscheinlichkeit das Programm, das die Schnittstelle belegt. Beenden Sie es (oder trennen Sie dort die Gerätesteuerung) und versuchen Sie es erneut diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index d9c231b..6200c6f 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -402,4 +402,5 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel volvió nuestra propia trama — una radio CI-V con Echo Back activado está escuchando a {0} baudios; preguntando a cada dirección CI-V conocida por turno Su radio: {0} en {1} — {2} baudios, 8N{3} (8 bits de datos, sin paridad, {3} de parada) Su radio es accesible a través de rigctld en {0} + En ejecución ahora mismo: {0} — casi con seguridad el programa que retiene el puerto. Ciérrelo (o desconecte su control de radio) y vuelva a intentarlo diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index bec74a7..fb9b179 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -402,4 +402,5 @@ Continuer ? notre propre trame est revenue — une radio CI-V avec Echo Back activé écoute à {0} bauds ; interrogation de chaque adresse CI-V connue à tour de rôle Votre radio : {0} sur {1} — {2} bauds, 8N{3} (8 bits de données, sans parité, {3} stop) Votre radio est joignable via rigctld à {0} + En cours d'exécution : {0} — presque certainement le programme qui occupe le port. Fermez-le (ou déconnectez-y le contrôle de la radio) et réessayez diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index b489118..296e947 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -402,4 +402,5 @@ 自分のフレームがそのまま返ってきました — Echo Back が有効な CI-V 無線機が {0} ボーで受信しています。既知の CI-V アドレスを順に問い合わせます あなたの無線機: {1} の {0} — {2} ボー、8N{3}(データ 8 ビット、パリティなし、ストップ {3} ビット) あなたの無線機は {0} の rigctld 経由で利用できます + 現在実行中: {0} — ほぼ確実にポートを占有しているプログラムです。終了する(またはそのリグ制御を切断する)してから、もう一度お試しください diff --git a/Resources/Strings.resx b/Resources/Strings.resx index fa5855a..42ba760 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -1289,4 +1289,7 @@ Continue? Your radio is reachable through rigctld at {0} + + Running right now: {0} — almost certainly the program holding the port. Close it (or disconnect its rig control) and try again + diff --git a/RigCheck.csproj b/RigCheck.csproj index 1fb7921..2d24e5e 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.14 - 0.6.14.0 - 0.6.14.0 + 0.6.15 + 0.6.15.0 + 0.6.15.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/DiagnosisEngine.cs b/Services/DiagnosisEngine.cs index a87de2a..fe4ced7 100644 --- a/Services/DiagnosisEngine.cs +++ b/Services/DiagnosisEngine.cs @@ -48,17 +48,46 @@ public DiagnosticResult Diagnose(RigctlResult result, ConnectionConfig cfg) FixCommand: null, LearnMoreUrl: BrandingInfo.HamlibDownloadUrl); - private static DiagnosticResult PortInUse(string port) => new( - Summary: Strings.Format("Diag_PortInUse_Summary", port), - Checks: + // The programs that hold a CAT port open, by process name. Checked only + // here, after the operator's own Run Tests has failed with "in use" — + // never on a timer or at startup. + private static readonly (string Process, string Name)[] PortHolders = + [ + ("wsjtx", "WSJT-X"), ("jt9", "WSJT-X"), ("fldigi", "Fldigi"), ("flrig", "Flrig"), + ("js8call", "JS8Call"), ("rigctld", "rigctld"), ("rigctld-wsjtx", "rigctld"), + ("RMS Express", "Winlink Express"), ("VARA", "VARA"), + ]; + + private static DiagnosticResult PortInUse(string port) + { + var running = PortHolders + .Where(h => + { + var found = System.Diagnostics.Process.GetProcessesByName(h.Process); + foreach (var p in found) p.Dispose(); + return found.Length > 0; + }) + .Select(h => h.Name) + .Distinct() + .ToList(); + + var checks = new List(); + if (running.Count > 0) + checks.Add(Strings.Format("Diag_PortInUse_Running", string.Join(", ", running))); + checks.AddRange( [ Strings.Get("Diag_PortInUse_1"), Strings.Get("Diag_PortInUse_2"), Strings.Get("Diag_PortInUse_3"), Strings.Get("Diag_PortInUse_4"), - ], - FixCommand: null, - LearnMoreUrl: null); + ]); + + return new( + Summary: Strings.Format("Diag_PortInUse_Summary", port), + Checks: checks.ToArray(), + FixCommand: null, + LearnMoreUrl: BrandingInfo.HelpUrl + "-port-in-use"); + } private static DiagnosticResult PortNotFound(string port) => new( Summary: Strings.Format("Diag_PortNotFound_Summary", port), @@ -120,8 +149,11 @@ public DiagnosticResult Diagnose(RigctlResult result, ConnectionConfig cfg) Strings.Format("Diag_Rigctld_3", cfg.RigctldPort), Strings.Get("Diag_Rigctld_4"), ], - FixCommand: $"rigctld -m {cfg.ModelId} -r {cfg.ComPort} -s {cfg.BaudRate} -t {cfg.RigctldPort}", - LearnMoreUrl: null); + // rigctld sits beside rigctl under the same naming (rigctld-wsjtx for WSJT-X's copy) + FixCommand: $"{RigctlCommandBuilder.ExeName.Replace("rigctl", "rigctld")} -m {cfg.ModelId} -r {cfg.ComPort}" + + (cfg.BaudRate > 0 ? $" -s {cfg.BaudRate}" : string.Empty) + + $" -t {cfg.RigctldPort}", + LearnMoreUrl: BrandingInfo.HelpUrl + "-firewall"); private static DiagnosticResult Cancelled() => new( Summary: Strings.Get("Diag_Cancelled_Summary"), diff --git a/Services/HamlibRunnerService.cs b/Services/HamlibRunnerService.cs index 8fc1386..6f69289 100644 --- a/Services/HamlibRunnerService.cs +++ b/Services/HamlibRunnerService.cs @@ -48,7 +48,7 @@ public async Task RunAsync(RigctlCommand command, var startInfo = new ProcessStartInfo { FileName = rigctlPath, - Arguments = string.Join(" ", command.Args.Select(QuoteIfNeeded)), + Arguments = string.Join(" ", Verbosity.Concat(command.Args).Select(QuoteIfNeeded)), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, @@ -93,7 +93,7 @@ await process "rigctl did not respond within the timeout period."); } - var stdout = stdoutBuilder.ToString().Trim(); + var stdout = StripBanner(stdoutBuilder.ToString()).Trim(); var stderr = stderrBuilder.ToString().Trim(); var exitCode = process.ExitCode; @@ -118,6 +118,24 @@ await process } } + // ── Verbosity ───────────────────────────────────────────────────────── + // At its default verbosity rigctl (Hamlib 4.7.1) says NOTHING when it + // fails — exit code 2 and empty output — so a port held by WSJT-X, a + // missing rigctld, and a radio that is switched off all looked the same. + // "-vv" makes it name the cause on stderr: + // serial_open: serial port COM3 is already open + // serial_open: serial port COM9 does not exist + // network_open: failed to connect to localhost:4532 + // and adds one banner line to stdout on success, which is stripped so + // the command's own output stays parseable: + // Opened rig model 3073, 'IC-7300' + + private static readonly string[] Verbosity = ["-vv"]; + + private static string StripBanner(string stdout) => + string.Join('\n', stdout.Split('\n') + .Where(l => !l.StartsWith("Opened rig model", StringComparison.Ordinal))); + // ── Error classification ────────────────────────────────────────────── private static RigctlError ClassifyError(int exitCode, string stderr, string stdout) @@ -125,20 +143,21 @@ private static RigctlError ClassifyError(int exitCode, string stderr, string std var combined = (stderr + stdout).ToLowerInvariant(); if (combined.Contains("port in use") || combined.Contains("access denied") - || combined.Contains("sharing violation")) + || combined.Contains("sharing violation") || combined.Contains("already open")) return RigctlError.PortInUse; if (combined.Contains("timeout") || combined.Contains("timed out")) return RigctlError.Timeout; if (combined.Contains("no such device") || combined.Contains("cannot open") - || combined.Contains("file not found")) + || combined.Contains("file not found") || combined.Contains("does not exist")) return RigctlError.PortNotFound; if (combined.Contains("invalid model") || combined.Contains("unknown rig")) return RigctlError.WrongModel; - if (combined.Contains("connection refused") || combined.Contains("rigctld")) + if (combined.Contains("connection refused") || combined.Contains("failed to connect") + || combined.Contains("rigctld")) return RigctlError.RigctldNotRunning; if (combined.Contains("no response") || combined.Contains("io_err")) diff --git a/ViewModels/ViewModels.cs b/ViewModels/ViewModels.cs index 607127a..ec6f9c7 100644 --- a/ViewModels/ViewModels.cs +++ b/ViewModels/ViewModels.cs @@ -220,6 +220,11 @@ public void LoadFrom(RigCheckSettings s) var saved = AvailablePorts.FirstOrDefault(p => p.PortName == s.ComPort); if (saved is not null) SelectedPort = saved; + + // Show the matching preset in the picker without re-applying its + // defaults — the saved baud and serial settings must win. + _selectedPreset = AvailablePresets.FirstOrDefault(p => p.HamlibModelId == s.RadioModelId); + OnPropertyChanged(nameof(SelectedPreset)); } public void SaveTo(RigCheckSettings s) From 4a7a48e88a055390ec54d92dee6aa6b8be6d63ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:59:57 +0000 Subject: [PATCH 11/17] docs: update translation coverage --- TRANSLATING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TRANSLATING.md b/TRANSLATING.md index 8aa9e9e..f77224f 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -16,11 +16,11 @@ starting from nothing, and it is the most useful thing you can do. | Language | File | Coverage | Status | | --- | --- | --- | --- | -| English (source) | `Strings.resx` | 100% (367 of 367) | Source | -| German (`de`) | `Strings.de.resx` | 100% (367 of 367) | Machine translated — review wanted | -| Spanish (`es`) | `Strings.es.resx` | 100% (367 of 367) | Machine translated — review wanted | -| French (`fr`) | `Strings.fr.resx` | 100% (367 of 367) | Machine translated — review wanted | -| Japanese (`ja`) | `Strings.ja.resx` | 100% (367 of 367) | Machine translated — review wanted | +| English (source) | `Strings.resx` | 100% (368 of 368) | Source | +| German (`de`) | `Strings.de.resx` | 100% (368 of 368) | Machine translated — review wanted | +| Spanish (`es`) | `Strings.es.resx` | 100% (368 of 368) | Machine translated — review wanted | +| French (`fr`) | `Strings.fr.resx` | 100% (368 of 368) | Machine translated — review wanted | +| Japanese (`ja`) | `Strings.ja.resx` | 100% (368 of 368) | Machine translated — review wanted | --- From 62ad73431ed08fc97d00eedcb00fe717b48d3b9b Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:04:20 -0400 Subject: [PATCH 12/17] Recognise Hamlib command errors at exit 0; unsupported query is a warning On a real IC-7300 "rigctl v" printed a trace dump ending in "Feature not available" and exited 0, so the VFO test passed with the dump as its message. HamlibRunnerService now finds Hamlib's error line in either stream regardless of exit code, maps "Feature not available" to RigctlError.NotSupported, and reports that line instead of the trace. GetVfo and GetSmeter return a Warning for NotSupported; DiagnosisEngine explains it. Result messages use the first output line only. Version 0.6.16. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 11 +++++++++++ Resources/Strings.de.resx | 4 ++++ Resources/Strings.es.resx | 4 ++++ Resources/Strings.fr.resx | 4 ++++ Resources/Strings.ja.resx | 4 ++++ Resources/Strings.resx | 12 ++++++++++++ RigCheck.csproj | 6 +++--- Services/DiagnosisEngine.cs | 7 +++++++ Services/HamlibRunnerService.cs | 29 +++++++++++++++++++++++++---- Services/TestRunnerService.cs | 25 +++++++++++++++++++++++-- 11 files changed, 98 insertions(+), 10 deletions(-) diff --git a/BrandingInfo.cs b/BrandingInfo.cs index 69c5ac5..daf1000 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.15"; + public const string Version = "0.6.16"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c196b..afd0cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.16] - 2026-09-21 + +First all-green run on an IC-7300; one false pass corrected. + +### Fixed +- "Get VFO" reported a pass with a page of Hamlib trace as the VFO name. The IC-7300 (like most + Icoms) has no get_vfo in Hamlib; rigctl printed "Feature not available" but still exited 0. + Hamlib's own error line is now recognised whatever the exit code, and an unsupported query is a + warning that says so — the radio is fine, the command just does not exist for it +- Result messages show only the first line of rigctl's output, never a trace + ## [0.6.15] - 2026-09-21 Failures now say why. diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index 8076368..d32b86d 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -403,4 +403,8 @@ Fortfahren? Ihr Funkgerät: {0} an {1} — {2} Baud, 8N{3} (8 Datenbits, keine Parität, {3} Stopp) Ihr Funkgerät ist über rigctld unter {0} erreichbar Läuft gerade: {0} — mit großer Wahrscheinlichkeit das Programm, das die Schnittstelle belegt. Beenden Sie es (oder trennen Sie dort die Gerätesteuerung) und versuchen Sie es erneut + Dieses Funkgerät meldet sein aktives VFO nicht über Hamlib (normal für {0} und die meisten Icoms) — kein Problem + Dieses Funkgerät meldet die Signalstärke nicht über Hamlib ({0}) — kein Problem + Der Hamlib-Treiber für das {0} implementiert diese Abfrage nicht. + Das Funkgerät antwortet; nur dieser eine Befehl existiert für es nicht. WSJT-X und die anderen Programme brauchen ihn nicht. diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index 6200c6f..6eaa0e5 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -403,4 +403,8 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Su radio: {0} en {1} — {2} baudios, 8N{3} (8 bits de datos, sin paridad, {3} de parada) Su radio es accesible a través de rigctld en {0} En ejecución ahora mismo: {0} — casi con seguridad el programa que retiene el puerto. Ciérrelo (o desconecte su control de radio) y vuelva a intentarlo + Esta radio no informa su VFO activo a través de Hamlib (normal para {0} y la mayoría de Icom) — no es un problema + Esta radio no informa la intensidad de señal a través de Hamlib ({0}) — no es un problema + El controlador de Hamlib para {0} no implementa esta consulta. + La radio responde; simplemente este comando no existe para ella. WSJT-X y los demás programas no lo necesitan. diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index fb9b179..6e47ef6 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -403,4 +403,8 @@ Continuer ? Votre radio : {0} sur {1} — {2} bauds, 8N{3} (8 bits de données, sans parité, {3} stop) Votre radio est joignable via rigctld à {0} En cours d'exécution : {0} — presque certainement le programme qui occupe le port. Fermez-le (ou déconnectez-y le contrôle de la radio) et réessayez + Cette radio n'indique pas son VFO actif via Hamlib (normal pour {0} et la plupart des Icom) — pas un problème + Cette radio n'indique pas la force du signal via Hamlib ({0}) — pas un problème + Le pilote Hamlib du {0} n'implémente pas cette requête. + La radio répond ; cette commande n'existe simplement pas pour elle. WSJT-X et les autres programmes n'en ont pas besoin. diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index 296e947..9287938 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -403,4 +403,8 @@ あなたの無線機: {1} の {0} — {2} ボー、8N{3}(データ 8 ビット、パリティなし、ストップ {3} ビット) あなたの無線機は {0} の rigctld 経由で利用できます 現在実行中: {0} — ほぼ確実にポートを占有しているプログラムです。終了する(またはそのリグ制御を切断する)してから、もう一度お試しください + この無線機は Hamlib 経由でアクティブ VFO を報告しません({0} や多くの Icom 機では正常)— 問題ありません + この無線機は Hamlib 経由で信号強度を報告しません({0})— 問題ありません + {0} 用の Hamlib ドライバーはこの問い合わせを実装していません。 + 無線機は応答しています。このコマンドがこの機種に存在しないだけです。WSJT-X などのプログラムには不要です。 diff --git a/Resources/Strings.resx b/Resources/Strings.resx index 42ba760..737bc89 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -1292,4 +1292,16 @@ Continue? Running right now: {0} — almost certainly the program holding the port. Close it (or disconnect its rig control) and try again + + This radio does not report its active VFO through Hamlib (normal for {0} and most Icoms) — not a problem + + + This radio does not report signal strength through Hamlib ({0}) — not a problem + + + Hamlib's driver for the {0} does not implement this query. + + + The radio is answering; this one command simply does not exist for it. WSJT-X and the other programs do not need it. + diff --git a/RigCheck.csproj b/RigCheck.csproj index 2d24e5e..d8e0b11 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.15 - 0.6.15.0 - 0.6.15.0 + 0.6.16 + 0.6.16.0 + 0.6.16.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/DiagnosisEngine.cs b/Services/DiagnosisEngine.cs index fe4ced7..2c08777 100644 --- a/Services/DiagnosisEngine.cs +++ b/Services/DiagnosisEngine.cs @@ -30,6 +30,7 @@ public DiagnosticResult Diagnose(RigctlResult result, ConnectionConfig cfg) RigctlError.NoResponse => NoResponse(cfg), RigctlError.WrongModel => WrongModel(cfg), RigctlError.RigctldNotRunning => RigctldNotRunning(cfg), + RigctlError.NotSupported => NotSupported(cfg), RigctlError.Cancelled => Cancelled(), _ => Unknown(result.ErrorMessage), }; @@ -155,6 +156,12 @@ private static DiagnosticResult PortInUse(string port) + $" -t {cfg.RigctldPort}", LearnMoreUrl: BrandingInfo.HelpUrl + "-firewall"); + private static DiagnosticResult NotSupported(ConnectionConfig cfg) => new( + Summary: Strings.Format("Diag_NotSupported_Summary", cfg.RadioModelName), + Checks: [Strings.Get("Diag_NotSupported_1")], + FixCommand: null, + LearnMoreUrl: null); + private static DiagnosticResult Cancelled() => new( Summary: Strings.Get("Diag_Cancelled_Summary"), Checks: [], diff --git a/Services/HamlibRunnerService.cs b/Services/HamlibRunnerService.cs index 6f69289..5b94dae 100644 --- a/Services/HamlibRunnerService.cs +++ b/Services/HamlibRunnerService.cs @@ -100,12 +100,18 @@ await process Log.Debug("rigctl exit={Exit} stdout={Out} stderr={Err}", exitCode, stdout, stderr); - if (exitCode == 0) + // rigctl exits 0 even when the command itself failed (seen with + // "v" on an IC-7300: "Feature not available", exit 0, plus a + // trace dump). Treat Hamlib's own error text as failure and + // keep only the message, never the trace. + var commandError = HamlibErrorLine(stdout) ?? HamlibErrorLine(stderr); + if (exitCode == 0 && commandError is null) return RigctlResult.Success(command, stdout); - // Exit code non-zero — classify the error - var error = ClassifyError(exitCode, stderr, stdout); - return RigctlResult.Failure(command, error, stderr.Length > 0 ? stderr : stdout); + var error = commandError is not null && commandError.Contains("Feature not available", StringComparison.OrdinalIgnoreCase) + ? RigctlError.NotSupported + : ClassifyError(exitCode, stderr, stdout); + return RigctlResult.Failure(command, error, commandError ?? (stderr.Length > 0 ? stderr : stdout)); } catch (OperationCanceledException) { @@ -132,6 +138,19 @@ await process private static readonly string[] Verbosity = ["-vv"]; + /// + /// Hamlib reports a failed command as "get_vfo: error = Feature not + /// available" or a bare "Feature not available" line, often buried in a + /// trace dump. Returns that one line, or null when there is none. + /// + private static string? HamlibErrorLine(string output) => + output.Split('\n') + .Select(l => l.Trim()) + .FirstOrDefault(l => l.Contains(": error = ", StringComparison.Ordinal) + || l.StartsWith("Feature not available", StringComparison.OrdinalIgnoreCase) + || l.StartsWith("Communication timed out", StringComparison.OrdinalIgnoreCase) + || l.StartsWith("IO error", StringComparison.OrdinalIgnoreCase)); + private static string StripBanner(string stdout) => string.Join('\n', stdout.Split('\n') .Where(l => !l.StartsWith("Opened rig model", StringComparison.Ordinal))); @@ -199,6 +218,8 @@ public enum RigctlError NoResponse, WrongModel, RigctldNotRunning, + /// The radio's Hamlib backend does not implement this query (get_vfo on many Icoms). Not a fault. + NotSupported, Cancelled, Unknown, } diff --git a/Services/TestRunnerService.cs b/Services/TestRunnerService.cs index a70e03d..00a0048 100644 --- a/Services/TestRunnerService.cs +++ b/Services/TestRunnerService.cs @@ -196,7 +196,13 @@ private async Task TestGetSmeterAsync(ConnectionConfig cfg, Cancella if (result.IsSuccess) return TestResult.Warning( TestId.GetSmeter, - Strings.Format("Msg_SmeterUnparsed", result.RawOutput), + Strings.Format("Msg_SmeterUnparsed", FirstLine(result.RawOutput)), + cmd.DisplayCommand); + + if (result.Error == RigctlError.NotSupported) + return TestResult.Warning( + TestId.GetSmeter, + Strings.Format("Msg_SmeterNotSupported", cfg.RadioModelName), cmd.DisplayCommand); return TestResult.Fail( @@ -215,7 +221,17 @@ private async Task TestGetVfoAsync(ConnectionConfig cfg, Cancellatio if (result.IsSuccess) return TestResult.Pass( TestId.GetVfo, - Strings.Format("Msg_Vfo", result.RawOutput.Trim()), + Strings.Format("Msg_Vfo", FirstLine(result.RawOutput)), + cmd.DisplayCommand); + + // Many Icom backends (IC-7300 included) have no get_vfo: Hamlib says + // "Feature not available". The radio is fine; the query just does + // not exist for it. A warning, so the run can still be all green + // in spirit, with the reason spelled out. + if (result.Error == RigctlError.NotSupported) + return TestResult.Warning( + TestId.GetVfo, + Strings.Format("Msg_VfoNotSupported", cfg.RadioModelName), cmd.DisplayCommand); return TestResult.Fail( @@ -305,6 +321,11 @@ private static bool TryParseSmeter(string raw, out string sLabel, out string dbm return true; } + /// The first non-empty line of rigctl output — the value, never a trace. + private static string FirstLine(string output) => + output.Split(' +', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? string.Empty; + private static IEnumerable RemainingTests() => [ TestId.GetFrequency, TestId.GetMode, TestId.GetPtt, From bfb4b5a018298a733ca53b5d6e68b1046a30dc70 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:04:32 +0000 Subject: [PATCH 13/17] docs: update translation coverage --- TRANSLATING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TRANSLATING.md b/TRANSLATING.md index f77224f..d4b6dbd 100644 --- a/TRANSLATING.md +++ b/TRANSLATING.md @@ -16,11 +16,11 @@ starting from nothing, and it is the most useful thing you can do. | Language | File | Coverage | Status | | --- | --- | --- | --- | -| English (source) | `Strings.resx` | 100% (368 of 368) | Source | -| German (`de`) | `Strings.de.resx` | 100% (368 of 368) | Machine translated — review wanted | -| Spanish (`es`) | `Strings.es.resx` | 100% (368 of 368) | Machine translated — review wanted | -| French (`fr`) | `Strings.fr.resx` | 100% (368 of 368) | Machine translated — review wanted | -| Japanese (`ja`) | `Strings.ja.resx` | 100% (368 of 368) | Machine translated — review wanted | +| English (source) | `Strings.resx` | 100% (372 of 372) | Source | +| German (`de`) | `Strings.de.resx` | 100% (372 of 372) | Machine translated — review wanted | +| Spanish (`es`) | `Strings.es.resx` | 100% (372 of 372) | Machine translated — review wanted | +| French (`fr`) | `Strings.fr.resx` | 100% (372 of 372) | Machine translated — review wanted | +| Japanese (`ja`) | `Strings.ja.resx` | 100% (372 of 372) | Machine translated — review wanted | --- From cd2c9ea17bc12aff900fd2b48c3f7c637922852a Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:06:11 -0400 Subject: [PATCH 14/17] Fix newline literal in FirstLine --- Services/TestRunnerService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Services/TestRunnerService.cs b/Services/TestRunnerService.cs index 00a0048..ac9aef3 100644 --- a/Services/TestRunnerService.cs +++ b/Services/TestRunnerService.cs @@ -323,8 +323,7 @@ private static bool TryParseSmeter(string raw, out string sLabel, out string dbm /// The first non-empty line of rigctl output — the value, never a trace. private static string FirstLine(string output) => - output.Split(' -', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? string.Empty; + output.Split('\n', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? string.Empty; private static IEnumerable RemainingTests() => [ From 4e2739a9cc0297c5d95e5b07931c1cc6fb7a0ebd Mon Sep 17 00:00:00 2001 From: Mark McDow <55188587+Computer-Tsu@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:27:09 -0400 Subject: [PATCH 15/17] Raw console accepts a pasted full command line; screenshots from the IC-7300 run RawCommand strips a leading rigctl exe name and the -m/-r/-s/-t/-C/-v options so the Connection panel's settings apply once. Placeholder text in the console input says a subcommand is expected. Five screenshots from the first real-hardware run added under docs/screenshots and referenced from the README. Version 0.6.17. --- BrandingInfo.cs | 2 +- CHANGELOG.md | 10 +++++++ README.md | 8 ++++- Resources/Strings.de.resx | 1 + Resources/Strings.es.resx | 1 + Resources/Strings.fr.resx | 1 + Resources/Strings.ja.resx | 1 + Resources/Strings.resx | 3 ++ Resources/Styles.xaml | 1 + RigCheck.csproj | 6 ++-- Services/RigctlCommandBuilder.cs | 28 ++++++++++++++++-- Views/Converters.cs | 9 ++++++ Views/MainWindow.xaml | 21 +++++++++---- ...Screenshot_RigCheck-0.6.16-FindMyRadio.png | Bin 0 -> 189496 bytes ...shot_RigCheck-0.6.16-FindMyRadioDialog.png | Bin 0 -> 28304 bytes .../Screenshot_RigCheck-0.6.16-Main.png | Bin 0 -> 52518 bytes .../Screenshot_RigCheck-0.6.16-RunTests.png | Bin 0 -> 111009 bytes .../Screenshot_RigCheck-0.6.16-ScanPC.png | Bin 0 -> 141674 bytes 18 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 docs/screenshots/Screenshot_RigCheck-0.6.16-FindMyRadio.png create mode 100644 docs/screenshots/Screenshot_RigCheck-0.6.16-FindMyRadioDialog.png create mode 100644 docs/screenshots/Screenshot_RigCheck-0.6.16-Main.png create mode 100644 docs/screenshots/Screenshot_RigCheck-0.6.16-RunTests.png create mode 100644 docs/screenshots/Screenshot_RigCheck-0.6.16-ScanPC.png diff --git a/BrandingInfo.cs b/BrandingInfo.cs index daf1000..e809836 100644 --- a/BrandingInfo.cs +++ b/BrandingInfo.cs @@ -12,7 +12,7 @@ public static class BrandingInfo public const string AppName = "RigCheck"; public const string SuiteName = "ShackDesk"; public const string FullName = "RigCheck by ShackDesk"; - public const string Version = "0.6.16"; + public const string Version = "0.6.17"; public const string Tagline = "Know your rig is ready"; // ── Developer / publisher ──────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index afd0cbd..53a23a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to RigCheck are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for milestones. +## [0.6.17] - 2026-09-21 + +### Fixed +- Pasting a full command line from the results (`rigctl-wsjtx -m 3073 -r COM3 -s 115200 f`) into + the raw console sent the connection options twice; the exe name and connection options are now + stripped so only the subcommand runs, and the box shows a placeholder saying so + +### Added +- Screenshots from the first IC-7300 run in the README + ## [0.6.16] - 2026-09-21 First all-green run on an IC-7300; one false pass corrected. diff --git a/README.md b/README.md index c48ab04..f310a32 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ RigCheck is the second ShackDesk suite application. "Know your rig is ready."
-![RigCheck main window](docs/screenshots/Screenshot_RigCheck-0.6.4-Main.png) +![RigCheck — Find my radio found an IC-7300, verified it with Hamlib, and handed over the WSJT-X settings](docs/screenshots/Screenshot_RigCheck-0.6.16-FindMyRadio.png) Serial CAT communication diagnostics using Hamlib. Verify your radio connection before it matters. @@ -151,6 +151,9 @@ Target users: is never a valid selection, so a passing result always means a real radio answered ### PC Scan (no radio needed) + +![Scan PC results](docs/screenshots/Screenshot_RigCheck-0.6.16-ScanPC.png) + The **Scan PC** button checks the computer side before any radio is involved, so "Run Tests is greyed out — why?" has an answer: @@ -199,6 +202,9 @@ ticked ports. The data files (`rig_families.json`, `rig_ids.json`, `port_skip_pa choose a built-in query by name and cannot contain command bytes. Design: `docs/discovery-flow.md`. ### Diagnostic Test Suite + +![Run Tests against an IC-7300](docs/screenshots/Screenshot_RigCheck-0.6.16-RunTests.png) + Run a sequence of standard Hamlib queries and display pass/fail results in plain English: diff --git a/Resources/Strings.de.resx b/Resources/Strings.de.resx index d32b86d..fec602a 100644 --- a/Resources/Strings.de.resx +++ b/Resources/Strings.de.resx @@ -407,4 +407,5 @@ Fortfahren? Dieses Funkgerät meldet die Signalstärke nicht über Hamlib ({0}) — kein Problem Der Hamlib-Treiber für das {0} implementiert diese Abfrage nicht. Das Funkgerät antwortet; nur dieser eine Befehl existiert für es nicht. WSJT-X und die anderen Programme brauchen ihn nicht. + nur der Unterbefehl, z. B. f m t l STRENGTH — eine eingefügte vollständige Befehlszeile wird für Sie gekürzt diff --git a/Resources/Strings.es.resx b/Resources/Strings.es.resx index 6eaa0e5..87bfcd9 100644 --- a/Resources/Strings.es.resx +++ b/Resources/Strings.es.resx @@ -407,4 +407,5 @@ Los detalles se han guardado en el registro. Si esto sigue ocurriendo, infórmel Esta radio no informa la intensidad de señal a través de Hamlib ({0}) — no es un problema El controlador de Hamlib para {0} no implementa esta consulta. La radio responde; simplemente este comando no existe para ella. WSJT-X y los demás programas no lo necesitan. + solo el subcomando, p. ej. f m t l STRENGTH — una línea de comando completa pegada se recorta automáticamente diff --git a/Resources/Strings.fr.resx b/Resources/Strings.fr.resx index 6e47ef6..20fc912 100644 --- a/Resources/Strings.fr.resx +++ b/Resources/Strings.fr.resx @@ -407,4 +407,5 @@ Continuer ? Cette radio n'indique pas la force du signal via Hamlib ({0}) — pas un problème Le pilote Hamlib du {0} n'implémente pas cette requête. La radio répond ; cette commande n'existe simplement pas pour elle. WSJT-X et les autres programmes n'en ont pas besoin. + sous-commande seule, p. ex. f m t l STRENGTH — une ligne de commande complète collée est raccourcie pour vous diff --git a/Resources/Strings.ja.resx b/Resources/Strings.ja.resx index 9287938..ed11cb2 100644 --- a/Resources/Strings.ja.resx +++ b/Resources/Strings.ja.resx @@ -407,4 +407,5 @@ この無線機は Hamlib 経由で信号強度を報告しません({0})— 問題ありません {0} 用の Hamlib ドライバーはこの問い合わせを実装していません。 無線機は応答しています。このコマンドがこの機種に存在しないだけです。WSJT-X などのプログラムには不要です。 + サブコマンドのみ(例: f m t l STRENGTH)— 完全なコマンド行を貼り付けても自動的に短縮されます diff --git a/Resources/Strings.resx b/Resources/Strings.resx index 737bc89..440e1cd 100644 --- a/Resources/Strings.resx +++ b/Resources/Strings.resx @@ -1304,4 +1304,7 @@ Continue? The radio is answering; this one command simply does not exist for it. WSJT-X and the other programs do not need it. + + subcommand only, e.g. f m t l STRENGTH — a pasted full command line is trimmed for you + diff --git a/Resources/Styles.xaml b/Resources/Styles.xaml index 843f7b9..89e838b 100644 --- a/Resources/Styles.xaml +++ b/Resources/Styles.xaml @@ -19,6 +19,7 @@ + diff --git a/RigCheck.csproj b/RigCheck.csproj index d8e0b11..3efb755 100644 --- a/RigCheck.csproj +++ b/RigCheck.csproj @@ -35,9 +35,9 @@ RigCheck Assets\rigcheck.ico app.manifest - 0.6.16 - 0.6.16.0 - 0.6.16.0 + 0.6.17 + 0.6.17.0 + 0.6.17.0 My Computer Guru LLC Copyright © 2025-2026 My Computer Guru LLC RigCheck by ShackDesk diff --git a/Services/RigctlCommandBuilder.cs b/Services/RigctlCommandBuilder.cs index 056f50d..9e09502 100644 --- a/Services/RigctlCommandBuilder.cs +++ b/Services/RigctlCommandBuilder.cs @@ -148,16 +148,38 @@ public static RigctlCommand SetFrequency(ConnectionConfig cfg, long frequencyHz) ///
public static RigctlCommand RawCommand(ConnectionConfig cfg, string rawInput) { - var tokens = rawInput.Trim().Split(' ', - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + var tokens = StripConnectionPrefix(rawInput.Trim().Split(' ', + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); var conn = ConnArgs(cfg); return conn with { Args = [..conn.Args, ..tokens], - DisplayCommand = $"{ExeName} {conn.ConnectionLabel} {rawInput.Trim()}" + DisplayCommand = $"{ExeName} {conn.ConnectionLabel} {string.Join(' ', tokens)}" }; } + // Operators paste the whole line the results panel shows them — + // "rigctl-wsjtx -m 3073 -r COM3 -s 115200 f" — into a console that only + // wants "f". Drop the exe name and the connection options so the panel's + // settings apply once, not twice. + private static readonly HashSet ConnectionOptions = + ["-m", "-r", "-s", "-t", "-C", "-p", "-P", "-c", "--model", "--rig-file", "--serial-speed"]; + + private static string[] StripConnectionPrefix(string[] tokens) + { + if (tokens.Length == 0 || !tokens[0].StartsWith("rigctl", StringComparison.OrdinalIgnoreCase)) + return tokens; + + var rest = new List(); + for (int i = 1; i < tokens.Length; i++) + { + if (ConnectionOptions.Contains(tokens[i]) && i + 1 < tokens.Length) { i++; continue; } + if (tokens[i].StartsWith("-v", StringComparison.Ordinal)) continue; + rest.Add(tokens[i]); + } + return rest.ToArray(); + } + // ── Private helpers ────────────────────────────────────────────────── private static RigctlCommand ConnArgs(ConnectionConfig cfg) => diff --git a/Views/Converters.cs b/Views/Converters.cs index ee36589..0c9b174 100644 --- a/Views/Converters.cs +++ b/Views/Converters.cs @@ -61,3 +61,12 @@ public object Convert(object value, Type t, object p, CultureInfo c) public object ConvertBack(object value, Type t, object p, CultureInfo c) => throw new NotSupportedException(); } + +/// Visible while a string is empty — used for placeholder text over an empty TextBox. +public sealed class EmptyToVisibleConverter : IValueConverter +{ + public object Convert(object value, Type t, object p, CultureInfo c) => + string.IsNullOrEmpty(value as string) ? Visibility.Visible : Visibility.Collapsed; + public object ConvertBack(object value, Type t, object p, CultureInfo c) => + throw new NotSupportedException(); +} diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml index 5b43c74..2d267a3 100644 --- a/Views/MainWindow.xaml +++ b/Views/MainWindow.xaml @@ -296,12 +296,21 @@