I have the following requirements:
- Write a GUI app for automated testing of some custom PCB hardware.
- The system must be usable for a technician with little to no advanced PC skills.
- The test setup has 4 different serial port interfaces:
- DUT (Device Under Test) USB-C port. (Used for debug and sending test commands)
- DUT RS232 port. (used for communication to PLC in final system.)
- Testjig RX USB-C port. (Port on identical hardware to the DUT, used to test RF network.)
- Testjig USB port. (Connected to STM32 Nucleo dev board, used to measure various voltages.)
- The tests are frequently of the form: send a command to device A, wait for answer from device B, check if answer is correct.
My issue is that there is an existing codebase which does not work reliably. Less experienced me struggled to get the
System.IO.Ports serial port class to work, so it has all sorts of weird attempts at workarounds and generally just ugly spaghetti code, blocking the event loop with
Thread.Sleep() calls, etc.
I found later that it wasn't just me who struggled with Microsoft's serial port class, so I switched to the SerialPortStream library. However, the codebase is currently extremely brittle and I absolutely hate to have to change anything in it. I want to use the opportunity to try to structure the code better and clean up as much as possible.
My current idea is to try to create a kind of "event driven" architecture in which each serial port listen for data which would denote a full message in its
DataReceived event handler, and then enqueue a full message in its queue automatically. It should also
Invoke a delegate to pop messages from the queue each time it appends a message. This function should parse each message and check for messages it recognizes.
The system will become a giant event-driven state machine which has nested state machines for each event source (user, DUT USB, DUT RS232, testjig RX, testjig Nucleo) as I see it.
My question: is this the best way to go about it? Are there easier/better options available? And if I go with this giant state machine, how should I protect my different threads spawned by different event handlers from data races when they have to read/update the shared state?