using AngleSharp.Dom.Events; using CapMachine.Wpf.ChannelModel; using CapMachine.Wpf.Services; using ImTools; using Masuit.Tools; using Masuit.Tools.Hardware; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Channels; using System.Threading.Tasks; using System.Timers; namespace CapMachine.Wpf.Models.ProModelPars { /// /// 程序执行模型 /// 包括程序名称和程序步骤集合、程序执行信息 /// 一个参数(速度、排气压力)对应一个程序执行模型 /// public class ProExModel : BindableBase { /// /// 实例化函数 /// public ProExModel(Channel channel) { ProRunChannel = channel; //秒触发一次 CycleTimer = new System.Timers.Timer(1000); CycleTimer.Elapsed += SlopExCycleAction; CycleTimer.AutoReset = true; CycleTimer.Enabled = true; CycleTimer.Start(); } /// /// 是否是速度仪表参数 /// public bool IsSpeed { get; set; } = false; /// /// 程序执行管道 /// public Channel ProRunChannel { get; set; } /// /// 名称 /// public string MeterName { get; set; } /// /// 当前模型是否启用 /// 当前有很多参数都是预设,但是有些参数是为其他的项目准备的,可能在当前的项目上都不会配置和使用。 /// 故设置Enable属性 /// public bool Enable { get; set; } = true; /// /// 程序步骤集合 /// 在ProRuntimeService中已经初始实例化 /// public List ListProStepExe { get; set; } /// /// 仪表步骤执行时间信息 /// public ProRunTime ProRunTimeModel { get; set; } /// /// 下一步步骤数据执行 /// public ProStepExe NextProStepExe { get; set; } /// /// 当前步骤数据执行 /// 给看步骤是否变化使用 /// public ProStepExe CurProStepExe { get; set; } /// /// 上一步步骤数据执行 /// 给看步骤是否变化使用 /// public ProStepExe LastProStepExe { get; set; } /// /// 当前程序段时间长 /// public int CurProTimeSum { get; set; } /// /// 当前程序段开始时间 /// public DateTime CurProStartTime { set; get; } #region 仪表步骤执行时间信息 /// /// 速度运行结束事件 /// public event EventHandler SpeedRunEndEvent; /// /// 是否启用 /// public bool RunEnable { get; set; } = false; /// /// RunTimeCallSglEvent 是否注册关联方法 /// public bool EventRegister { get; set; } = false; /// /// 当前步骤开始运行时间 /// public DateTime StepStartDt { get; set; } /// /// 当前步骤结束运行时间 /// public DateTime StepEndDt { get; set; } /// /// 循环扫描每个参数的结束时间是否到达的线程是一个线程循环执行,逐个执行,理论上每个都不同步,有一点点时间差,就是执行步骤会延后一点点 /// 我们的打点斜坡数据是自己的定时器执行的,每个参数同步执行,而且是有一点点提前执行的,因为先打点执行循环后再计算结束时间 /// 以上的就会造成,当新步骤到来执行时,其实之前的步骤的斜率打点的步骤可能已经打点完毕了,那么是乐见这样的状态,防止了两个的时间冲突问题。 /// private DateTime _CurrentDateTime; /// /// 当前时间 /// public DateTime CurrentDateTime { get { return _CurrentDateTime; } set { //value.ToString("yyyy-MM-dd HH:mm:ss") == EndDateTime.ToString("yyyy-MM-dd HH:mm:ss") if (RunEnable == true && value >= StepEndDt) { _CurrentDateTime = value; //达到后不再触发 //RunEnable = false; //时间到了触发下载下一步步骤 //不为空的数据 if (NextProStepExe == null) { Console.WriteLine($"【时间】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 【参数名称】:{MeterName} " + $"【Msg】:当前仪表参数全部执行完毕 "); //速度执行结束发布事件 if (IsSpeed) SpeedRunEndEvent.Invoke(this, "OK"); //为空时不执行后续的数据 RunEnable = false; return; } Console.WriteLine($"【时间】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 【参数名称】:{MeterName} " + $"【程序Seg】{CurProStepExe.ProSegName} " + $"【程序步骤】{CurProStepExe.MeterStep} " + $"【Msg】:新步骤开始执行-----------> "); ////////首先判断参数是否相同/////////// var SvResult = false;//True代表不同,false代表相同 //判断PID是否跟上一次一样 if (CurProStepExe.EndSV != NextProStepExe.EndSV) { SvResult = true; } var PidResult = false;//True代表不同,false代表相同 //判断PID是否跟上一次一样 if (CurProStepExe.PIDNo != NextProStepExe.PIDNo) { PidResult = true; } var LimitResult = false;//True代表不同,false代表相同 //判断Limit是否跟上一次一样 if (CurProStepExe.LimitNo != NextProStepExe.LimitNo) { LimitResult = true; } ////////发送步骤信息/////////// if (NextProStepExe.ExistSlop) { //存在斜坡数据,SV是通过集合陆续打点写入,但是PID和Limit需要判断是否要写入,只要写一次即可 ProRunChannel.Writer.WriteAsync(new ProRunChannelData() { MeterName = MeterName, SV = NextProStepExe.EndSV, ProSegName = NextProStepExe.ProSegName, MeterStep = NextProStepExe.MeterStep, CurLoadLimit = LimitResult == true ? new Limit() { Up = (short)NextProStepExe.CurConfigLimitDto.Up, Down = (short)NextProStepExe.CurConfigLimitDto.Down } : new Limit(), CurLoadPID = PidResult == true ? new PID() { P = (short)NextProStepExe.CurConfigPIDDto.P, I = (short)NextProStepExe.CurConfigPIDDto.I, D = (short)NextProStepExe.CurConfigPIDDto.D } : new PID(), RunStepType = GetRunStepType(PidResult, LimitResult, false), ListStepExd = GetStepExds(NextProStepExe),//拓展参数 }); //存在坡度数据 var SecStepDur = NextProStepExe.EndSV - NextProStepExe.StartSV; var SecStepValue = SecStepDur * 1.0 / NextProStepExe.KeepTime; //如果ListSlopExStep上还在执行打点任务的话,则如何处理 //先清除数据,并暂停 StopSlopExStep(); //组装斜坡数据,按照秒为间隔发送 for (var i = 1; i <= NextProStepExe.KeepTime; i++) { if (i == NextProStepExe.KeepTime) { //最后一个打点步骤数据数据,防止上面的相除得到误差,故在最后一个打点数据用NextProStepExe.EndSV,这样就确保了最后一个打点数据就是EndSV var SlopExStep = new SlopExStep() { StepNo = i, SV = NextProStepExe.EndSV, IsHasEx = false, }; ListSlopExStep.Add(SlopExStep); } else//非最后一个数据,正常累加 { var SlopExStep = new SlopExStep() { StepNo = i, SV = NextProStepExe.StartSV + (int)(SecStepValue * i), IsHasEx = false, }; ListSlopExStep.Add(SlopExStep); } } //需要发送一次StartSV数据吗? //组装完成开始循环打点 StartSlopExStep(); } else//不存在斜坡数据的话,则直接发送EndSV数据即可 { ProRunChannel.Writer.WriteAsync(new ProRunChannelData() { MeterName = MeterName, SV = NextProStepExe.EndSV, ProSegName = NextProStepExe.ProSegName, MeterStep = NextProStepExe.MeterStep, CurLoadLimit = LimitResult == true ? new Limit() { Up = (short)NextProStepExe.CurConfigLimitDto.Up, Down = (short)NextProStepExe.CurConfigLimitDto.Down } : new Limit(), CurLoadPID = PidResult == true ? new PID() { P = (short)NextProStepExe.CurConfigPIDDto.P, I = (short)NextProStepExe.CurConfigPIDDto.I, D = (short)NextProStepExe.CurConfigPIDDto.D } : new PID(), RunStepType = GetRunStepType(PidResult, LimitResult, true), ListStepExd = GetStepExds(NextProStepExe),//拓展参数 }); Console.WriteLine($"【时间】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 【参数名称】:{MeterName} " + $"【程序Seg】{CurProStepExe.ProSegName} " + $"【程序步骤】{CurProStepExe.MeterStep} " + $"【SV】{NextProStepExe.EndSV} " + $"【保持时间】{NextProStepExe.KeepTime} 秒" ); } ////////准备新的数据/////////// LastProStepExe = CurProStepExe.DeepClone(); //准备下一步步骤数据 CurProStepExe = NextProStepExe.DeepClone(); //标记状态-当前执行中 if (ListProStepExe.Where(a => a.MeterStep == CurProStepExe.MeterStep).Any()) { ListProStepExe.FindFirst(a => a.MeterStep == CurProStepExe.MeterStep).MeterStepIsExeing = true; } //标记状态-上一个执行完毕标记 if (ListProStepExe.Where(a => a.MeterStep == LastProStepExe.MeterStep).Any()) { ListProStepExe.FindFirst(a => a.MeterStep == LastProStepExe.MeterStep).MeterStepIsExeing = false; } //标记状态-上一个执行完成 if (ListProStepExe.Where(a => a.MeterStep == LastProStepExe.MeterStep).Any()) { ListProStepExe.FindFirst(a => a.MeterStep == LastProStepExe.MeterStep).MeterStepIsOK = true; } //设置下一步步骤数据 if (ListProStepExe.Where(x => x.MeterStep == CurProStepExe.MeterStep + 1).Any()) { //存在下一步数据 NextProStepExe = ListProStepExe.FirstOrDefault(x => x.MeterStep == CurProStepExe.MeterStep + 1)!; } else { //没有下一步数据则判定为最后一步 NextProStepExe = null; } //设置步骤开始时间 StepStartDt = DateTime.Now; //设置步骤结束时间 StepEndDt = StepStartDt.AddSeconds(CurProStepExe.KeepTime); //设置下一步步骤运行时间 RunEnable = true; } else { _CurrentDateTime = value; RunTime = (int)(_CurrentDateTime - StepEndDt).TotalSeconds; } } } /// /// 获取拓展信息 /// 一般是速度有这些参数 /// /// private List GetStepExds(ProStepExe proStepExe) { if (proStepExe.ListStepExd != null && proStepExe.ListStepExd.Count() > 0) { return proStepExe.ListStepExd; } return null; } /// /// 当前步骤已经运行时长-秒 /// public int RunTime { get; set; } /// /// 通过组合Pid和Limit组合出运行步骤类型 /// /// PID是否不同 /// Limit是否不同 /// SV是否不同 /// 运行步骤类型 private RunStepType GetRunStepType(bool pid, bool limit, bool sv) { if (pid && limit && sv) { return RunStepType.Step; } else if (pid && sv) { return RunStepType.StepPID; } else if (limit && sv) { return RunStepType.StepLimit; } else if (sv) { return RunStepType.StepSV; } else if (pid && limit) { return RunStepType.LimitPid; } else if (limit) { return RunStepType.Limit; } else if (pid) { return RunStepType.Pid; } else { return RunStepType.No; } } #endregion #region 步骤斜率执行 打点执行 /// /// 开始打点执行 /// private void StartSlopExStep() { //用SlopExEnable控制,存在一个可能,就是设置后,立刻执行,那么第一个步骤(第一秒)时间会被压缩,后面就正常了。最大是快一秒了,第一秒提前打点,这是可以接受的, //结合步骤的扫描执行,形成时间差,就是新步骤到来时,斜率打点已经执行完毕,防止冲突,乐见这个状态 SlopExEnable = true; } /// /// 停止执行打点任务的执行 /// private void StopSlopExStep() { SlopExEnable = false; //清空数据 ListSlopExStep.Clear(); } /// /// 斜率执行步骤集合 /// 打点集合 /// public List ListSlopExStep { get; set; } = new List(); /// /// 斜率执行循环周期 /// /// /// /// private void SlopExCycleAction(object? sender, ElapsedEventArgs e) { try { //如果Execute执行的是一个很耗时的方法,会导致方法未执行完毕,定时器又启动了一个线程来执行Execute方法 //CycleTimer.Stop(); //先关闭定时器 if (SlopExEnable) { var NoExData = ListSlopExStep.Where(a => a.IsHasEx == false).OrderBy(a => a.StepNo).ToList(); if (NoExData.Any()) { //发送步骤信息 ProRunChannel.Writer.TryWrite(new ProRunChannelData() { MeterName = MeterName, SV = NoExData.First().SV, ProSegName = CurProStepExe.ProSegName, MeterStep = CurProStepExe.MeterStep, SlopStepNo = NoExData.First().StepNo, CurLoadLimit = null, CurLoadPID = null, RunStepType = RunStepType.SlopCell, }); //发送完毕后进行标记数据 NoExData.First().IsHasEx = true; Console.WriteLine($"【时间】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 【参数名称】:{MeterName} " + $"【程序Seg】{CurProStepExe.ProSegName} " + $"【程序步骤】{CurProStepExe.MeterStep} " + $"【斜坡打点步骤】{NoExData.First().StepNo} " + $"【斜坡打点值SV】{NoExData.First().SV} " + $"【Msg】:发送斜坡打点 "); //执行一个循环打点后判断是否整个打点循环执行完毕 if (!ListSlopExStep.Where(a => a.IsHasEx == false).Any()) { //执行完毕就标记状态 SlopExEnable = false; Console.WriteLine($"【时间】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 【参数名称】:{MeterName} " + $"【程序Seg】{CurProStepExe.ProSegName} " + $"【程序步骤】{CurProStepExe.MeterStep} " + $"【Msg】:斜坡打点执行完毕 "); } } else { //执行完毕了,没有要执行的数据效率 SlopExEnable = false; return; } } //CycleTimer.Start(); //执行完毕后再开启器 } catch (Exception ex) { //CycleTimer.Start(); //执行完毕后再开启器 //LogService.Info($"时间:{DateTime.Now.ToString()}-【PwAnalyze-CycleAction】-{ex.Message}"); } } /// /// 周期斜坡打点暂停执行 /// public void PauseSlopExCyclePause() { SlopExEnable = false; } /// /// 周期斜坡打点 继续运行 /// public void ContinueSlopExCyclePause() { SlopExEnable = true; } /// /// 周期定时器 /// private System.Timers.Timer CycleTimer { get; set; } /// /// 步骤斜率执行使能状态 /// public bool SlopExEnable { get; set; } = false; #endregion } }