Files

344 lines
14 KiB
C#
Raw Permalink Normal View History

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
};
}
}
}