MiniPlex and Linux – the socat edition

Getting the Windows executable for the MiniPlex configuration program working was pretty neat. What would be neater is being able to perhaps write a Linux-native (or platform-agnostic) program that can at least display the configuration and stream the activity. Perhaps even update the configuration, though new versions of the firmware would result in new features to write code for.

The problem is, the MiniPlex is on Blue Opal, and writing code on a laptop on a boat is not great – I can do it, but I much prefer to use my home PC with twin monitors (usually code on one, reference documentation on the other).

As I noted in the previous post I actually have a video capture, and text file, of interactions with the software and hardware. This means I could at least set up a program that can do things like stream random NMEA events, or simulate responses to instructions. It won’t be perfect, because a bug in the simulation might result in incorrect code being written.

So, how can I get a COM port set up on a PC that has no serial ports, and no MiniPlex connected to it over USB? socat.

socat is a multipurpose relay (SOcket CAT), according to its man page. I just need to work out the right options to get it to send a text file to what looks like a COM port when a request is made to that port. Then I need to make that virtual serial port show up in Wine. Can’t be too hard, can it?

Linking com1 to a program

Well, socat doesn’t have the easiest man page to read, but there’s plenty of examples on the ‘net and I ended up with

socat -d -d -lpMockFTDI pty,link=./com1,rawer EXEC:test.py,pty,rawer

which resulted in

2023/05/07 22:16:31 MockFTDI[23378] N PTY is /dev/pts/3
2023/05/07 22:16:31 MockFTDI[23378] N forking off child, using pty for reading and writing
2023/05/07 22:16:31 MockFTDI[23378] N forked off child process 23379
2023/05/07 22:16:31 MockFTDI[23378] N forked off child process 23379
2023/05/07 22:16:31 MockFTDI[23378] N starting data transfer loop with FDs [5,5] and [7,7]
2023/05/07 22:16:31 MockFTDI[23379] N execvp'ing "test.py"
[py] Unhandled sentence='$PSMDCF,A*62'
[py] Version requested (sentence='$PSMDVER*4B')

The lines marked [py] are coming from my Python code, currently a simple

import sys
import logging

logger: logging.Logger = logging.getLogger()

while True:
    for sentence in map(str.rstrip, sys.stdin):
        if sentence.startswith("$PSMDVER"):
            logger.error(f"[py] Version requested ({sentence=})")
        else:
            logger.error(f"[py] Unhandled {sentence=}")

MPXConfig says there’s no MiniPlex at the other end, which is right. Next step is to hard-code the response to $PSMDVER, and that should at least get the program to start cleanly and fill in the various options in the UI.

NMEA checksums and line feeds

Various sentences need to have checksums applied. I went back to my previous NMEA work, and grabbed that checksum code. Then, using the diagnostics log, I started bashing at code to get my Python program to emit sentences with the right structure.

The diagnostics log looks like

\t:tx*42\$PSMDVER
\t:rx*44\\r:00723850,s:MX73-0*2A\$PSMDVER,3.16.0,MiniPlex-3Wi-N2K,3B020373,A01B*01

The NMEA standard for tag blocks indicates that they start and end with a back-slash. The two upper-case characters after a * are the checksum of the sentence or tag block, not including any back-slashes or asterisks. What I couldn’t tell for a while was whether the \t:rx*44\ was something my Python code had to send, or something that MPXConfig was writing locally. It turned out to be the latter, because including it in the tag block that the Python code sends would result in an incorrect checksum. Of course, had I read the fine manual a bit more carefully, the section on TAG blocks would have clued me in on the fact that the t:rx*44 bit was probably local.

However, even after solving this, MPXConfig would insist that the multiplexer was not responding. Thankfully, my old notes on experimenting with engine RPMs came to my rescue – NMEA standard says line endings are \r\n, but Python will default to \n on a Linux machine. Fixed that bit of code, and …

So now I have a working MiniPlex simulator that MPXConfig can communicate with over a serial port on Linux.