344 lines
14 KiB
C#
344 lines
14 KiB
C#
|
|
using MainShell.DeviceMaintance.Model;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
|
|||
|
|
namespace MainShell.Hardware
|
|||
|
|
{
|
|||
|
|
public interface IDeviceCylinderService
|
|||
|
|
{
|
|||
|
|
IReadOnlyList<CylinderDefinition> Definitions { get; }
|
|||
|
|
bool TryGetDefinition(string name, out CylinderDefinition definition);
|
|||
|
|
bool TryExtend(string name);
|
|||
|
|
bool TryExtend(string name, out string failureReason);
|
|||
|
|
bool TryRetract(string name);
|
|||
|
|
bool TryRetract(string name, out string failureReason);
|
|||
|
|
Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
|||
|
|
Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
|||
|
|
Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
|||
|
|
Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public class DeviceCylinderService : IDeviceCylinderService
|
|||
|
|
{
|
|||
|
|
private readonly IDeviceIoMonitorService _ioMonitorService;
|
|||
|
|
private readonly Dictionary<string, CylinderDefinition> _definitionsByName;
|
|||
|
|
private static readonly StringComparer PointReferenceComparer = StringComparer.OrdinalIgnoreCase;
|
|||
|
|
private static readonly TimeSpan ActiveRefreshTimeout = TimeSpan.FromMilliseconds(120);
|
|||
|
|
private static readonly TimeSpan FeedbackRetryInterval = TimeSpan.FromMilliseconds(50);
|
|||
|
|
|
|||
|
|
public IReadOnlyList<CylinderDefinition> Definitions { get; }
|
|||
|
|
|
|||
|
|
public DeviceCylinderService(IDeviceIoMonitorService ioMonitorService)
|
|||
|
|
{
|
|||
|
|
_ioMonitorService = ioMonitorService;
|
|||
|
|
Definitions = CylinderDefinitionLoader.LoadDefinitions();
|
|||
|
|
_definitionsByName = Definitions.ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool TryGetDefinition(string name, out CylinderDefinition definition)
|
|||
|
|
{
|
|||
|
|
return _definitionsByName.TryGetValue(name, out definition);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool TryExtend(string name)
|
|||
|
|
{
|
|||
|
|
string failureReason;
|
|||
|
|
return TryExtend(name, out failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool TryExtend(string name, out string failureReason)
|
|||
|
|
{
|
|||
|
|
CylinderDefinition definition;
|
|||
|
|
if (!TryGetDefinition(name, out definition))
|
|||
|
|
{
|
|||
|
|
failureReason = "未找到对应气缸定义。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Execute(definition, true, out failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool TryRetract(string name)
|
|||
|
|
{
|
|||
|
|
string failureReason;
|
|||
|
|
return TryRetract(name, out failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool TryRetract(string name, out string failureReason)
|
|||
|
|
{
|
|||
|
|
CylinderDefinition definition;
|
|||
|
|
if (!TryGetDefinition(name, out definition))
|
|||
|
|
{
|
|||
|
|
failureReason = "未找到对应气缸定义。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Execute(definition, false, out failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
var result = await TryExtendAndWaitResultAsync(name, timeout, cancellationToken);
|
|||
|
|
return result.Success;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
var result = await TryRetractAndWaitResultAsync(name, timeout, cancellationToken);
|
|||
|
|
return result.Success;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
CylinderDefinition definition;
|
|||
|
|
string failureReason;
|
|||
|
|
if (!TryGetDefinition(name, out definition))
|
|||
|
|
{
|
|||
|
|
return CreateFailureResult("未找到对应气缸定义。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!Execute(definition, true, out failureReason))
|
|||
|
|
{
|
|||
|
|
return CreateFailureResult(failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await WaitForFeedbackResultAsync(definition.ExtendedFeedbackPoints, definition.RetractedFeedbackPoints, timeout, cancellationToken);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
CylinderDefinition definition;
|
|||
|
|
string failureReason;
|
|||
|
|
if (!TryGetDefinition(name, out definition))
|
|||
|
|
{
|
|||
|
|
return CreateFailureResult("未找到对应气缸定义。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!Execute(definition, false, out failureReason))
|
|||
|
|
{
|
|||
|
|
return CreateFailureResult(failureReason);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await WaitForFeedbackResultAsync(definition.RetractedFeedbackPoints, definition.ExtendedFeedbackPoints, timeout, cancellationToken);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool Execute(CylinderDefinition definition, bool extend, out string failureReason)
|
|||
|
|
{
|
|||
|
|
if (!TryValidateConditions(definition, extend, out failureReason))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
List<DeviceIoOutputWriteRequest> requests;
|
|||
|
|
if (!TryBuildWriteRequests(definition, extend, out requests, out failureReason))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string failurePointReference;
|
|||
|
|
var result = _ioMonitorService.TrySetOutputStates(requests, out failurePointReference);
|
|||
|
|
failureReason = result
|
|||
|
|
? string.Empty
|
|||
|
|
: string.IsNullOrWhiteSpace(failurePointReference)
|
|||
|
|
? "输出控制执行失败。"
|
|||
|
|
: $"输出控制执行失败:{failurePointReference}";
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryValidateConditions(CylinderDefinition definition, bool extend, out string failureReason)
|
|||
|
|
{
|
|||
|
|
failureReason = string.Empty;
|
|||
|
|
var conditions = extend ? definition.ExtendConditions : definition.RetractConditions;
|
|||
|
|
if (conditions == null || conditions.Count == 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_ioMonitorService.TryRefreshNow(ActiveRefreshTimeout);
|
|||
|
|
|
|||
|
|
foreach (var condition in conditions)
|
|||
|
|
{
|
|||
|
|
DeviceIoPointState point;
|
|||
|
|
if (!TryGetPoint(condition.PointReference, out point))
|
|||
|
|
{
|
|||
|
|
failureReason = !string.IsNullOrWhiteSpace(condition.Message)
|
|||
|
|
? condition.Message
|
|||
|
|
: $"条件 IO {condition.PointReference} 状态不可用。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (point.Value != condition.ExpectedState)
|
|||
|
|
{
|
|||
|
|
failureReason = !string.IsNullOrWhiteSpace(condition.Message)
|
|||
|
|
? condition.Message
|
|||
|
|
: $"条件 IO {point.Name} 未满足,要求 {(condition.ExpectedState ? "ON" : "OFF")}。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task<CylinderExecutionResult> WaitForFeedbackResultAsync(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints, TimeSpan timeout, CancellationToken cancellationToken)
|
|||
|
|
{
|
|||
|
|
var normalizedOnPoints = NormalizePointReferences(requiredOnPoints);
|
|||
|
|
var normalizedOffPoints = NormalizePointReferences(requiredOffPoints);
|
|||
|
|
if (normalizedOnPoints.Count == 0 && normalizedOffPoints.Count == 0)
|
|||
|
|
{
|
|||
|
|
return CreateSuccessResult();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var start = DateTime.UtcNow;
|
|||
|
|
|
|||
|
|
while (DateTime.UtcNow - start < timeout)
|
|||
|
|
{
|
|||
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|||
|
|
|
|||
|
|
var remaining = timeout - (DateTime.UtcNow - start);
|
|||
|
|
var refreshTimeout = remaining < ActiveRefreshTimeout ? remaining : ActiveRefreshTimeout;
|
|||
|
|
if (refreshTimeout > TimeSpan.Zero)
|
|||
|
|
{
|
|||
|
|
await _ioMonitorService.TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var onReady = normalizedOnPoints.All(IsPointOn);
|
|||
|
|
var offReady = normalizedOffPoints.All(pointReference => !IsPointOn(pointReference));
|
|||
|
|
if (onReady && offReady)
|
|||
|
|
{
|
|||
|
|
return CreateSuccessResult();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await Task.Delay(FeedbackRetryInterval, cancellationToken);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return CreateFeedbackTimeoutResult(normalizedOnPoints, normalizedOffPoints);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryBuildWriteRequests(CylinderDefinition definition, bool extend, out List<DeviceIoOutputWriteRequest> requests, out string failureReason)
|
|||
|
|
{
|
|||
|
|
requests = new List<DeviceIoOutputWriteRequest>();
|
|||
|
|
failureReason = string.Empty;
|
|||
|
|
|
|||
|
|
var desiredStates = new Dictionary<string, bool>(PointReferenceComparer);
|
|||
|
|
if (!AddOutputRequests(desiredStates, definition.ExtendOutputPoints, extend, out failureReason) ||
|
|||
|
|
!AddOutputRequests(desiredStates, definition.RetractOutputPoints, !extend, out failureReason))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requests.AddRange(desiredStates.Select(x => new DeviceIoOutputWriteRequest
|
|||
|
|
{
|
|||
|
|
PointReference = x.Key,
|
|||
|
|
OutputOn = x.Value
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
if (requests.Count > 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
failureReason = "未配置输出控制点。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool AddOutputRequests(IDictionary<string, bool> desiredStates, IEnumerable<string> pointReferences, bool outputOn, out string failureReason)
|
|||
|
|
{
|
|||
|
|
failureReason = string.Empty;
|
|||
|
|
if (pointReferences == null)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var pointReference in pointReferences.Where(x => !string.IsNullOrWhiteSpace(x)))
|
|||
|
|
{
|
|||
|
|
bool existingState;
|
|||
|
|
if (desiredStates.TryGetValue(pointReference, out existingState))
|
|||
|
|
{
|
|||
|
|
if (existingState != outputOn)
|
|||
|
|
{
|
|||
|
|
failureReason = $"气缸输出配置冲突:{pointReference} 同时要求为 {(existingState ? "ON" : "OFF")} 和 {(outputOn ? "ON" : "OFF")}。";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
desiredStates[pointReference] = outputOn;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static List<string> NormalizePointReferences(IEnumerable<string> pointReferences)
|
|||
|
|
{
|
|||
|
|
return (pointReferences ?? Enumerable.Empty<string>())
|
|||
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|||
|
|
.Distinct(PointReferenceComparer)
|
|||
|
|
.ToList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryGetPoint(string pointReference, out DeviceIoPointState point)
|
|||
|
|
{
|
|||
|
|
return _ioMonitorService.TryGetPoint(pointReference, out point)
|
|||
|
|
|| _ioMonitorService.TryGetPointByPointKey(pointReference, out point);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool IsPointOn(string pointReference)
|
|||
|
|
{
|
|||
|
|
return _ioMonitorService.IsPointOn(pointReference)
|
|||
|
|
|| _ioMonitorService.IsPointOnByPointKey(pointReference);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private CylinderExecutionResult CreateFeedbackTimeoutResult(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints)
|
|||
|
|
{
|
|||
|
|
var missingOnPoints = requiredOnPoints.Where(pointReference => !IsPointOn(pointReference)).ToList();
|
|||
|
|
var missingOffPoints = requiredOffPoints.Where(IsPointOn).ToList();
|
|||
|
|
var messages = new List<string>();
|
|||
|
|
if (missingOnPoints.Count > 0)
|
|||
|
|
{
|
|||
|
|
messages.Add("以下反馈未到位(期望ON):" + string.Join(",", missingOnPoints.Select(GetPointDisplayName)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (missingOffPoints.Count > 0)
|
|||
|
|
{
|
|||
|
|
messages.Add("以下反馈未复位(期望OFF):" + string.Join(",", missingOffPoints.Select(GetPointDisplayName)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var failurePoints = missingOnPoints.Concat(missingOffPoints).Distinct(PointReferenceComparer).ToList();
|
|||
|
|
return new CylinderExecutionResult
|
|||
|
|
{
|
|||
|
|
Success = false,
|
|||
|
|
FailureReason = messages.Count > 0 ? string.Join(";", messages) : "等待气缸反馈超时。",
|
|||
|
|
FailurePointReferences = failurePoints
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string GetPointDisplayName(string pointReference)
|
|||
|
|
{
|
|||
|
|
DeviceIoPointState point;
|
|||
|
|
return TryGetPoint(pointReference, out point) ? point.Name : pointReference;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static CylinderExecutionResult CreateSuccessResult()
|
|||
|
|
{
|
|||
|
|
return new CylinderExecutionResult
|
|||
|
|
{
|
|||
|
|
Success = true,
|
|||
|
|
FailureReason = string.Empty
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static CylinderExecutionResult CreateFailureResult(string failureReason)
|
|||
|
|
{
|
|||
|
|
return new CylinderExecutionResult
|
|||
|
|
{
|
|||
|
|
Success = false,
|
|||
|
|
FailureReason = failureReason ?? string.Empty
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|