Add the digital interface, running MMTTY under Wine
MMTTY and 2Tone have no socket or pipe interface: N1MM hosts XMMT.ocx and exchanges window messages with the engine. A Linux process cannot load that control, so bridge/nonemm-mmtty-bridge.exe hosts it under Wine and passes lines over its standard input and output. docs/digital-bridge.md states the protocol and what the control needs. The window is N1MM's: receive pane with coloured callsigns, grab list, call stacking, twenty-four macro buttons and the engine controls. One left click copies what is under it — a callsign to the callsign box, anything else to the exchange box the contest keeps for that kind of value. Config > Digital registers XMMT.ocx in the Wine prefix on its own, making the prefix first if it is not there. The engine, the bridge and the control start at the copies shipped beside the program. The bridge reads the control's own events. OnTranslateMessage carries only the messages the control has no event for, so nothing was ever decoded through it. hamlib's data mode names read as digital modes now, and a mode typed into the callsign box changes mode the way a frequency changes band, so a station with no radio can reach RTTY at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
141
docs/digital-bridge.md
Normal file
141
docs/digital-bridge.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# MMTTY and 2Tone through Wine
|
||||
|
||||
MMTTY is a Windows program with no socket, pipe or file interface. N1MM does not
|
||||
talk to it directly either: it hosts `XMMT.ocx`, a 32-bit ActiveX control that
|
||||
starts `MMTTY.EXE` and exchanges window messages with it. A Linux process cannot
|
||||
load that control, so Nonemm runs a small Windows program under Wine —
|
||||
`bridge/nonemm-mmtty-bridge.exe` — which hosts the control and passes lines over
|
||||
its standard input and output.
|
||||
|
||||
```
|
||||
Nonemm ──stdin/stdout──▶ bridge.exe (Wine) ──XMMT.ocx──▶ MMTTY.EXE / 2Tone
|
||||
```
|
||||
|
||||
2Tone uses the same control, so it runs the same way. The only differences are
|
||||
the command line and the window title, both in `MmttyOptions`.
|
||||
|
||||
## The line protocol
|
||||
|
||||
One line per message: a verb, then fields, separated by tabs. Backslash, tab,
|
||||
carriage return and newline inside a field are backslash-escaped, so a field
|
||||
never splits a line. `BridgeLine.cs` and `bridge/Protocol.cpp` are the two ends
|
||||
of it.
|
||||
|
||||
Wine writes its own diagnostics to standard error, which leaves standard output
|
||||
to the protocol.
|
||||
|
||||
To the bridge:
|
||||
|
||||
| Line | What the bridge does |
|
||||
|---|---|
|
||||
| `open <title> <port> <command line>` | sets `Title`, `ComName` and `InvokeCommand`, sets `bActive`, then posts the host window handle |
|
||||
| `send <text>` | `SendString` |
|
||||
| `ptt <0\|1>` | `SetMmttyPTT` |
|
||||
| `post <message> <parameter>` | `PostMmttyMessage`, for everything in `MmttyMessage` |
|
||||
| `close` | shuts the engine down and leaves the bridge running |
|
||||
| `quit` | shuts the engine down and exits |
|
||||
|
||||
From the bridge:
|
||||
|
||||
| Line | Where it comes from |
|
||||
|---|---|
|
||||
| `ready` | the control is created and listening |
|
||||
| `connected <version>` | `OnConnected`, with `verMMTTY` |
|
||||
| `disconnected <status>` | `OnDisconnected`: 0 failed to close, 1 closed, 2 failed to start |
|
||||
| `rx <code>` | one decoded character |
|
||||
| `tx <0\|1>` | the engine started or stopped transmitting |
|
||||
| `mark <hz>`, `space <hz>` | the tone pair moved |
|
||||
| `switch <bits>`, `view <bits>` | AFC (4), net (8) and reverse (256), and the engine's view state |
|
||||
| `log <text>`, `error <text>` | anything the bridge has to say |
|
||||
|
||||
Each of those comes from an event of the control's own: `OnCharRcvd`,
|
||||
`OnPttEvent`, `OnFreqChanged` (mark and space in one event), `OnSwitchChanged`
|
||||
and `OnViewChanged`. The control raises `OnTranslateMessage` only for the
|
||||
MMTTY messages it has no event for — width, resolution, thread and the like —
|
||||
so the bridge does not listen to it. N1MM reads the characters the same way, in
|
||||
`DigitalMultiRXWindow.XMMR_OnCharRcvd`; the `OnTranslateMessage` handler in
|
||||
`DigitalInterface.cs` that maps codes 32771 to 32778 never runs with this
|
||||
version of the control.
|
||||
|
||||
## Starting the engine
|
||||
|
||||
The order is N1MM's, from `DigitalInterface.cs`:
|
||||
|
||||
1. Read `Mmtty.INI` beside the program. `[Define] PTT` is the serial port, and
|
||||
`AFC`, `TxNet` and `Rev` are the switch word the engine starts with. Without
|
||||
them, the first toggle moves the wrong way.
|
||||
2. Build the command line: the quoted program, the window size (`-r` normal,
|
||||
`-t` small, `-s` medium 1, `-u` medium 2), `-a` when the window is not on
|
||||
top, and `-Z` for MMTTY but not for 2Tone.
|
||||
3. `open`. The control appends `-h<handle>` of its own, which is how the engine
|
||||
finds its way back.
|
||||
4. An engine that answers `disconnected 2` is started again, up to ten times.
|
||||
That is N1MM's retry, and it is needed: MMTTY does not always come up first
|
||||
time.
|
||||
|
||||
## What the control needs that is not obvious
|
||||
|
||||
Three things cost a day between them, so they are written down:
|
||||
|
||||
- **`IPersistStreamInit::InitNew` before anything else.** The control is MFC
|
||||
based, and until it is initialised every dispatch call returns `E_UNEXPECTED`
|
||||
— property gets included. Creating and activating it is not enough.
|
||||
- **A real window.** In-place activation needs a parent window, so the bridge
|
||||
needs an X display even though it shows nothing.
|
||||
- **The control's own shutdown blocks.** `IOleObject::Close` and setting
|
||||
`bActive` to false wait for the engine while holding the thread that would
|
||||
have to pump the messages, so neither returns. The bridge posts the shutdown
|
||||
message, closes the engine's windows, and kills the process if it is still
|
||||
there five seconds later.
|
||||
|
||||
## Setting it up
|
||||
|
||||
Config > Digital has a Register button. It does what the next paragraph
|
||||
describes, in the prefix named in the same dialog, and writes the result under
|
||||
the button. Press it once, before the first start.
|
||||
|
||||
The steps are in `WinePrefixSetup`. A prefix that is not there yet is made with
|
||||
`wineboot -u` and `WINEARCH=win32`, because the control is 32 bit. The control
|
||||
is then copied into the Windows folder and registered:
|
||||
|
||||
```sh
|
||||
export WINEPREFIX=~/.wine-nonemm WINEARCH=win32
|
||||
wineboot -u
|
||||
cp bridge/XMMT.ocx "$WINEPREFIX/drive_c/windows/system32/"
|
||||
wine regsvr32 'C:\windows\system32\XMMT.ocx'
|
||||
```
|
||||
|
||||
A 64-bit prefix keeps 32-bit code in `syswow64` and has a second `regsvr32.exe`
|
||||
there. That is the one to register the control with: the 64-bit one cannot load
|
||||
it.
|
||||
|
||||
`XMMT.ocx` ships with N1MM Logger+ and is distributed with Nonemm in the
|
||||
`bridge` folder. MMTTY itself is downloaded separately and can live anywhere in
|
||||
the prefix.
|
||||
|
||||
Build the bridge with the mingw-w64 cross compiler:
|
||||
|
||||
```sh
|
||||
make -C bridge
|
||||
```
|
||||
|
||||
## Where the program looks
|
||||
|
||||
MMTTY, the bridge and `XMMT.ocx` are distributed with Nonemm, in `mmtty` and
|
||||
`bridge` folders beside the program, and the build copies them there from the
|
||||
working copy. Config > Digital starts at those three paths, so an operator who
|
||||
runs the distributed copy has nothing to fill in. An engine kept somewhere else, or
|
||||
2Tone in place of MMTTY, is browsed for in the same dialog. Neither program is
|
||||
in the repository.
|
||||
|
||||
The bridge keeps its own window hidden. Started with `--show` it shows it, which
|
||||
is the way to see what the control is doing.
|
||||
|
||||
## What has not been tested
|
||||
|
||||
The bridge has been run against MMTTY 1.70 under Wine 10: it starts the engine,
|
||||
connects, reports the version, takes text, opens MMTTY's setup window, moves
|
||||
the tone pair and reports `mark`, `space`, `switch`, `view` and `tx` back. It
|
||||
has never been run with a sound card, so nothing has been decoded and no `rx`
|
||||
line has ever come from a real signal. FSK keying through EXTFSK and PTT on a
|
||||
serial port have not been tried either, and neither has 2Tone.
|
||||
@@ -17,12 +17,23 @@ is still sitting there undiscovered.
|
||||
| `ClusterClient` | a node fake over a real socket, sending the telnet negotiation, the login prompt and spot lines | no live cluster node. Which nodes send bare CR, and which send option negotiation, is guessed from N1MM's code. |
|
||||
| `StationNetwork` | the message format, round-tripped | no second station, and no N1MM on the same network. |
|
||||
| `CwDaemonSender` | the UDP messages, and a fake daemon that answers the `<ESC>h` reply request | no `cwdaemon`, no radio keyed. |
|
||||
| `MmttyEngine` and the Wine bridge | MMTTY 1.70 under Wine 10, started and stopped through `XMMT.ocx` | no sound card, so nothing has been decoded or transmitted and no `rx` or `tx` line has come from a real signal. No FSK through EXTFSK, no PTT on a serial port, and 2Tone has never been run. `docs/digital-bridge.md` |
|
||||
| `WinkeyerSender` | the status-byte reader, on its own | no test of the serial side, and no WinKeyer. The host-mode open sequence is from the WinKeyer datasheet; the status bits are from N1MM's `Winkey.cs`. |
|
||||
|
||||
The Cabrillo output has not been put in front of a contest sponsor's robot.
|
||||
|
||||
## Half-built
|
||||
|
||||
**The digital window: what N1MM has and this does not.** The window is here —
|
||||
receive pane, coloured callsigns, grab list, call stacking, twenty-four macro
|
||||
buttons, the engine controls — and the function keys, ESM and the QTC window
|
||||
send through the engine on a digital mode. Left out: the waterfall, the spectrum
|
||||
and the XY scope, which the engine reports and nothing draws; more than one
|
||||
receive pane, which is MMVARI's multi-channel decoding rather than MMTTY's;
|
||||
sending a text file; and N1MM's Send All, which keys a whole QTC series at once.
|
||||
The other interfaces N1MM offers — fldigi, MMVARI, the hardware TNCs — are not
|
||||
here either, though `DigitalEngine` is the place to add them.
|
||||
|
||||
**Voice keying.** `MessageSender` was written to cover a voice keyer playing a
|
||||
recording, and nothing implements it. No DVK support either, so the second radio
|
||||
of an SO2R station cannot call CQ by voice. Alternating CQ therefore works on CW
|
||||
|
||||
Reference in New Issue
Block a user