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 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 TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)); Task TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)); Task TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)); Task TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)); } public class DeviceCylinderService : IDeviceCylinderService { private readonly IDeviceIoMonitorService _ioMonitorService; private readonly Dictionary _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 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 TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)) { var result = await TryExtendAndWaitResultAsync(name, timeout, cancellationToken); return result.Success; } public async Task TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)) { var result = await TryRetractAndWaitResultAsync(name, timeout, cancellationToken); return result.Success; } public async Task 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 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 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 WaitForFeedbackResultAsync(IReadOnlyList requiredOnPoints, IReadOnlyList 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 requests, out string failureReason) { requests = new List(); failureReason = string.Empty; var desiredStates = new Dictionary(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 desiredStates, IEnumerable 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 NormalizePointReferences(IEnumerable pointReferences) { return (pointReferences ?? Enumerable.Empty()) .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 requiredOnPoints, IReadOnlyList requiredOffPoints) { var missingOnPoints = requiredOnPoints.Where(pointReference => !IsPointOn(pointReference)).ToList(); var missingOffPoints = requiredOffPoints.Where(IsPointOn).ToList(); var messages = new List(); 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 }; } } }