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.

192 lines
6.7 KiB

2 months ago
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);
}
}