RJCP.SerialPortStream.Virtual 3.0.1

dotnet add package RJCP.SerialPortStream.Virtual --version 3.0.1
NuGet\Install-Package RJCP.SerialPortStream.Virtual -Version 3.0.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RJCP.SerialPortStream.Virtual" Version="3.0.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RJCP.SerialPortStream.Virtual --version 3.0.1
#r "nuget: RJCP.SerialPortStream.Virtual, 3.0.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install RJCP.SerialPortStream.Virtual as a Cake Addin
#addin nuget:?package=RJCP.SerialPortStream.Virtual&version=3.0.1

// Install RJCP.SerialPortStream.Virtual as a Cake Tool
#tool nuget:?package=RJCP.SerialPortStream.Virtual&version=3.0.1

Serial Port Stream - Virtual Native Implementation

SerialPortStream v3.0 provides a constructor that can accept an object implementing the interface INativeSerial. For Windows, the normal implementation is WinNativeSerial, and for Linux (and Mac) it is UnixNativeSerial.

For the purposes of testing the serial port stream and protocols, one can use this package and inject the VirtualNativeSerial which doesn't talk to low level devices. Instead, user application code can use the VirtualNativeSerial to simulate a virtual serial port connection.

It's main purpose it to support unit testing and integration testing, that code can implement an class to implement a protocol, and talk to the application under test via the VirtualNativeSerial.

By not using global resources (it's a virtual implementation), unit tests can happily run in parallel (unlike when performing integration tests and relying on physical hardware, or drivers that must register the device names and are global for all applications, preventing parallel execution of test cases).

SerialPortStream.Virtual is its own NuGet Package

For complete separation of concerns, this is a separate package to the SerialPortStream. Import it in your packages only as you need it.

Implementation and Usage

It is expected that your code has a factory for creating an object of type SerialPortStream. This factory is important that production code would get an instance that can talk to real serial ports, where test cases would get an object that uses the VirtualNativeSerial instead.

Not a Full Serial Implementation

When implementing your protocol, you should design and abstract the reading and writing of data (at a byte level) from the protocol itself. This can make it easier later to convert your implementation to a stand-alone protocol simulator that can use real serial ports (or emulated drivers such as Com0Com).

The VirtualNativeSerial performs reads and writes atomically, where real serial ports operate only at individual bytes. When implementing your protocol, you should not assume you've received all data.

Example on a Factory Pattern

public interface ISerialPortFactory {
    SerialPortStream Create(string port);
}

public class SerialPortFactory : ISerialPortFactory {
    private static ISerialPortFactory _SerialPortFactory;

    public static ISerialPortFactory Instance {
        get {
            if (_SerialPortFactory == null) {
                _SerialPortFactory = new SerialPortFactory();
            }
            return _SerialPortFactory;
        }
        set {
            _SerialPortFactory = value;
        }
    }

    public SerialPortStream Create(string port) {
        return new SerialPortStream(port);
    }
}

The above code could be in your production software.

Then in your test case code you can assign it with a factory for your test cases:

public class VirtualSerialPortFactory : ISerialPortFactory {
    public Dictionary<string, VirtualNativeSerial> EndPoints = new Dictionary<string, VirtualNativeSerial>();

    public SerialPortStream Create(string port) {
        VirtualNativeSerial serial = new VirtualNativeSerial();;
        if (EndPoints.Contains(port)) {
            EndPoints[port] = serial;
        } else {
            EndPoints.Add(port, serial);
        }
        return new SerialPortStream(serial);
    }
}

And you can assign the factory, and use this to get the VirtualNativeSerial

SerialPortFactory.Instance = new VirtualSerialPortFactory()

Safe usage to interact with the SerialPortStream should be done with the VirtualNativeSerial.VirtualBuffer property.

Getting Data from the User that was Written

You can read the data the user has requested to send to the serial port via VirtualBuffer.ReadSentData(). It will copy the data that the user write with API like SerialPortStream.Write() into a buffer. You can use this buffer to know what the user wrote to implement a protocol.

The following properties are useful:

  • serial.VirtualBuffer.SentDataLength contains the number of bytes that in the buffer which your test driver can read.
  • serial.VirtualBuffer.ReadSendData(byte[], int, int) moves the data from the internal buffers to your own buffers, which you can use to implement your protocol.
  • serial.VirtualBuffer.WriteEvent to be notified when the user has written data in the buffers. Within this event, you can set flags (or WaitHandles) to know that data has arrived when implementing your read/write thread as a Task.

Responding and Sending the User Data

You can push data to the user that they can read it via the VirtualBuffer.WriteReceivedData(). It will append the buffers given (as bytes) to the byte buffer that the user can read with a call to SerialPortStream.Read().

The following properties are useful:

  • serial.VirtualBuffer.ReceivedDataFree to know how much space is available to write in the buffer.
  • serial.VirtualBuffer.WriteReceivedData(byte[], int, int) copies the data from the buffer to the serial port stream buffers that the user can read the data. The user can be notified of this data via the SerialPortStream.DataReceived event (slow), or they might have a continuous loop that just calls SerialPortStream.Read() which blocks until data arrives.
  • serial.VirtualBuffer.ReadEvent to be notified when the user has read (and so data is removed) from the serial port stream buffer. This can let your protocol implementation know that more data can be written.

Implementation Details

Assume that all methods and properties within the VirtualNativeSerial are private except for those exposed by the property VirtualBuffer.

Implementation may change over time. Only the VirtualBuffer (i.e. the interface IVirtualSerialBuffer) will try to be stable.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Framework net40 is compatible.  net403 was computed.  net45 was computed.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.1 286 3/9/2024
3.0.0 201 6/9/2023