Files
ADP/MainModule/ViewModels/AutomatedTestingViewModel.cs

302 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Input;
using DeviceCommand.Devices;
using Logger;
using Prism.Ioc;
using TestingModule.ViewModels;
using UIShare;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
using static System.Formats.Asn1.AsnWriter;
namespace MainModule.ViewModels
{
public class AutomatedTestingViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
private string _testStatus;
private readonly IScopedProvider _scope;
private bool IsInitialized =false;
private bool _isRunningErrorSteps = false;
private SubscriptionToken? _alarmToken;
#endregion
#region
public bool KeepAlive => true; // 保持存活
public string TestStatus
{
get => _testStatus;
set => SetProperty(ref _testStatus, value);
}
// 该 AutomatedTestingView 实例独占的 ScopedContext5 个子面板共享
public ScopedContext _scopedContext { get; }
public StepRunning _stepRunning { get; }
public GlobalInfo _globalInfo { get; }
public SystemConfig _systemConfig { get; set; }
public DeviceManager _deviceManager { get; set; }
// 5 个子 ViewModel 全部从同一个 scope 解析,自动注入同一个 ScopedContext
public CommandTreeViewModel CommandTreeVM { get; }
public StepsManagerViewModel StepsManagerVM { get; }
public SingleStepEditViewModel SingleStepEditVM { get; }
public LogAreaViewModel LogAreaVM { get; }
public ParametersManagerViewModel ParametersManagerVM { get; }
#endregion
public ICommand RefreshCommand { get; set; }
public ICommand BackToProtocolCommand { get; set; }
public ICommand LoadedCommand { get; set; }
public AutomatedTestingViewModel(IContainerExtension container) : base(container)
{
// 每个 AutomatedTestingViewModel 实例创建独立的容器作用域
_scope = container.CreateScope();
_globalInfo =container.Resolve<GlobalInfo>();
_systemConfig = _scope.Resolve<SystemConfig>();
//加载Json数据
if (ConfigService.IsExit(_globalInfo.CurrentOpeningScope))
{
string filePath = System.IO.Path.Combine(_systemConfig.SystemPath, $"{_globalInfo.CurrentOpeningScope}.json");
if (System.IO.File.Exists(filePath))
{
string json = System.IO.File.ReadAllText(filePath);
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
}
}
//容器解析顺序不要改变!!!
_scopedContext = _scope.Resolve<ScopedContext>();
_deviceManager = _scope.Resolve<DeviceManager>();
_stepRunning = _scope.Resolve<StepRunning>();
// 从同一个 _scope 解析 5 个子 VMDI 会把同一个 ScopedContext 注入它们
CommandTreeVM = _scope.Resolve<CommandTreeViewModel>();
StepsManagerVM = _scope.Resolve<StepsManagerViewModel>();
SingleStepEditVM = _scope.Resolve<SingleStepEditViewModel>();
LogAreaVM = _scope.Resolve<LogAreaViewModel>();
ParametersManagerVM = _scope.Resolve<ParametersManagerViewModel>();
RefreshCommand = new DelegateCommand(OnRefresh);
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
LoadedCommand = new AsyncDelegateCommand(OnLoad);
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Subscribe(async (scope) => await SilenceBuzzer(scope));
_alarmToken = _eventAggregator.GetEvent<AlarmEvent>().Subscribe(OnAlarmTriggered);
}
private async Task SilenceBuzzer(string scope)
{
if (_deviceManager.IOGroup == null) return;
if (scope == "default")
{
for(int i = 1; i <= 8; i++)
{
await _deviceManager.IOGroup.Async(i, ., false);
}
}
else if (scope == TestStatus)
{
int = _systemConfig.SharedParameterList
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
if ( >= 1 && <= 8)
{
await _deviceManager.IOGroup.Async(, ., false);
}
}
}
/// <summary>
/// AlarmEvent 回调:
/// 上下限 → 蜂鸣器报警 true
/// 上下极限 → 蜂鸣器报警 true + 执行对应 scope 的异常流程
/// </summary>
private async void OnAlarmTriggered((string Scope, string Fingerprint, string Status) args)
{
if (args.Scope != TestStatus) return;
if (_deviceManager?.IOGroup == null) return;
int = _systemConfig.SharedParameterList
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
if ( < 1 || > 8) return;
// 上下限 / 上下极限 均触发蜂鸣器报警
await _deviceManager.IOGroup.Async(, ., true);
LoggerHelper.WarnWithNotify(args.Scope, $"[{args.Scope}] 报警触发: {args.Status} ({args.Fingerprint}),蜂鸣器已开启。");
// 上下极限额外触发异常流程
if (args.Status == "超上极限" || args.Status == "超下极限")
{
_ = TriggerErrorStepsAsync();
}
}
/// <summary>
/// 执行异常流程(带并发保护,同一时间仅允许一次)。
/// 如果正常流程正在运行,先取消并等待其停止,再执行异常流程。
/// </summary>
private async Task TriggerErrorStepsAsync()
{
if (_isRunningErrorSteps) return;
_isRunningErrorSteps = true;
try
{
// 1. 如果正常流程正在运行,先停止它
// IsStop == false 表示正在运行IsStop == null 表示未启动或已结束
if (_scopedContext.IsStop == false)
{
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 报警触发,正在停止正常流程...");
_stepRunning.stepCTS.Cancel();
// 等待 ShellViewModel 清理完毕IsStop 变回 null、stepCTS 被重置)
// ExecuteMethodStep 内部硬编码使用 stepCTS.Token必须等它重置后才能跑异常流程
var deadline = DateTime.Now.AddSeconds(5);
while ((_scopedContext.IsStop != null || _stepRunning.stepCTS.IsCancellationRequested)
&& DateTime.Now < deadline)
{
await Task.Delay(50);
}
// 超时后仍未重置则强制重置,确保异常流程的设备命令能拿到有效 Token
if (_stepRunning.stepCTS.IsCancellationRequested)
{
_stepRunning.stepCTS = new CancellationTokenSource();
_scopedContext.IsStop = null;
_scopedContext.RunState = "运行";
}
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 正常流程已停止,开始执行异常流程");
}
// 2. 执行异常流程
await _stepRunning.ExecuteErrorSteps(
_scopedContext.Program,
cancellationToken: _stepRunning.errorStepCTS.Token);
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(TestStatus, $"[{TestStatus}] 报警触发异常流程失败: {ex.Message}");
}
finally
{
_isRunningErrorSteps = false;
if (_stepRunning.errorStepCTS.IsCancellationRequested)
_stepRunning.errorStepCTS = new CancellationTokenSource();
}
}
public void Dispose()
{
// 1. 如果 TestStatus 为空,说明还没走到 OnNavigatedTo 赋值,直接释放 scope 即可
if (string.IsNullOrEmpty(TestStatus))
{
_scope?.Dispose();
return;
}
try
{
if (_alarmToken != null)
_eventAggregator.GetEvent<AlarmEvent>().Unsubscribe(_alarmToken);
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Unsubscribe(async (scope) => await SilenceBuzzer(scope));
// 2. 显式释放硬件资源(防止端口占用/死锁)
if (_deviceManager is IDisposable disposableDevice)
{
disposableDevice.Dispose();
}
(CommandTreeVM as IDisposable)?.Dispose();
(StepsManagerVM as IDisposable)?.Dispose();
(SingleStepEditVM as IDisposable)?.Dispose();
(LogAreaVM as IDisposable)?.Dispose();
(ParametersManagerVM as IDisposable)?.Dispose();
_globalInfo.ContextDic?.Remove(TestStatus);
_globalInfo.StepRunningDic?.Remove(TestStatus);
_globalInfo.ConfigDic?.Remove(TestStatus);
_globalInfo.ScopeDic?.Remove(TestStatus);
}
catch (Exception ex)
{
Logger.LoggerHelper.ErrorWithNotify(TestStatus, $"卸载机台 [{TestStatus}] 全局引用或资源失败: {ex.Message}");
}
finally
{
_scope?.Dispose();
}
}
#region
private async Task OnLoad()
{
if (!IsInitialized)
{
await _deviceManager.ConnectAllDevices();
IsInitialized = true;
}
}
private void OnRefresh()
{
// 双击:把自己的名字扔出去
_globalInfo.CurrentScope = TestStatus;
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
}
private void OnBackToProtocol()
{
// 返回:扔个空字符串出去
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
}
#endregion
#region
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
if (navigationContext.Parameters.ContainsKey("Name"))
{
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
_globalInfo.ContextDic.Add(TestStatus, _scopedContext);
_globalInfo.StepRunningDic.Add(TestStatus, _stepRunning);
_globalInfo.ScopeDic.Add(TestStatus, _scope);
_globalInfo.ConfigDic.Add(TestStatus, _systemConfig);
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
{
var filePath = _systemConfig.DefaultProgramFilePath;
_systemConfig.CurrentADPFile= _systemConfig.DefaultProgramFilePath;
// 读取 JSON 文件
string json = File.ReadAllText(filePath);
// 反序列化为 ProgramVM
var program = Newtonsoft.Json.JsonConvert.DeserializeObject<ProgramVM>(json);
if (program == null)
{
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"文件格式不正确或为空: {filePath}");
return;
}
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
_scopedContext.Program.Parameters = program.Parameters;
_scopedContext.Program.StepCollection = program.StepCollection;
_scopedContext.Program.ErrorStepCollection = program.ErrorStepCollection;
_scopedContext.CurrentFilePath = filePath;
foreach (var item in _systemConfig.SharedParameterList)
{
var parameter = _scopedContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
if (parameter != null)
{
parameter.Value = item.Value;
}
}
}
}
}
#endregion
}
}