using Nonemm.Digital; namespace Nonemm.EngineProbe; /// Starts MMTTY through the bridge, asks it the questions in `EngineProbe`, and /// writes what it answered to a file. It has to run where MMTTY runs: a sound /// card and a Wine prefix with XMMT.ocx registered. public static class Program { private const string Usage = """ usage: engine probe --engine [options] --engine the engine to start, as a Linux path --bridge the bridge program (default bridge/nonemm-mmtty-bridge.exe) --prefix WINEPREFIX to run in (default: Wine's own) --ptt serial port to key (default: none, so no radio is keyed) --out where to write the report (default engine-probe.log) --log where to write the protocol log, if it is wanted The probe transmits: MMTTY makes tones on the sound card for about half a minute. It keys no serial port unless --ptt says so, but a rig listening to that sound card through VOX will still go on the air. """; public static async Task Main(string[] arguments) { Dictionary options; try { options = Read(arguments); } catch (ArgumentException problem) { Console.Error.WriteLine(problem.Message); Console.Error.WriteLine(); Console.Error.WriteLine(Usage); return 1; } using CancellationTokenSource stopping = new(); Console.CancelKeyPress += (_, e) => { e.Cancel = true; stopping.Cancel(); }; using ProbeLog log = new(Option(options, "out", "engine-probe.log")); MmttyEngine engine = new( new WineBridgeChannel( Option(options, "bridge", Path.Combine("bridge", "nonemm-mmtty-bridge.exe")), options.GetValueOrDefault("prefix"), logFolder: options.GetValueOrDefault("log")), new MmttyOptions { EnginePath = options["engine"], PttPort = Option(options, "ptt", ""), }); try { log.Write($"starting {options["engine"]}"); await engine.StartAsync(stopping.Token).ConfigureAwait(false); log.Write($"MMTTY {engine.Version} is up"); await new EngineProbe(engine, log).RunAsync(stopping.Token).ConfigureAwait(false); } catch (OperationCanceledException) { log.Write("stopped"); } catch (Exception problem) { log.Write($"failed: {problem.Message}"); Console.Error.WriteLine(problem); return 1; } finally { await Shutdown(engine, log).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine($"the report is in {log.Path}"); } return 0; } /// The engine is left keyed if the probe stopped partway through, so the /// transmission is aborted before the engine is shut down, whatever /// happened. private static async Task Shutdown(MmttyEngine engine, ProbeLog log) { try { await engine.AbortAsync().ConfigureAwait(false); await engine.StopAsync().ConfigureAwait(false); } catch (Exception problem) { log.Write($"the engine did not shut down cleanly: {problem.Message}"); } engine.Dispose(); } private static Dictionary Read(string[] arguments) { Dictionary options = []; for (int i = 0; i < arguments.Length; i += 2) { if (!arguments[i].StartsWith("--", StringComparison.Ordinal)) { throw new ArgumentException($"expected an option, found {arguments[i]}"); } if (i + 1 >= arguments.Length) { throw new ArgumentException($"{arguments[i]} needs a value"); } options[arguments[i][2..]] = arguments[i + 1]; } if (!options.ContainsKey("engine")) { throw new ArgumentException("--engine is required"); } return options; } private static string Option(Dictionary options, string name, string fallback) => options.TryGetValue(name, out string? given) ? given : fallback; }