So, I could have done a simple comport.ReadLine() with a while loop and walked away. As I enjoy over-engineering something that could be done in 5 lines of code, I did it in 50. But why, why god why did I do it in 40 lines? To make it thread safe and event driven!
So lets look at the code.
private string _fwVersion;
private readonly Mutex mutex = new Mutex();
private AutoResetEvent firmwareAreState;
public string GetFirmwareVersion()
{
mutex.WaitOne();
comport.DataReceived += comport_DataReceivedFirmware;
_fwVersion = string.Empty;
firmwareAreState = new AutoResetEvent(false);
for (var i = 0; i < 5; i++)
{
if (!string.IsNullOrEmpty(_fwVersion))
break;
comport.Write(firmwareVersion);
firmwareAreState.WaitOne(500);
}
if (string.IsNullOrEmpty(_fwVersion))
_fwVersion = "ERROR";
comport.DataReceived -= comport_DataReceivedFirmware;
mutex.ReleaseMutex();
return _fwVersion;
}
private void comport_DataReceivedFirmware(object sender, SerialDataReceivedEventArgs e)
{
var returnVal = comport.ReadLine();
if (string.IsNullOrEmpty(returnVal) || returnVal == returnLine)
return;
_fwVersion = returnVal;
firmwareAreState.Set();
}
The SerialPort object has the DataReceived event which, in my opinion, is the proper way to handle this type of data transfer. If I had a while loop, I would either have to Thread.Sleep or just run at 100% until I get the response I want. Neither of those are appropriate. We have the tools, we can do this the proper* way.
Lets break down the primary method, GetFirmwareVersion. The key objects here are the AutoResetEvent and a Mutex which are all in .Net. This would be easier to explain from the inside out but I’m going to do outside in since people read from the top rather than a random point in the middle.
The Mutex will block the call from being executed before the prior call has finished. All other calls will be blocked and will wait until the mutex has been released with the ReleaseMutex method. Then the next queued call will continue execute. In Clint terms, only one thread can have access at only one time. This differs from a lock since I can jump functions.
With the AutoResetEvent, I can block the primary thread while I wait for the event to respond to work. At first, I had this as a WaitHandle and a ThreadPool but realized that this wasn’t needed. The AutoResetEvent object would then cause a deadlock without the timeout. Since the relay boards seem not to respond 100% of the time to very prompt calls, I had to be a bit more aggressive to get stuff to work so I added in the for loop and the timeout to verify my command was executed. Now this is different from a mutex as a mutex stops a new thread from entering to do work while an AutoResetEvent stops the current thread from doing work until it is cleared.
Well, how does one release the block? Well, in this instance, the DataReceived event from the SerialPort, I call the AutoResetEvent object’s Set method. I named mine firmwareAreState. As soon as I call Set, the primary thread is released and continues to do work. I don’t do pointless while loops and don’t do pointless thread sleeps. This is similar to a yield instead of a return.
The final key thing that most people don’t do is dynamically add and remove events for an object. If I fire off this function 5 times and don’t remove the events, I will run into race conditions and memory access issues I don’t want. I have my function for data access then I remove it as soon as I am done. On some objects, this may not be a big deal, however my serial port object is global in my object.
So why do this plainly overkill logic for getting the firmware? This was my test for actually opening the relays. The logic here will be mirrored but I do think I’ll make it a be more generic so if work does need to be done to tweak it, it is one spot updating rather than cutting and copying 50 spots. The wonders of delegates and refactoring!
Also, here is my test application UI for the relay boards. Right now, firmware only works.
* By “proper” way, I meant to say that I scratched my head, had a Dr. Pepper and came up with this solution so I could go out and play with my friends.