using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace Housing.Services; public sealed class Ni6501StartSignalWatcher { private readonly StartSignalSettings _settings; public Ni6501StartSignalWatcher(StartSignalSettings settings) { _settings = settings; } public async Task WaitForStartAsync(CancellationToken cancellationToken = default) { if (!_settings.Enabled) { return; } try { await Task.Run(() => WaitForStart(cancellationToken), cancellationToken); } catch (DllNotFoundException ex) { throw new InvalidOperationException("NI-DAQmx 드라이버(nicaiu.dll)를 찾을 수 없습니다. NI-DAQmx Runtime 설치와 NI-6501 인식 상태를 확인하세요.", ex); } catch (EntryPointNotFoundException ex) { throw new InvalidOperationException("NI-DAQmx DLL에서 필요한 함수가 보이지 않습니다. NI-DAQmx Runtime 버전을 확인하세요.", ex); } catch (BadImageFormatException ex) { throw new InvalidOperationException("NI-DAQmx DLL 비트 수가 현재 프로그램과 맞지 않습니다. x64/x86 Runtime과 실행 설정을 확인하세요.", ex); } } private void WaitForStart(CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(_settings.PhysicalChannel)) { throw new InvalidOperationException("Hardware.ini [StartSignal] PhysicalChannel 값을 설정하세요."); } using var input = new DaqmxDigitalInput(_settings.PhysicalChannel); var stopwatch = Stopwatch.StartNew(); if (_settings.RequireInactiveBeforeStart) { while (_settings.IsActive(input.ReadSingleLine())) { ThrowIfTimedOut(stopwatch, "기존 신호 해제 대기"); Delay(cancellationToken); } } while (!_settings.IsActive(input.ReadSingleLine())) { ThrowIfTimedOut(stopwatch, "시작 신호 대기"); Delay(cancellationToken); } } private void ThrowIfTimedOut(Stopwatch stopwatch, string state) { if (_settings.TimeoutMilliseconds <= 0 || stopwatch.ElapsedMilliseconds <= _settings.TimeoutMilliseconds) { return; } throw new TimeoutException( $"NI-6501 시작 신호 대기 시간 초과: {state}, 채널={_settings.PhysicalChannel}, 제한={_settings.TimeoutMilliseconds}ms"); } private void Delay(CancellationToken cancellationToken) { if (cancellationToken.WaitHandle.WaitOne(_settings.PollIntervalMilliseconds)) { throw new OperationCanceledException(cancellationToken); } } private sealed class DaqmxDigitalInput : IDisposable { private const int DaqmxValChanPerLine = 0; private const int DaqmxValGroupByChannel = 0; private nint _taskHandle; public DaqmxDigitalInput(string physicalChannel) { Check(DaqmxCreateTask("", out _taskHandle), "NI-DAQmx Task 생성 실패"); try { Check(DaqmxCreateDIChan(_taskHandle, physicalChannel, "", DaqmxValChanPerLine), "NI-6501 DI 채널 생성 실패"); Check(DaqmxStartTask(_taskHandle), "NI-6501 DI Task 시작 실패"); } catch { Dispose(); throw; } } public bool ReadSingleLine() { var readArray = new byte[1]; Check( DaqmxReadDigitalLines( _taskHandle, 1, 1.0, DaqmxValGroupByChannel, readArray, (uint)readArray.Length, out _, out _, nint.Zero), "NI-6501 DI 읽기 실패"); return readArray[0] != 0; } public void Dispose() { if (_taskHandle == nint.Zero) { return; } DaqmxStopTask(_taskHandle); DaqmxClearTask(_taskHandle); _taskHandle = nint.Zero; } private static void Check(int errorCode, string message) { if (errorCode >= 0) { return; } var detail = GetExtendedErrorInfo(); throw new InvalidOperationException(string.IsNullOrWhiteSpace(detail) ? message : $"{message}: {detail}"); } private static string GetExtendedErrorInfo() { var errorMessage = new StringBuilder(2048); var errorCode = DaqmxGetExtendedErrorInfo(errorMessage, (uint)errorMessage.Capacity); return errorCode == 0 ? errorMessage.ToString().Trim() : string.Empty; } [DllImport("nicaiu.dll", EntryPoint = "DAQmxCreateTask", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int DaqmxCreateTask(string taskName, out nint taskHandle); [DllImport("nicaiu.dll", EntryPoint = "DAQmxCreateDIChan", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int DaqmxCreateDIChan(nint taskHandle, string lines, string nameToAssignToLines, int lineGrouping); [DllImport("nicaiu.dll", EntryPoint = "DAQmxStartTask", CallingConvention = CallingConvention.Cdecl)] private static extern int DaqmxStartTask(nint taskHandle); [DllImport("nicaiu.dll", EntryPoint = "DAQmxReadDigitalLines", CallingConvention = CallingConvention.Cdecl)] private static extern int DaqmxReadDigitalLines( nint taskHandle, int numSampsPerChan, double timeout, int fillMode, byte[] readArray, uint arraySizeInBytes, out int sampsPerChanRead, out int numBytesPerSamp, nint reserved); [DllImport("nicaiu.dll", EntryPoint = "DAQmxStopTask", CallingConvention = CallingConvention.Cdecl)] private static extern int DaqmxStopTask(nint taskHandle); [DllImport("nicaiu.dll", EntryPoint = "DAQmxClearTask", CallingConvention = CallingConvention.Cdecl)] private static extern int DaqmxClearTask(nint taskHandle); [DllImport("nicaiu.dll", EntryPoint = "DAQmxGetExtendedErrorInfo", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int DaqmxGetExtendedErrorInfo(StringBuilder errorString, uint bufferSize); } }