添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,950 @@
|
||||
using MainShell.EventArgsFolder;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public interface IDeviceIoMonitorService
|
||||
{
|
||||
event EventHandler<DeviceIoChangedEventArgs> IoChanged;
|
||||
IReadOnlyList<DeviceIoPointDefinition> Definitions { get; }
|
||||
IReadOnlyDictionary<string, List<DeviceIoPointDefinition>> DefinitionsByModule { get; }
|
||||
IReadOnlyDictionary<string, DeviceIoPointState> LatestPoints { get; }
|
||||
bool IsOnline { get; }
|
||||
bool TryGetPoint(int id, out DeviceIoPointState point);
|
||||
bool TryGetPoint(string name, out DeviceIoPointState point);
|
||||
bool TryGetPointByPointKey(string pointKey, out DeviceIoPointState point);
|
||||
bool IsPointOn(int id);
|
||||
bool IsPointOn(string name);
|
||||
bool IsPointOnByPointKey(string pointKey);
|
||||
Task<bool> WaitForPointStateAsync(int id, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitForPointStateAsync(string name, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitForPointStateByPointKeyAsync(string pointKey, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
IReadOnlyList<DeviceIoPointState> GetPointsByModule(string module);
|
||||
void Start();
|
||||
void Stop();
|
||||
void RequestRefresh();
|
||||
bool TryRefreshNow(TimeSpan timeout);
|
||||
Task<bool> TryRefreshNowAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
bool TrySetOutputState(int id, bool outputOn);
|
||||
bool TrySetOutputState(string name, bool outputOn);
|
||||
bool TrySetOutputStateByPointKey(string pointKey, bool outputOn);
|
||||
bool TrySetOutputStates(IReadOnlyList<DeviceIoOutputWriteRequest> requests, out string failurePointReference);
|
||||
bool TrySetOutputStatesByPointKey(IReadOnlyDictionary<string, bool> outputStates, out string failurePointKey);
|
||||
}
|
||||
|
||||
public class DeviceIoMonitorService : IDeviceIoMonitorService
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly AutoResetEvent _refreshSignal = new AutoResetEvent(false);
|
||||
private readonly TimeSpan _pollInterval = TimeSpan.FromMilliseconds(100);
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly List<DeviceIoPointDefinition> _definitions = new List<DeviceIoPointDefinition>();
|
||||
private readonly Dictionary<string, List<DeviceIoPointDefinition>> _definitionsByModule = new Dictionary<string, List<DeviceIoPointDefinition>>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DeviceIoPointDefinition> _definitionsByName = new Dictionary<string, DeviceIoPointDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DeviceIoPointDefinition> _definitionsByPointKey = new Dictionary<string, DeviceIoPointDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly object _refreshWaitersSyncRoot = new object();
|
||||
private readonly List<TaskCompletionSource<bool>> _refreshWaiters = new List<TaskCompletionSource<bool>>();
|
||||
private const string DeviceIoConfigFileName = "DeviceIoPoints.csv";
|
||||
|
||||
private readonly Dictionary<string, DeviceIoPointState> _latestPoints = new Dictionary<string, DeviceIoPointState>(StringComparer.OrdinalIgnoreCase);
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public event EventHandler<DeviceIoChangedEventArgs> IoChanged;
|
||||
|
||||
public IReadOnlyList<DeviceIoPointDefinition> Definitions => _definitions;
|
||||
public IReadOnlyDictionary<string, List<DeviceIoPointDefinition>> DefinitionsByModule => _definitionsByModule;
|
||||
public IReadOnlyDictionary<string, DeviceIoPointState> LatestPoints => GetLatestPointsSnapshot();
|
||||
public bool IsOnline => _hardwareManager.TdCard != null || _hardwareManager.AcsCard != null;
|
||||
|
||||
|
||||
public DeviceIoMonitorService(HardwareManager hardwareManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
LoadIoPointDefinitions();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_cts != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadIoPointDefinitions();
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
_refreshSignal.WaitOne(_pollInterval);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
IList<DeviceIoPointState> readPoints;
|
||||
if (!TryReadIoPoints(out readPoints) || readPoints == null || readPoints.Count == 0)
|
||||
{
|
||||
CompleteRefreshWaiters(false);
|
||||
await Task.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = CollectChangedPoints(readPoints);
|
||||
CompleteRefreshWaiters(true);
|
||||
if (changed.Count == 0)
|
||||
{
|
||||
await Task.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
var handler = IoChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var changedCopy = changed.Select(x => x.Clone()).ToList();
|
||||
var allCopy = GetLatestPointsSnapshot();
|
||||
Execute.OnUIThread(() => handler(this, new DeviceIoChangedEventArgs(changedCopy, allCopy)));
|
||||
}
|
||||
|
||||
await Task.Yield();
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_cts == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cts.Cancel();
|
||||
_refreshSignal.Set();
|
||||
CompleteRefreshWaiters(false);
|
||||
_cts.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public void RequestRefresh()
|
||||
{
|
||||
_refreshSignal.Set();
|
||||
}
|
||||
|
||||
public bool TryRefreshNow(TimeSpan timeout)
|
||||
{
|
||||
return TryRefreshNowAsync(timeout).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<bool> TryRefreshNowAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Start();
|
||||
|
||||
var waiter = new TaskCompletionSource<bool>();
|
||||
CancellationTokenRegistration registration = default(CancellationTokenRegistration);
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
_refreshWaiters.Add(waiter);
|
||||
}
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
registration = cancellationToken.Register(() => waiter.TrySetCanceled());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RequestRefresh();
|
||||
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
{
|
||||
timeout = _pollInterval;
|
||||
}
|
||||
|
||||
var completedTask = await Task.WhenAny(waiter.Task, Task.Delay(timeout, cancellationToken));
|
||||
if (completedTask != waiter.Task)
|
||||
{
|
||||
RemoveRefreshWaiter(waiter);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await waiter.Task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
registration.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetOutputState(int id, bool outputOn)
|
||||
{
|
||||
var definition = ResolveUniqueDefinitionById(id, IoPointType.Output);
|
||||
if (definition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStateByPointKey(string pointKey, bool outputOn)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByPointKey.TryGetValue(pointKey ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStates(IReadOnlyList<DeviceIoOutputWriteRequest> requests, out string failurePointReference)
|
||||
{
|
||||
failurePointReference = string.Empty;
|
||||
if (requests == null || requests.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var definitions = new List<Tuple<DeviceIoPointDefinition, bool, string>>();
|
||||
foreach (var request in requests)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.PointReference))
|
||||
{
|
||||
failurePointReference = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!TryResolveOutputDefinition(request.PointReference, out definition))
|
||||
{
|
||||
failurePointReference = request.PointReference;
|
||||
return false;
|
||||
}
|
||||
|
||||
definitions.Add(Tuple.Create(definition, request.OutputOn, request.PointReference));
|
||||
}
|
||||
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
if (!_hardwareManager.TryWriteIoPointValue(item.Item1, item.Item2))
|
||||
{
|
||||
failurePointReference = item.Item3;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RequestRefresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStatesByPointKey(IReadOnlyDictionary<string, bool> outputStates, out string failurePointKey)
|
||||
{
|
||||
failurePointKey = string.Empty;
|
||||
if (outputStates == null || outputStates.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var definitions = new List<Tuple<DeviceIoPointDefinition, bool, string>>();
|
||||
foreach (var item in outputStates)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByPointKey.TryGetValue(item.Key ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
failurePointKey = item.Key;
|
||||
return false;
|
||||
}
|
||||
|
||||
definitions.Add(Tuple.Create(definition, item.Value, item.Key));
|
||||
}
|
||||
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
if (!_hardwareManager.TryWriteIoPointValue(item.Item1, item.Item2))
|
||||
{
|
||||
failurePointKey = item.Item3;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RequestRefresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetOutputState(string name, bool outputOn)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByName.TryGetValue(name ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryGetPoint(int id, out DeviceIoPointState point)
|
||||
{
|
||||
var definition = ResolveUniqueDefinitionById(id, null);
|
||||
if (definition == null)
|
||||
{
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(definition.PointKey, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPointByPointKey(string pointKey, out DeviceIoPointState point)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(pointKey ?? string.Empty, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsPointOn(int id)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPoint(id, out point) && point.Value;
|
||||
}
|
||||
|
||||
public bool IsPointOn(string name)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPoint(name, out point) && point.Value;
|
||||
}
|
||||
|
||||
public bool IsPointOnByPointKey(string pointKey)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPointByPointKey(pointKey, out point) && point.Value;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateAsync(int id, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOn(id) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateByPointKeyAsync(string pointKey, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOnByPointKey(pointKey) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateAsync(string name, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOn(name) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPoint(string name, out DeviceIoPointState point)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByName.TryGetValue(name ?? string.Empty, out definition) && !_definitionsByPointKey.TryGetValue(name ?? string.Empty, out definition))
|
||||
{
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(definition.PointKey, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DeviceIoPointState> GetPointsByModule(string module)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _latestPoints.Values
|
||||
.Where(x => string.Equals(x.Module, module, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(x => x.Clone())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryReadIoPoints(out IList<DeviceIoPointState> points)
|
||||
{
|
||||
points = new List<DeviceIoPointState>();
|
||||
|
||||
if (Definitions == null || Definitions.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, bool> readValues;
|
||||
bool hasBatchValues = _hardwareManager.TryReadIoPointValues(Definitions, out readValues);
|
||||
|
||||
foreach (DeviceIoPointDefinition definition in Definitions)
|
||||
{
|
||||
bool value;
|
||||
if (!definition.Enabled)
|
||||
{
|
||||
value = definition.DefaultValue;
|
||||
}
|
||||
else if (hasBatchValues && readValues != null && readValues.TryGetValue(definition.PointKey, out value))
|
||||
{
|
||||
}
|
||||
else if (!_hardwareManager.TryReadIoPointValue(definition, out value))
|
||||
{
|
||||
value = definition.DefaultValue;
|
||||
}
|
||||
|
||||
DeviceIoPointState state;
|
||||
if (definition.Type == IoPointType.Output)
|
||||
{
|
||||
state = new DeviceOutputPointState
|
||||
{
|
||||
Id = definition.Id,
|
||||
PointKey = definition.PointKey,
|
||||
Module = definition.Module,
|
||||
Name = definition.Name,
|
||||
Card = definition.Card,
|
||||
StationNo = definition.StationNo,
|
||||
LineNo = definition.LineNo,
|
||||
Value = value,
|
||||
IsInverse = definition.IsInverse,
|
||||
CommandValue = value
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
state = new DeviceInputPointState
|
||||
{
|
||||
Id = definition.Id,
|
||||
PointKey = definition.PointKey,
|
||||
Module = definition.Module,
|
||||
Name = definition.Name,
|
||||
Card = definition.Card,
|
||||
StationNo = definition.StationNo,
|
||||
LineNo = definition.LineNo,
|
||||
Value = value,
|
||||
IsInverse = definition.IsInverse
|
||||
};
|
||||
}
|
||||
|
||||
points.Add(state);
|
||||
}
|
||||
|
||||
return points.Count > 0;
|
||||
}
|
||||
|
||||
private void LoadIoPointDefinitions()
|
||||
{
|
||||
_definitions.Clear();
|
||||
_definitionsByModule.Clear();
|
||||
_definitionsByName.Clear();
|
||||
_definitionsByPointKey.Clear();
|
||||
|
||||
var pointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var path = ResolveConfigPath();
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = File.ReadAllLines(path, Encoding.UTF8);
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var raw = lines[i];
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var line = raw.Trim();
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 0 && line.IndexOf("Id", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cols = line.Split(',');
|
||||
if (cols.Length < 11)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (!int.TryParse(cols[0].Trim(), out id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var moduleIndex = 1;
|
||||
var nameIndex = 2;
|
||||
var typeIndex = 3;
|
||||
var cardIndex = 4;
|
||||
var stationIndex = 5;
|
||||
var indexColumnIndex = 6;
|
||||
var subIndexColumnIndex = 7;
|
||||
var lineNoIndex = 8;
|
||||
var enabledIndex = 9;
|
||||
var defaultValueIndex = 10;
|
||||
var isInverseIndex = cols.Length >= 12 ? 11 : -1;
|
||||
var descriptionIndex = isInverseIndex >= 0 ? 12 : 11;
|
||||
|
||||
var name = cols[nameIndex].Trim();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var module = cols[moduleIndex].Trim();
|
||||
if (string.IsNullOrWhiteSpace(module))
|
||||
{
|
||||
module = "Default";
|
||||
}
|
||||
|
||||
var type = ParseIoPointType(cols[typeIndex]);
|
||||
var card = ParseIoCardType(cols[cardIndex]);
|
||||
var stationNo = ParseInt(cols[stationIndex]);
|
||||
var index = ParseInt(cols[indexColumnIndex]);
|
||||
var subIndex = ParseInt(cols[subIndexColumnIndex]);
|
||||
var lineNo = cols[lineNoIndex].Trim();
|
||||
var pointKey = DeviceIoPointDefinition.BuildPointKey(type, card, stationNo, index, subIndex, lineNo);
|
||||
|
||||
if (!pointKeys.Add(pointKey) || !names.Add(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var definition = new DeviceIoPointDefinition
|
||||
{
|
||||
Id = id,
|
||||
PointKey = pointKey,
|
||||
Module = module,
|
||||
Name = name,
|
||||
Type = type,
|
||||
Card = card,
|
||||
StationNo = stationNo,
|
||||
Index = index,
|
||||
SubIndex = subIndex,
|
||||
LineNo = lineNo,
|
||||
Enabled = ParseBoolean(cols[enabledIndex], true),
|
||||
DefaultValue = ParseBoolean(cols[defaultValueIndex], false),
|
||||
IsInverse = isInverseIndex >= 0 && isInverseIndex < cols.Length && ParseBoolean(cols[isInverseIndex], false),
|
||||
Description = cols.Length > descriptionIndex ? string.Join(",", cols.Skip(descriptionIndex)).Trim() : string.Empty
|
||||
};
|
||||
|
||||
_definitions.Add(definition);
|
||||
_definitionsByName[definition.Name] = definition;
|
||||
_definitionsByPointKey[definition.PointKey] = definition;
|
||||
|
||||
List<DeviceIoPointDefinition> moduleDefinitions;
|
||||
if (!_definitionsByModule.TryGetValue(module, out moduleDefinitions))
|
||||
{
|
||||
moduleDefinitions = new List<DeviceIoPointDefinition>();
|
||||
_definitionsByModule[module] = moduleDefinitions;
|
||||
}
|
||||
|
||||
moduleDefinitions.Add(definition);
|
||||
}
|
||||
}
|
||||
|
||||
private static int ParseInt(string raw)
|
||||
{
|
||||
int value;
|
||||
return int.TryParse((raw ?? string.Empty).Trim(), out value) ? value : 0;
|
||||
}
|
||||
|
||||
private static IoPointType ParseIoPointType(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return IoPointType.Input;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "Output", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "OUT", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "DO", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "O", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return IoPointType.Output;
|
||||
}
|
||||
|
||||
return IoPointType.Input;
|
||||
}
|
||||
|
||||
private static IoCardType ParseIoCardType(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return IoCardType.Td;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "Acs", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "ACS", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return IoCardType.Acs;
|
||||
}
|
||||
|
||||
return IoCardType.Td;
|
||||
}
|
||||
|
||||
private static bool ParseBoolean(string raw, bool defaultValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "yes", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "y", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(text, "0", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "false", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "no", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "n", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private DeviceIoPointDefinition ResolveUniqueDefinitionById(int id, IoPointType? pointType)
|
||||
{
|
||||
var matches = _definitions
|
||||
.Where(x => x.Id == id && (!pointType.HasValue || x.Type == pointType.Value))
|
||||
.Take(2)
|
||||
.ToList();
|
||||
|
||||
return matches.Count == 1 ? matches[0] : null;
|
||||
}
|
||||
|
||||
private void CompleteRefreshWaiters(bool result)
|
||||
{
|
||||
List<TaskCompletionSource<bool>> waiters;
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
if (_refreshWaiters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
waiters = _refreshWaiters.ToList();
|
||||
_refreshWaiters.Clear();
|
||||
}
|
||||
|
||||
foreach (var waiter in waiters)
|
||||
{
|
||||
waiter.TrySetResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveRefreshWaiter(TaskCompletionSource<bool> waiter)
|
||||
{
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
_refreshWaiters.Remove(waiter);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryResolveOutputDefinition(string pointReference, out DeviceIoPointDefinition definition)
|
||||
{
|
||||
definition = null;
|
||||
if (string.IsNullOrWhiteSpace(pointReference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_definitionsByPointKey.TryGetValue(pointReference, out definition))
|
||||
{
|
||||
return definition.Type == IoPointType.Output && definition.Enabled;
|
||||
}
|
||||
|
||||
if (_definitionsByName.TryGetValue(pointReference, out definition))
|
||||
{
|
||||
return definition.Type == IoPointType.Output && definition.Enabled;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (int.TryParse(pointReference, out id))
|
||||
{
|
||||
definition = ResolveUniqueDefinitionById(id, IoPointType.Output);
|
||||
return definition != null && definition.Enabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ResolveConfigPath()
|
||||
{
|
||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var localPath = Path.Combine(baseDir, "Configuration", DeviceIoConfigFileName);
|
||||
var sourcePath = ResolveRootConfigPath(baseDir);
|
||||
|
||||
if (TrySyncConfigToLocal(sourcePath, localPath))
|
||||
{
|
||||
return localPath;
|
||||
}
|
||||
|
||||
return File.Exists(localPath) ? localPath : null;
|
||||
}
|
||||
|
||||
private static string ResolveRootConfigPath(string baseDir)
|
||||
{
|
||||
var current = new DirectoryInfo(baseDir);
|
||||
while (current != null)
|
||||
{
|
||||
var rootConfigDir = Path.Combine(current.FullName, "Configuration");
|
||||
if (Directory.Exists(rootConfigDir) && Directory.Exists(Path.Combine(current.FullName, "MainShell")))
|
||||
{
|
||||
var path = Path.Combine(rootConfigDir, DeviceIoConfigFileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TrySyncConfigToLocal(string sourcePath, string localPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(Path.GetFullPath(sourcePath), Path.GetFullPath(localPath), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var localDir = Path.GetDirectoryName(localPath);
|
||||
if (!string.IsNullOrWhiteSpace(localDir))
|
||||
{
|
||||
Directory.CreateDirectory(localDir);
|
||||
}
|
||||
|
||||
if (!File.Exists(localPath) || File.GetLastWriteTimeUtc(localPath) < File.GetLastWriteTimeUtc(sourcePath))
|
||||
{
|
||||
File.Copy(sourcePath, localPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<DeviceIoPointState> CollectChangedPoints(IList<DeviceIoPointState> readPoints)
|
||||
{
|
||||
var changed = new List<DeviceIoPointState>();
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.Count == 0)
|
||||
{
|
||||
foreach (var point in readPoints)
|
||||
{
|
||||
if (point == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
foreach (var point in readPoints)
|
||||
{
|
||||
if (point == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_latestPoints.TryGetValue(point.PointKey, out DeviceIoPointState old))
|
||||
{
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
changed.Add(PreparePoint(point.Clone()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasPointChanged(old, point))
|
||||
{
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
changed.Add(PreparePoint(point.Clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, DeviceIoPointState> GetLatestPointsSnapshot()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _latestPoints.ToDictionary(x => x.Key, x => PreparePoint(x.Value.Clone()));
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceIoPointState PreparePoint(DeviceIoPointState point)
|
||||
{
|
||||
var outputPoint = point as DeviceOutputPointState;
|
||||
DeviceIoPointDefinition definition;
|
||||
_definitionsByPointKey.TryGetValue(point.PointKey ?? string.Empty, out definition);
|
||||
if (outputPoint != null && definition != null && definition.Enabled)
|
||||
{
|
||||
outputPoint.BindWriteAction(outputOn => TrySetOutputState(definition.Name, outputOn));
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
private static bool HasPointChanged(DeviceIoPointState oldPoint, DeviceIoPointState newPoint)
|
||||
{
|
||||
if (oldPoint == null || newPoint == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oldPoint.Value != newPoint.Value ||
|
||||
oldPoint.PointType != newPoint.PointType ||
|
||||
!string.Equals(oldPoint.PointKey, newPoint.PointKey, StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(oldPoint.Name, newPoint.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(oldPoint.Module, newPoint.Module, StringComparison.OrdinalIgnoreCase) ||
|
||||
oldPoint.Card != newPoint.Card)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldOutput = oldPoint as DeviceOutputPointState;
|
||||
var newOutput = newPoint as DeviceOutputPointState;
|
||||
if (oldOutput != null && newOutput != null)
|
||||
{
|
||||
return oldOutput.CommandValue != newOutput.CommandValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user