158 lines
5.1 KiB
C#
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);
|
|
}
|
|
}
|