using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using leak_test_project.Models; using leak_test_project.Utils; namespace leak_test_project.Infrastructure { /// /// 실제 ADLINK 하드웨어와 통신하는 DIO 보드 구현체. /// Dask.cs 래퍼를 사용하여 PCIS-DASK API를 호출합니다. /// public class RealDioBoard : IDioBoard { private short _cardNumber = -1; private readonly Dictionary _inputs = new Dictionary(); private readonly Dictionary _outputs = new Dictionary(); private bool _disposed = false; private CancellationTokenSource _pollingCts; private Task _pollingTask; private readonly object _ioLock = new object(); // 하드웨어 동시성 제어용 Lock public event EventHandler InputChanged; public event EventHandler ErrorOccurred; public bool Initialize() { var config = DioConfigParser.LoadDefault(); if (config != null) { int bitIndex = 0; foreach (var p in config.InputPoints) { p.BitIndex = bitIndex++; _inputs[p.Name] = p; } bitIndex = 0; foreach (var p in config.OutputPoints) { p.BitIndex = bitIndex++; _outputs[p.Name] = p; } ushort cardType = GetCardTypeFromConfig(config.BoardType); ushort boardNumber = (ushort)config.BoardNumber; try { short ret = DASK.Register_Card(cardType, boardNumber); if (ret < 0) { Console.WriteLine($"[RealDioBoard] Error registering ADLINK Card {config.BoardType}. Error code: {ret}"); return false; } _cardNumber = ret; Console.WriteLine($"[RealDioBoard] Registered ADLINK Card {config.BoardType} (Handle: {_cardNumber})"); } catch (Exception ex) { Console.WriteLine($"[RealDioBoard] Critical error during HW Registration: {ex.Message}"); // 드라이버가 없거나 하드웨어 접근 오류 시 앱이 죽지 않도록 false 반환 return false; } } else { Console.WriteLine("[RealDioBoard] No INI config found."); return false; } // Start background polling task for inputs _pollingCts = new CancellationTokenSource(); _pollingTask = Task.Run(() => InputPollingLoop(_pollingCts.Token)); return true; } private ushort GetCardTypeFromConfig(string boardType) { switch (boardType.ToUpper()) { case "PCI_7230": return DASK.PCI_7230; case "PCI_7432": return DASK.PCI_7432; case "PCI_7433": return DASK.PCI_7433; case "PCI_7434": return DASK.PCI_7434; case "PCI_7250": return DASK.PCI_7250; // Add more if needed. Default to 7432 default: Console.WriteLine($"[RealDioBoard] Unknown BoardType {boardType}. Defaulting to PCI_7432."); return DASK.PCI_7432; } } public bool ReadInput(string pointName) { if (_cardNumber < 0) return false; if (_inputs.TryGetValue(pointName, out var point)) { // Find port and line mathematically (Wait, actually we should use DIO port mapping) // Assuming simple 1-to-1 mapping where index == line in Port 0. int index = new List(_inputs.Values).IndexOf(point); if (index >= 0) { ushort port = 0; // Usually port 0 for first 32 lines. uint readValue; short ret = DASK.DI_ReadPort((ushort)_cardNumber, port, out readValue); if (ret >= 0) { bool isOn = (readValue & (1U << index)) != 0; point.Value = isOn; return isOn; } } } return false; } public void WriteOutput(string pointName, bool value) { if (_outputs.TryGetValue(pointName, out var point)) { // 화면의 상태(불빛)는 하드웨어 연동 여부와 관계없이 항상 업데이트합니다. (원자적 연산) point.Value = value; // 실제 하드웨어가 없으면 물리적 제어 부분만 생략합니다. if (_cardNumber < 0) return; int index = point.BitIndex; ushort port = 0; // === 스레드 안전성 확보 (경쟁 조건 방지) === lock (_ioLock) { uint outValue; DASK.DO_ReadPort((ushort)_cardNumber, port, out outValue); if (value) outValue |= (1U << index); else outValue &= ~(1U << index); DASK.DO_WritePort((ushort)_cardNumber, port, outValue); } // Console.WriteLine($"[RealDioBoard] Set {pointName} (Line {index}) to {(value ? "ON" : "OFF")}"); } } private async Task InputPollingLoop(CancellationToken token) { try { while (!token.IsCancellationRequested) { try { if (_cardNumber >= 0) { ushort port = 0; uint readValue; short ret; lock (_ioLock) { ret = DASK.DI_ReadPort((ushort)_cardNumber, port, out readValue); } if (ret >= 0) { foreach (var point in _inputs.Values) { bool isCurrentlyOn = (readValue & (1U << point.BitIndex)) != 0; // If changed from OFF to ON if (isCurrentlyOn && !point.Value) { point.Value = true; // Console.WriteLine($"[RealDioBoard] Input {point.Name} triggered (OFF→ON)"); InputChanged?.Invoke(this, new DioEventArgs(point.Name, true)); } // If changed from ON to OFF else if (!isCurrentlyOn && point.Value) { point.Value = false; } } } else { FileLogger.Log("ERROR", $"[RealDioBoard] DI_ReadPort Error: {ret}"); } } } catch (Exception ex) { FileLogger.Log("ERROR", $"[RealDioBoard] Inner Polling Error: {ex.Message}"); } // 10ms polling rate (approx) await Task.Delay(10, token); } } catch (TaskCanceledException) { // Expected when disposing } catch (Exception ex) { FileLogger.Log("ERROR", $"[RealDioBoard] Critical Polling Loop Error: {ex.Message}"); } } public List GetInputPoints() { return new List(_inputs.Values); } public List GetOutputPoints() { return new List(_outputs.Values); } public void Dispose() { if (!_disposed) { _disposed = true; if (_pollingCts != null) { _pollingCts.Cancel(); _pollingCts.Dispose(); } if (_cardNumber >= 0) { DASK.Release_Card((ushort)_cardNumber); Console.WriteLine("[RealDioBoard] Released ADLINK Card."); _cardNumber = -1; } } } } }