|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 실제 ADLINK 하드웨어와 통신하는 DIO 보드 구현체.
|
|
|
|
|
/// Dask.cs 래퍼를 사용하여 PCIS-DASK API를 호출합니다.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class RealDioBoard : IDioBoard
|
|
|
|
|
{
|
|
|
|
|
private short _cardNumber = -1;
|
|
|
|
|
private readonly Dictionary<string, DioPoint> _inputs = new Dictionary<string, DioPoint>();
|
|
|
|
|
private readonly Dictionary<string, DioPoint> _outputs = new Dictionary<string, DioPoint>();
|
|
|
|
|
private bool _disposed = false;
|
|
|
|
|
|
|
|
|
|
private CancellationTokenSource _pollingCts;
|
|
|
|
|
private Task _pollingTask;
|
|
|
|
|
|
|
|
|
|
public event EventHandler<DioEventArgs> InputChanged;
|
|
|
|
|
|
|
|
|
|
public bool Initialize()
|
|
|
|
|
{
|
|
|
|
|
var config = DioConfigParser.LoadDefault();
|
|
|
|
|
if (config != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var p in config.InputPoints)
|
|
|
|
|
_inputs[p.Name] = p;
|
|
|
|
|
|
|
|
|
|
foreach (var p in config.OutputPoints)
|
|
|
|
|
_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<DioPoint>(_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 (_cardNumber < 0) return;
|
|
|
|
|
|
|
|
|
|
if (_outputs.TryGetValue(pointName, out var point))
|
|
|
|
|
{
|
|
|
|
|
int index = new List<DioPoint>(_outputs.Values).IndexOf(point);
|
|
|
|
|
if (index >= 0)
|
|
|
|
|
{
|
|
|
|
|
point.Value = value;
|
|
|
|
|
ushort port = 0;
|
|
|
|
|
|
|
|
|
|
// Actually, to safely write a single bit without affecting others:
|
|
|
|
|
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 = DASK.DI_ReadPort((ushort)_cardNumber, port, out readValue);
|
|
|
|
|
if (ret >= 0)
|
|
|
|
|
{
|
|
|
|
|
var inputPoints = new List<DioPoint>(_inputs.Values);
|
|
|
|
|
for (int i = 0; i < inputPoints.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var point = inputPoints[i];
|
|
|
|
|
bool isCurrentlyOn = (readValue & (1U << i)) != 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));
|
|
|
|
|
}
|
|
|
|
|
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<DioPoint> GetInputPoints()
|
|
|
|
|
{
|
|
|
|
|
return new List<DioPoint>(_inputs.Values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<DioPoint> GetOutputPoints()
|
|
|
|
|
{
|
|
|
|
|
return new List<DioPoint>(_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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|