You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.7 KiB
191 lines
6.7 KiB
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);
|
|
}
|
|
}
|
|
|