SSPC-Tester/SSPCTester.Devices/Drivers/Real/Pci2312SafetyDio.cs

158 lines
5.1 KiB
C#

using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SSPCTester.Devices.Config;
using SSPCTester.Devices.Interfaces;
namespace SSPCTester.Devices.Drivers.Real;
public sealed class Pci2312SafetyDio : ISafetyDio, IDisposable
{
private readonly Pci2312Options _options;
private readonly ILogger<Pci2312SafetyDio> _logger;
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly byte[] _outputStates = new byte[16];
private IntPtr _device = InvalidHandle;
private bool _isConnected;
public Pci2312SafetyDio(IOptions<DeviceOptions> options, ILogger<Pci2312SafetyDio> logger)
{
_options = options.Value.Pci2312;
_logger = logger;
}
public bool IsConnected => _isConnected && _device != InvalidHandle && _device != IntPtr.Zero;
public string DisplayName => "PCI2312";
public event EventHandler<bool>? ConnectionChanged;
public async Task<bool> ConnectAsync(CancellationToken ct = default)
{
await DisconnectAsync().ConfigureAwait(false);
await _gate.WaitAsync(ct).ConfigureAwait(false);
try
{
IntPtr device = Native.PCI2312_CreateDevice(_options.DeviceId);
if (device == InvalidHandle || device == IntPtr.Zero)
throw new InvalidOperationException($"PCI2312 CreateDevice failed, DeviceId={_options.DeviceId}.");
_device = device;
SetConnected(true);
_logger.LogInformation("PCI2312 connected. DeviceId={DeviceId}", _options.DeviceId);
return true;
}
catch (DllNotFoundException ex)
{
SetConnected(false);
throw new InvalidOperationException("PCI2312 driver DLL was not found. Ensure PCI2312_64.dll is next to the application or in PATH.", ex);
}
catch (BadImageFormatException ex)
{
SetConnected(false);
throw new InvalidOperationException("PCI2312 driver DLL bitness does not match the application process.", ex);
}
finally
{
_gate.Release();
}
}
public async Task DisconnectAsync()
{
await _gate.WaitAsync().ConfigureAwait(false);
try
{
if (_device != InvalidHandle && _device != IntPtr.Zero)
{
try { Native.PCI2312_ReleaseDevice(_device); }
finally { _device = InvalidHandle; }
}
SetConnected(false);
}
finally
{
_gate.Release();
}
}
public async Task<bool> ReadInputAsync(int channel, CancellationToken ct = default)
{
ValidateChannel(channel);
await _gate.WaitAsync(ct).ConfigureAwait(false);
try
{
var states = new byte[16];
int ok = Native.PCI2312_GetDeviceDI(RequireDevice(), states);
if (ok <= 0)
throw new InvalidOperationException("PCI2312_GetDeviceDI failed.");
return states[channel] != 0;
}
finally
{
_gate.Release();
}
}
public async Task WriteOutputAsync(int channel, bool on, CancellationToken ct = default)
{
ValidateChannel(channel);
await _gate.WaitAsync(ct).ConfigureAwait(false);
try
{
_outputStates[channel] = on ? (byte)1 : (byte)0;
int ok = Native.PCI2312_SetDeviceDO(RequireDevice(), _outputStates);
if (ok <= 0)
throw new InvalidOperationException("PCI2312_SetDeviceDO failed.");
}
finally
{
_gate.Release();
}
}
public void Dispose()
{
try { DisconnectAsync().GetAwaiter().GetResult(); }
catch { }
_gate.Dispose();
}
private IntPtr RequireDevice()
{
if (!IsConnected)
throw new InvalidOperationException("PCI2312 is not connected.");
return _device;
}
private void SetConnected(bool connected)
{
if (_isConnected == connected) return;
_isConnected = connected;
ConnectionChanged?.Invoke(this, connected);
}
private static void ValidateChannel(int channel)
{
if (channel is < 0 or > 15)
throw new ArgumentOutOfRangeException(nameof(channel), channel, "PCI2312 channel must be 0-15.");
}
private static readonly IntPtr InvalidHandle = new(-1);
private static class Native
{
[DllImport("PCI2312_64", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr PCI2312_CreateDevice(int deviceId);
[DllImport("PCI2312_64", CallingConvention = CallingConvention.StdCall)]
public static extern int PCI2312_ReleaseDevice(IntPtr device);
[DllImport("PCI2312_64", CallingConvention = CallingConvention.StdCall)]
public static extern int PCI2312_SetDeviceDO(IntPtr device, byte[] outputStates);
[DllImport("PCI2312_64", CallingConvention = CallingConvention.StdCall)]
public static extern int PCI2312_GetDeviceDI(IntPtr device, byte[] inputStates);
}
}