Nothing was ever transmitted. SetMmttyPTT does not start a transmission: N1MM
calls it with 1 to stop once the buffer is empty, which is its XmitOff, and with
0 to stop now, which is its AbortXmit. A transmission starts by setting the
control's PTT property, in XmitOn.
So the bridge learns `key <0|1>` for that property, and MmttyEngine now keys
with it, ends a message with SetMmttyPTT(1) so the buffer still goes out, and
aborts with SetMmttyPTT(0). This is the digital {TX} in the entry window and the
digital window as well as the probe: none of them could key the engine before.
The probe checks that the engine keys before it measures anything, and stops
with a plain statement if it does not, rather than reporting numbers from an
engine sitting still. It also asks a new question: whether the engine holds a
word until the space after it, which is MMTTY's Way to send. Received characters
are marked as noise while the engine is not transmitting, since a machine with a
sound card decodes the band all the way through the run.
The first run on a real engine says MMTTY 1.70 connects, the control answers
TxBufLen and refuses NotAProperty with DISP_E_UNKNOWNNAME. What TxBufLen counts
is still open: read while nothing was transmitting it rose over time and rose by
four after four backspaces, which is not what characters-left would do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
152 lines
6.9 KiB
Markdown
152 lines
6.9 KiB
Markdown
# 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` |
|
|
| `key <0\|1>` | sets the control's `PTT` property, which is what starts a transmission |
|
|
| `ptt <0\|1>` | `SetMmttyPTT`: 0 stops now, 1 stops once the buffer is empty |
|
|
| `post <message> <parameter>` | `PostMmttyMessage`, for everything in `MmttyMessage` |
|
|
| `buffer [property]` | reads `TxBufLen`, or the property named instead, and answers `buffer` |
|
|
| `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 |
|
|
| `buffer <n>` | how many characters the engine still has to transmit, or -1 when the control would not say |
|
|
| `log <text>`, `error <text>` | anything the bridge has to say |
|
|
|
|
`buffer` is the one thing that is asked for rather than reported: the control
|
|
has no event for the length of the transmit buffer, so it is polled. A property
|
|
name in the request is only for finding out what the control answers to; a name
|
|
it does not know fails with `DISP_E_UNKNOWNNAME`, which comes back as a `log`
|
|
line and `buffer -1` rather than an `error`, because a property the control will
|
|
not answer stops nothing.
|
|
|
|
Each of the others 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.
|