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 _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 options, ILogger logger) { _options = options.Value.Pci2312; _logger = logger; } public bool IsConnected => _isConnected && _device != InvalidHandle && _device != IntPtr.Zero; public string DisplayName => "PCI2312"; public event EventHandler? ConnectionChanged; public async Task 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 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); } }