I am using Win32 API (Overlapped IO) for serial port communication. I have used PInvoke to call the Win32 API from my C# code.
In my previous implementation, the receiver thread reads data by using Polling mechanism (Infinite while loop reads RX buffer periodically). Now, I am trying to use Event-Driven approach to read the data (Using WaitCommEvent API Call). We expect the new implementation will reduce the CPU usage and Speed-Up the communication.
But, After changing to Event-Driven approach, the communication is actually slowed down, taking twice the time to read when compared to Polling mechanism.
Can anyone suggest which one of these is the best approach improve the communication speed and reduce the CPU usage?
Below is my code snippet:
private void ReadDataForWindows()
{
var data = new byte[255];
// Specify a set of events to be monitored for the port.
if (SetCommMask(this.Handle, EV_DEFAULT))
{
while (this.Handle != (IntPtr)INVALID_HANDLE_VALUE)
{
uint eventMask = 0;
// Wait for an event to occur for the port.
eventMask = this.WaitForEvent();
if (eventMask == EV_RXCHAR)
{
this.ReadFromRXBuffer(data);
}
else if (eventMask == EV_ERR)
{
uint errors;
ClearCommError(this.Handle, out errors, IntPtr.Zero);
}
// Re-specify the set of events to be monitored for the port.
if (!SetCommMask(this.Handle, EV_DEFAULT))
{
break;
}
}
}
}
private void ReadFromRXBuffer(byte[] data)
{
uint dwBytesTransferred;
// Loop for waiting for the data.
do
{
// Read the data from the serial port.
this.Read(data, 255, out dwBytesTransferred);
// Display the data read.
if (dwBytesTransferred > 0)
{
if (this.DataReceived != null)
{
var dataReceived = new byte[dwBytesTransferred];
for (var i = 0; i < dwBytesTransferred; i++)
{
dataReceived[i] = data[i];
}
this.DataReceived(this, new ByteDataReceivedEventArgs(dataReceived, Convert.ToInt32(dwBytesTransferred)));
}
}
} while (dwBytesTransferred > 0);
}
private uint WaitForEvent()
{
uint eventMask = 0;
IntPtr evPtr = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask));
Marshal.WriteInt32(evPtr, 0);
if (WaitCommEvent(this.Handle, evPtr, this.RxOvr.MemPtr) == false)
{
int error = Marshal.GetLastWin32Error();
// Operation is executing in the background
if (error == ERROR_IO_PENDING)
{
this.ReceiveSignal.WaitOne();
}
else
{
this.Fault = "WaitCommEvent() Failed. System Returned Error Code: " +
error.ToString();
return 0;
}
}
eventMask = (uint)Marshal.ReadInt32(evPtr);
return eventMask;
}
Related
My app is built on C# synchronous sockets, and I am trying to replace them with asynchronous ones, in order to improve efficiency (if possible). So far, I have replaced on the server the Send() and Accept() with the asynchronous ones (based on the MSDN documents), but I have some issues to implement the Receive().
Original (synchronous):
Socket _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
bool _flag = true;
CheckFun()
{
while (_flag)
{
Thread.Sleep(10); // every ~10ms check
byte[] _buffer;
if (_socket.Available != 0)
{
_buffer = new byte[1]; // just check 1st byte
_socket.Receive(_buffer, 1, SocketFlags.None);
if (_buffer[0] == 1)
{
ReceiveFun();
}
}
}
}
ReceiveFun()
{
int hdrSize = 12;
byte[] buffer = new byte[hdrSize];
_socket.Receive(buffer, hdrSize, SocketFlags.None);
int _dataLength = buffer[0]; // the 1st byte has already been removed before calling the ReceiveFun()
buffer = new byte[_dataLength];
int _bytesRead = 0;
while (_bytesRead != _dataLength)
{
while (_socket.Available == 0)
{
if (!_socket.Connected)
return 0;
}
_bytesRead += _socket.Receive(buffer, _bytesRead, _dataLength - _bytesRead, SocketFlags.None);
}
//read bytes blah blah...
}
My question is how to turn this operation into an asynchronous one and concatenate the bytes received until I receive all information? And then again wait for the next one?
EDIT
Async
public class State
{
public int nToReadBytes = 0;
public int nBytesRead = 0;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize]; // Receive buffer.
}
List<byte> lReceivedBytes = new List<byte>();
int hdrSize = 12;
public void ReadCallback(IAsyncResult ar)
{
// Retrieve the state object and the handler socket
// from the asynchronous state object.
var state = ar.AsyncState as State;
// Read data from the client socket.
int availableBytes = oSocket.EndReceive(ar);
if (availableBytes > 0)
{
if (lReceivedBytes.Count == 0)
{
if (state.buffer[0] == 1)
{
// the first field of the header has been successfully decoded
if (availableBytes > 1)
{
state.nToReadBytes = BitConverter.ToInt32(state.buffer, 1) + hdrSize;
int _bytesCopy = Math.Min(state.nToReadBytes, state.buffer.Length); //in case that the data is less than the State.BufferSize (unlikely)
state.nBytesRead += _bytesCopy;
lReceivedBytes.AddRange(state.buffer);
}
}
else if (state.buffer[0] == 2)
{
// the first field of the header has been successfully decoded but do nothing!
_socket.BeginReceive(state.buffer, 0, State.BufferSize, 0, new AsyncCallback(ReadCallback), state);
return;
}
else
throw new InvalidDataException("Invalid hdr field [1-2]: " + state.buffer[0]);
}
else
{
state.nBytesRead += state.buffer.Length;
lReceivedBytes.AddRange(state.buffer);
}
if (lReceivedBytes.Count == state.nToReadBytes)
{
//read all information and clear list and States in the end (?)
// ...
lReceivedBytes.Clear();
state.nToReadBytes = 0;
state.nBytesRead = 0;
_socket.BeginReceive(state.buffer, 0, State.BufferSize, 0, new AsyncCallback(ReadCallback), state);
}
else
{
//int _newSize = Math.Min(state.nToReadBytes - state.nBytesRead, State.BufferSize); // for restriction (?)
int _newSize = state.nToReadBytes - state.nBytesRead; // for now don't check
_socket.BeginReceive(state.buffer, 0, _newSize, 0, new AsyncCallback(ReadCallback), state); //shall I increase the size (it could be between 90 kB - 170kB, until all info is received)
}
}
else
_socket.BeginReceive(state.buffer, 0, State.BufferSize, 0, new AsyncCallback(ReadCallback), state);
}
The Socket class has two main asynchronous paradigms: the original callback-based Asynchronous Programming Model (APM) and the only-slightly-newed Event-based Asynchronous Pattern (EAP). Both of these can be somewhat unwieldy to implement as compared to the synchronous appproach, as they require you to adjust your thinking to connection state instead of just local method logic, breaking up what would be a single method into parts dealing with the initiation and completion of the operation.
Fortunately, the newer Task Parallel Library model leverages async and await to allow asynchronous code to be written in almost identical fashion to the equivalent synchronous version. For the Socket class, to take advantage of this, you need to wrap its asynchronous API in a TPL-compatible way. .NET does provide a general-purpose way to take existing APM APIs and wrap them in tasks (see https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/tpl-and-traditional-async-programming), but IMHO it is simpler to take advantage of the NetworkStream class, which wraps a socket in a Stream object.
Since Stream already has received the "TPL love", i.e. has ReceiveAsync() and similar methods to allow TPL-based operations, I find this somewhat easier than dealing with the wrapper methods designed to map APM to TPL.
In your code, that would wind up looking something like this:
// Somewhere appropriate -- your code example isn't specific or complete enough
// to offer anything more detailed than this
NetworkStream _stream = new NetworkStream(_socket);
async Task ReceiveFun()
{
int _bytesRead = 0, hdrSize = 12;
byte[] buffer = new byte[hdrSize];
while (_bytesRead < hdrSize)
{
int bytesRead = await _stream.ReadAsync(buffer, _bytesRead, hdrSize - _bytesRead);
if (bytesRead == 0) throw new InvalidDataException("unexpected end-of-stream");
_bytesRead += bytesRead;
}
int _dataLength = buffer[0]; // the 1st byte has already been removed before calling the ReceiveFun()
buffer = new byte[_dataLength];
_bytesRead = 0;
while (_bytesRead < _dataLength)
{
int bytesRead = await _stream.ReadAsync(buffer, _bytesRead, _dataLength - _bytesRead);
if (bytesRead == 0) throw new InvalidDataException("unexpected end-of-stream");
_bytesRead += bytesRead;
}
//read bytes blah blah...
}
Note that your original synchronous code had a bug: you were not using the returned byte count when reading the header, and so had no way to know if the full header was actually being returned in a single call or not. You must always look at the count of bytes read; this can always be as few as 1 when data is available, and of course will be 0 when the end of stream is reached (i.e. the remote endpoint uses the shutdown operation).
Your code also did not check for end-of-stream correctly. Just look at the bytes returned when you try to read from the socket. I've fixed that in the above as well.
I am making an Application in C# which reads CAN (Control Area Network) messages and sends them too. I need to do it in 10 milliseconds. The OS i am using is Windows Embedded 7 Pro.
public void ID0008Update10ms(DataTable supportPoints, int a)
{
System.TimeSpan timer10ms = System.TimeSpan.FromMilliseconds(10);
intialiseCAN();
while (a==1)
{
Stopwatch t = Stopwatch.StartNew();
sendCAN();
getCAN();
ID0006NavComStatus(supportPoints);
string state = Convert.ToString(gNavStatus);
while (t.Elapsed < timer10ms)
{ /*nothing*/}
}
}
The issue is the sendCAN() and reciveCAN() dynamically load the .dll file
public int sendCAN(ref can_msg msg, IntPtr pDll)
{
if (pDll == IntPtr.Zero)
{
MessageBox.Show("Loading Failed");
}
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "CAN_Transmission");
CAN_Transmission sendCAN = (CAN_Transmission)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(CAN_Transmission));
int result = sendCAN( ref msg);
return result;
}
This makes the cycle slow, I am not able to send the Message within 10ms. Can anyone propose a better way. Please remember. I use Windows Embedded.
You should move as much as possible outside the loop. If it doesn't absolutely have to be there, move it. Along the lines of....
private CAN_TransmissionMethod CAN_Transmission;
//Cache the delegate outside the loop.
private bool InitialiseCAN2(IntPtr pDll)
{
if (pDll == IntPtr.Zero)
{
Log("Loading Failed");
return false;
}
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "CAN_Transmission");
CAN_Transmission = (CAN_TransmissionMethod)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(CAN_Transmission));
return true;
}
public int sendCAN(ref can_msg msg)
{
if (CAN_Transmission == null)
return -1;//Can't send, no delegate, Log, Fail, Explode... make a cup of tea.
int result = CAN_Transmission( ref msg);
return result;
}
public void ID0008Update10ms(DataTable supportPoints, int a)
{
System.TimeSpan timer10ms = System.TimeSpan.FromMilliseconds(10);
intialiseCAN();
initialiseCAN2(pDll)
while (a==1)
{
Stopwatch t = Stopwatch.StartNew();
sendCAN(ref thereIsSupposedToBeAMessageHere);
getCAN(ref probablySupposedToBeSomethingHereToo);
ID0006NavComStatus(supportPoints);
string state = Convert.ToString(gNavStatus);
while (t.Elapsed < timer10ms)
{ /*nothing*/}
}
}
I'm using UDP sockets to communicate a C++ application with a C# application.
In the C# -> C++ direction everything seems to be working fine, but the other way around is the one that's driving me nuts.
Communication does work, but messages are getting way late (like 2 secs delay) in the C# app, even though they're being sent every frame (it's a 3D app), and the receiving code is executing every 10 ms.
I need real time so this is a very painful problem. Do you think this might be related to packet losses? Then why don't they get lost in the other direction?
EDIT:
C# app code for syncing data:
public void RecibeDatos()
{
if (MessageReceived && U != null && S != null)
{
MessageReceived = false;
//Console.WriteLine("listening for messages");
U.BeginReceive(ReceiveCallback, S);
}
}
public void ReceiveCallback(IAsyncResult ar)
{
UdpClient u = ((UdpState)(ar.AsyncState)).U;
IPEndPoint e = ((UdpState)(ar.AsyncState)).E;
receivedBytes = u.EndReceive(ar, ref e);
//int currentProtocol = (int) numero;
//ResetSignal = reset > 0;
//Console.WriteLine("Received: " + currentProtocol);
MessageReceived = true;
}
C++ Code for sending data:
float indiceFloat[1];
indiceFloat[0] = indice_protocolo_actual;
sender->setBuffer((void *)indiceFloat, sizeof(indiceFloat));
sender->sync();
sync method on J_Enviar (sender) class:
void J_Enviar::sync( void )
{
if(!_initialized) init();
if( _buffer == 0L )
{
fprintf( stderr, "Broadcaster::sync() - No buffer\n" );
return;
}
#if defined (WIN32) && !defined(__CYGWIN__)
unsigned int size = sizeof( SOCKADDR_IN );
sendto( _so, (const char *)_buffer, _buffer_size, 0, (struct sockaddr *)&saddr, size );
int err = WSAGetLastError ();
if (err!=0)
fprintf( stderr, "Broadcaster::sync() - error %d\n",err );
#else
unsigned int size = sizeof( struct sockaddr_in );
sendto( _so, (const void *)_buffer, _buffer_size, 0, (struct sockaddr *)&saddr, size );
#endif
}
Providing full SocketManager code for Receiving C# end:
using System;
using System.Net;
using System.Net.Sockets;
namespace WpfApplication1
{
public class SocketManager
{
private static SocketManager _instance = null;
static readonly object Padlock = new object();
private IPEndPoint E;
private UdpClient U;
private UdpState S;
private byte[] receivedBytes;
public static bool MessageReceived = true;
private SocketManager()
{
}
public byte[] ReceivedBytes
{
get { return receivedBytes; }
}
public static SocketManager Instance
{
get
{
lock(Padlock)
{
return _instance ?? (_instance = new SocketManager());
}
}
}
public void CreateReceivingSocket(IPAddress a, int puerto)
{
if(E==null || (E.Address != a && E.Port != puerto))
{
E = new IPEndPoint(a, puerto);
U = new UdpClient(puerto);
S = new UdpState { E = E, U = U };
}
}
public void ReceiveCallback(IAsyncResult ar)
{
UdpClient u = ((UdpState)(ar.AsyncState)).U;
IPEndPoint e = ((UdpState)(ar.AsyncState)).E;
receivedBytes = u.EndReceive(ar, ref e);
//int currentProtocol = (int) numero;
//ResetSignal = reset > 0;
//Console.WriteLine("Received: " + currentProtocol);
MessageReceived = true;
}
public void RecibeDatos()
{
if (MessageReceived && U != null && S != null)
{
MessageReceived = false;
//Console.WriteLine("listening for messages");
U.BeginReceive(ReceiveCallback, S);
}
}
public void CloseConnection()
{
if (E != null)
{
E.Port = 5502;
E = null;
}
if (U != null)
U.Close();
}
}
public class UdpState
{
public IPEndPoint E;
public UdpClient U;
}
}
And this is my dispatchertimerclick which makes the program receive each 10 ms:
_dispatcherTimer.Tick += DispatcherTimerTick;
_dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
_dispatcherTimer.Start();
private void DispatcherTimerTick(object sender, EventArgs e)
{
_exp1Class.sendData();
_sm.RecibeDatos();
byte[] recibidos = _sm.ReceivedBytes;
if (recibidos != null)
{
float numero = BitConverter.ToSingle(recibidos, 0);
_exp1Class.CurrentProtocol = (int) numero;
}
}
I don't see when you kick off your first BeginReceive. (Ah, it is done from your first timer tick I think?) It should be initaited as soon as you are ready to receive data. Secondly, your ReceiveCallback should take the received data and place it into a queue of some sort and immediately call BeginReceive again. Otherwise you are holding up thge arrival of the next data frame until the prior was consumed. Finally, watch for threading issues, as the Threading timer and the UDP callback each will run on seperate threads from your application main thread.
The only reason your code works at all is because you pre-initialized MessageReceived = true even before you receive any callbacks. When the first tick happens the call to RecibeDatos invokes BeginReceive because that bool was set to true.
Think of BeginReceive as saying "call me back when you have some data from the network". You don't need to poll the network using your timer. (You can choose to consume that data via your timer if your application requires that, but lets leave that aside for the moment).
Here are the rough steps:
First, upon start (or enable, run, etc) you should call BeginReceive. You will now receive a notification when data arrives.
Secondly, when the callback happens you complete that read of the data bytes using EndReceive. That data would typically be buffered or otherwise dispatched. You should then call BeginReceive again (within the callback) so that upon the next set of data being available you will again get notified. It becomes a kind of async loop.
The question is what to do with the data you are reading. You might consider placing the data into a queue, and then having your Timer pop these data frames off of the queue for processing. Be aware that the data can arrive multiple times between your Ticks.
I have a C# client/server network program I've written using
TCPListener and TCPClient classes. The server is reading everything
from the client (small amounts of xml) just fine until I try to send a
large file (100k) back to the client.
I'm using stream functions for
both client and server with non-blocking socket functions. When I do a
socket.SendFile("filename") back to the client, the file is getting
cut off - I've set the receive buffer size on the client to well past
100k but it still gets cut off around 25k and the communication
between client and server is unreliable afterwords.
My basic question
is what happens if data is somehow left in the pipe ? i.e.. will it be
read by the next socket.Read... Does every Send call require exactly
one and only one Read ? Maybe I'm not giving the client enough time to
read the file but their both on the same machine and I've tried
sleeping for a few seconds in various places w/o success.
It is very possible that you cannot read the entire message through one Read call (perhaps all data has not arrived yet). In network programming, you would often place the call to Read in a while loop and simply Read() until you have received the entire expected message.
1 Send call might take more than one Read call to receive, and 1 Read call might read the data send by several Send call.
TCP just provides a stream, so it's up to you to define how the data or messages you send are partitioned.
In this case, you probably just need to loop ,doing Read until the stream is closed.
You probably want something like this:
socket.Blocking = false;
const int ChunkSize = 1492;
const int ReceiveTimeout = 10000;
const int SendTimeout = 10000;
public void Send(Byte[] data)
{
var sizeBytes = BitConverter.GetBytes(data.Length);
SendInternal(sizeBytes);
SendInternal(data);
}
public Byte[] Receive()
{
var sizeBytes = ReceiveInternal(4);
var size = BitConverter.ToInt32(sizeBytes, 0);
var data = ReceiveInternal(size);
return data;
}
private void SendInternal(Byte[] data)
{
var error = SocketError.Success;
var lastUpdate = Environment.TickCount;
var size = data.Length;
var count = 0;
var sent = 0;
while (sent < size)
{
count = Math.Min(ChunkSize, size - sent);
count = socket.Send(data, sent, count, SocketFlags.None, out error);
if (count > 0)
{
sent += count;
lastUpdate = Environment.TickCount;
}
if (error != SocketError.InProgress && error != SocketError.Success && error != SocketError.WouldBlock)
throw new SocketException((Int32)error);
if (Environment.TickCount - lastUpdate > SendTimeout)
throw new TimeoutException("Send operation timed out.");
if (count == 0 && !socket.Poll(100, SelectMode.SelectWrite))
throw new SocketException((Int32)SocketError.Shutdown);
}
}
private Byte[] ReceiveInternal(Int32 size)
{
var error = SocketError.Success;
var lastUpdate = Environment.TickCount;
var buffer = new Byte[ChunkSize];
var count = 0;
var received = 0;
using (var ms = new MemoryStream(size))
{
while (received < size)
{
count = Math.Min(ChunkSize, size - received);
count = socket.Receive(buffer, 0, count, SocketFlags.None, out error);
if (count > 0)
{
ms.Write(buffer, 0, count);
received += count;
lastUpdate = Environment.TickCount;
}
if (error != SocketError.InProgress && error != SocketError.Success && error != SocketError.WouldBlock)
throw new SocketException((Int32)error);
if (Environment.TickCount - lastUpdate > ReceiveTimeout)
throw new TimeoutException("Receive operation timed out.");
if (count == 0 && socket.Poll(100, SelectMode.SelectRead) && socket.Available == 0)
throw new SocketException((Int32)SocketError.Shutdown);
}
return ms.ToArray();
}
}
What I would usually do is create a header structure that is sent
Header Size (int, 4 bytes)
File Name Offset (int, 4 bytes)
File Name Size (int , 4 bytes)
File Data Offset (int, 4 bytes)
File Data Size (int , 4 bytes)
[ message data here]
and then that header is read using either a BinaryReader or copying the bytes to a struct using Marshal. This way you always know what data is arriving and how many times you need to call Read().
The header size field is also helps with versioning the protocol (keep the structure the same but add to it for later clients so you can keep backwards compatibility). If you define the structure in C# be sure to do it like so:
[StructLayout LayoutKind.Sequential]
struct MessageHeader
{
public int HeaderSize;
public int FileNameOffset;
public int FileNameSize;
public int FileDataOffset;
public int FileDataSize;
}
Then Marshal.PtrToStructure will allow you do create an instance of this struct right from the byte[] you read from the socket
Try sending the chunk from the server side in chunks. Just as the other said, posting the code would be of great help to us.
I'm trying to figure out a method of connecting from C# code to a digital scale. The particular scale is an Ohaus SP202 digital scale which comes with a USB connection. I would like to read the weight measured on the scale programmatically. I don't have the scale yet, I'm just doing the research before hand.
Has anyone done this before? I've been doing research on the internet and didn't find anything worth mentioning yet.
USB Hardware communication popularly works one of three ways.
Proprietary software talks to hardware via proprietary driver.
Devices have a Serial emulation chip (e.g. FTDI) When you plug in the scale you just need to install the Virtual Comm Port drivers and the device shows up as a comm port on your system. Then it is as easy as using System.IO.Ports.SerialPort to talk to the device.
The device implements a HID profile and will be available through your OS's HID system. I've used this .NET HID library on Windows to successfully talk with barcode scanners that implement HID. The HID library will send data to you from the hardware in chunks that you decipher based on the hardware you are talking to.
With method 2 and 3 you will just need to find the data format for the scale that you are talking to. The scales that I have used send updates every second or so with the weight and other information shown on the hardware UI, such as if the load has stabilized or not.
Looking at their discussion forums it looks like their scales use method 2 (http://ohaus.com/support/forum_messages.asp?topicid=584) and that you need to Poll the scale by sending a "P\r\n" it will then respond with the characters that are shown on the display (http://ohaus.com/support/forum_messages.asp?topicid=802).
I haven't used that specific scale, but I have connected to other digital scales previously. Basically it is usually just doing serial communications via the USB to Com converter.
If the scale has an API for this all the better, but if not then you will just be using System.IO.Ports.SerialPort which is pretty standard serial programming. A starter article here
I don't know any details on this scale, but I've done some USB stuff.
Its most likely using usb interrupt to transfer the data. All usb mice also use interrupt, so if you can figure out how to read the mouse signal (with using an HID api), then it should be exactly the same as the scale, except the data format returned would be completely different.
run on ftw
Scales was one of the first devices I ever had to read data from. At that time, it was just a serial port and I'm guessing that you'd still be able to read from the serial port with SerialPort.Net.
Here's the code for Serial Port transfer on Windows
Reference
using Device.Net;
using Device.Net.Windows;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SerialPort.Net.Windows
{
public class WindowsSerialPortDevice : DeviceBase, IDevice
{
#region Fields
private readonly int _BaudRate;
private readonly byte _ByteSize;
private bool disposed;
private readonly Parity _Parity;
private SafeFileHandle _ReadSafeFileHandle;
private readonly StopBits _StopBits;
private ushort ReadBufferSize { get; }
#endregion
#region Public Properties
public bool IsInitialized => _ReadSafeFileHandle != null && !_ReadSafeFileHandle.IsInvalid;
/// <summary>
/// TODO: No need to implement this. The property probably shouldn't exist at the base level
/// </summary>
public IApiService ApiService { get; }
public ConnectedDeviceDefinition ConnectedDeviceDefinition { get; private set; }
#endregion
#region Constructor
public WindowsSerialPortDevice(
string deviceId,
int baudRate = 9600,
StopBits stopBits = StopBits.One,
Parity parity = Parity.None,
byte byteSize = 8,
ushort readBufferSize = 1024,
ILoggerFactory loggerFactory = null,
IApiService apiService = null) : base(
deviceId,
loggerFactory,
(loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WindowsSerialPortDevice>())
{
ApiService = apiService ?? new ApiService(null);
ConnectedDeviceDefinition = new ConnectedDeviceDefinition(DeviceId, DeviceType.SerialPort);
if ((byteSize == 5 && stopBits == StopBits.Two) || (stopBits == StopBits.OnePointFive && byteSize > 5))
throw new ArgumentException(Messages.ErrorInvalidByteSizeAndStopBitsCombo);
if (byteSize is < 5 or > 8)
throw new ArgumentOutOfRangeException(nameof(byteSize), Messages.ErrorByteSizeMustBeFiveToEight);
if (baudRate is < 110 or > 256000)
throw new ArgumentOutOfRangeException(nameof(baudRate), Messages.ErrorBaudRateInvalid);
if (stopBits == StopBits.None)
throw new ArgumentException(Messages.ErrorMessageStopBitsMustBeSpecified, nameof(stopBits));
ReadBufferSize = readBufferSize;
_BaudRate = baudRate;
_ByteSize = byteSize;
_StopBits = stopBits;
_Parity = parity;
}
#endregion
#region Public Methods
public Task InitializeAsync(CancellationToken cancellationToken = default) => Task.Run(Initialize, cancellationToken);
private uint Write(byte[] data) => data == null ? 0 : ApiService.AWriteFile(_ReadSafeFileHandle, data, data.Length, out var bytesWritten, 0) ? (uint)bytesWritten : 0;
public override Task<uint> WriteAsync(byte[] data, CancellationToken cancellationToken = default)
{
ValidateConnection();
return Task.Run(() =>
{
var bytesWritten = Write(data);
Logger.LogDataTransfer(new Trace(false, data));
return bytesWritten;
}, cancellationToken);
}
public override Task<TransferResult> ReadAsync(CancellationToken cancellationToken = default)
{
ValidateConnection();
return Task.Run(() =>
{
var buffer = new byte[ReadBufferSize];
var bytesRead = Read(buffer);
var transferResult = new TransferResult(buffer, bytesRead);
Logger.LogDataTransfer(new Trace(false, transferResult));
return transferResult;
}, cancellationToken);
}
public override Task Flush(CancellationToken cancellationToken = default)
{
ValidateConnection();
return Task.Run(() => ApiService.APurgeComm(_ReadSafeFileHandle, APICalls.PURGE_RXCLEAR | APICalls.PURGE_TXCLEAR),
cancellationToken);
}
public override void Dispose()
{
if (disposed)
{
Logger.LogWarning(Messages.WarningMessageAlreadyDisposed, DeviceId);
return;
}
disposed = true;
Logger.LogInformation(Messages.InformationMessageDisposingDevice, DeviceId);
if (_ReadSafeFileHandle != null)
{
_ReadSafeFileHandle.Dispose();
_ReadSafeFileHandle = new SafeFileHandle((IntPtr)0, true);
}
base.Dispose();
}
public void Close() => Dispose();
#endregion
#region Private Methods
private void Initialize()
{
_ReadSafeFileHandle = ApiService.CreateReadConnection(DeviceId, FileAccessRights.GenericRead | FileAccessRights.GenericWrite);
if (_ReadSafeFileHandle.IsInvalid) return;
var dcb = new Dcb();
var isSuccess = ApiService.AGetCommState(_ReadSafeFileHandle, ref dcb);
_ = WindowsHelpers.HandleError(isSuccess, Messages.ErrorCouldNotGetCommState, Logger);
dcb.ByteSize = _ByteSize;
dcb.fDtrControl = 1;
dcb.BaudRate = (uint)_BaudRate;
dcb.fBinary = 1;
dcb.fTXContinueOnXoff = 0;
dcb.fAbortOnError = 0;
dcb.fParity = 1;
#pragma warning disable IDE0010 // Add missing cases
dcb.Parity = _Parity switch
{
Parity.Even => 2,
Parity.Mark => 3,
Parity.Odd => 1,
Parity.Space => 4,
Parity.None => 0,
_ => 0
};
dcb.StopBits = _StopBits switch
{
StopBits.One => 0,
StopBits.OnePointFive => 1,
StopBits.Two => 2,
StopBits.None => throw new ArgumentException(Messages.ErrorMessageStopBitsMustBeSpecified),
_ => throw new ArgumentException(Messages.ErrorMessageStopBitsMustBeSpecified),
};
#pragma warning restore IDE0010 // Add missing cases
isSuccess = ApiService.ASetCommState(_ReadSafeFileHandle, ref dcb);
_ = WindowsHelpers.HandleError(isSuccess, Messages.ErrorCouldNotSetCommState, Logger);
var timeouts = new CommTimeouts
{
WriteTotalTimeoutConstant = 0,
ReadIntervalTimeout = 1,
WriteTotalTimeoutMultiplier = 0,
ReadTotalTimeoutMultiplier = 0,
ReadTotalTimeoutConstant = 0
};
isSuccess = ApiService.ASetCommTimeouts(_ReadSafeFileHandle, ref timeouts);
_ = WindowsHelpers.HandleError(isSuccess, Messages.ErrorCouldNotSetCommTimeout, Logger);
Logger.LogInformation("Serial Port device initialized successfully. Port: {port}", DeviceId);
}
private uint Read(byte[] data)
=>
ApiService.AReadFile(_ReadSafeFileHandle, data, data.Length, out var bytesRead, 0)
? bytesRead
: throw new IOException(Messages.ErrorMessageRead);
private void ValidateConnection()
{
if (!IsInitialized)
{
throw new InvalidOperationException(Messages.ErrorMessageNotInitialized);
}
}
#endregion
}
}