Files
CapMachine/CapMachine.Wpf/Models/ProModelPars/ProExModel.cs
2025-04-15 21:42:57 +08:00

525 lines
21 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 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
{
/// <summary>
/// 程序执行模型
/// 包括程序名称和程序步骤集合、程序执行信息
/// 一个参数(速度、排气压力)对应一个程序执行模型
/// </summary>
public class ProExModel : BindableBase
{
/// <summary>
/// 实例化函数
/// </summary>
public ProExModel(Channel<ProRunChannelData> channel)
{
ProRunChannel = channel;
//秒触发一次
CycleTimer = new System.Timers.Timer(1000);
CycleTimer.Elapsed += SlopExCycleAction;
CycleTimer.AutoReset = true;
CycleTimer.Enabled = true;
CycleTimer.Start();
}
/// <summary>
/// 是否是速度仪表参数
/// </summary>
public bool IsSpeed { get; set; } = false;
/// <summary>
/// 程序执行管道
/// </summary>
public Channel<ProRunChannelData> ProRunChannel { get; set; }
/// <summary>
/// 名称
/// </summary>
public string MeterName { get; set; }
/// <summary>
/// 当前模型是否启用
/// 当前有很多参数都是预设,但是有些参数是为其他的项目准备的,可能在当前的项目上都不会配置和使用。
/// 故设置Enable属性
/// </summary>
public bool Enable { get; set; } = true;
/// <summary>
/// 程序步骤集合
/// 在ProRuntimeService中已经初始实例化
/// </summary>
public List<ProStepExe> ListProStepExe { get; set; }
/// <summary>
/// 仪表步骤执行时间信息
/// </summary>
public ProRunTime ProRunTimeModel { get; set; }
/// <summary>
/// 下一步步骤数据执行
/// </summary>
public ProStepExe NextProStepExe { get; set; }
/// <summary>
/// 当前步骤数据执行
/// 给看步骤是否变化使用
/// </summary>
public ProStepExe CurProStepExe { get; set; }
/// <summary>
/// 上一步步骤数据执行
/// 给看步骤是否变化使用
/// </summary>
public ProStepExe LastProStepExe { get; set; }
/// <summary>
/// 当前程序段时间长
/// </summary>
public int CurProTimeSum { get; set; }
/// <summary>
/// 当前程序段开始时间
/// </summary>
public DateTime CurProStartTime { set; get; }
#region
/// <summary>
/// 速度运行结束事件
/// </summary>
public event EventHandler<string> SpeedRunEndEvent;
/// <summary>
/// 是否启用
/// </summary>
public bool RunEnable { get; set; } = false;
/// <summary>
/// RunTimeCallSglEvent 是否注册关联方法
/// </summary>
public bool EventRegister { get; set; } = false;
/// <summary>
/// 当前步骤开始运行时间
/// </summary>
public DateTime StepStartDt { get; set; }
/// <summary>
/// 当前步骤结束运行时间
/// </summary>
public DateTime StepEndDt { get; set; }
/// <summary>
/// 循环扫描每个参数的结束时间是否到达的线程是一个线程循环执行,逐个执行,理论上每个都不同步,有一点点时间差,就是执行步骤会延后一点点
/// 我们的打点斜坡数据是自己的定时器执行的,每个参数同步执行,而且是有一点点提前执行的,因为先打点执行循环后再计算结束时间
/// 以上的就会造成,当新步骤到来执行时,其实之前的步骤的斜率打点的步骤可能已经打点完毕了,那么是乐见这样的状态,防止了两个的时间冲突问题。
/// </summary>
private DateTime _CurrentDateTime;
/// <summary>
/// 当前时间
/// </summary>
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;
}
}
}
/// <summary>
/// 获取拓展信息
/// 一般是速度有这些参数
/// </summary>
/// <returns></returns>
private List<StepExd> GetStepExds(ProStepExe proStepExe)
{
if (proStepExe.ListStepExd != null && proStepExe.ListStepExd.Count() > 0)
{
return proStepExe.ListStepExd;
}
return null;
}
/// <summary>
/// 当前步骤已经运行时长-秒
/// </summary>
public int RunTime { get; set; }
/// <summary>
/// 通过组合Pid和Limit组合出运行步骤类型
/// </summary>
/// <param name="pid">PID是否不同</param>
/// <param name="limit">Limit是否不同</param>
/// <param name="sv">SV是否不同</param>
/// <returns>运行步骤类型</returns>
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
/// <summary>
/// 开始打点执行
/// </summary>
private void StartSlopExStep()
{
//用SlopExEnable控制存在一个可能就是设置后立刻执行那么第一个步骤第一秒时间会被压缩后面就正常了。最大是快一秒了第一秒提前打点这是可以接受的
//结合步骤的扫描执行,形成时间差,就是新步骤到来时,斜率打点已经执行完毕,防止冲突,乐见这个状态
SlopExEnable = true;
}
/// <summary>
/// 停止执行打点任务的执行
/// </summary>
private void StopSlopExStep()
{
SlopExEnable = false;
//清空数据
ListSlopExStep.Clear();
}
/// <summary>
/// 斜率执行步骤集合
/// 打点集合
/// </summary>
public List<SlopExStep> ListSlopExStep { get; set; } = new List<SlopExStep>();
/// <summary>
/// 斜率执行循环周期
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
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}");
}
}
/// <summary>
/// 周期斜坡打点暂停执行
/// </summary>
public void PauseSlopExCyclePause()
{
SlopExEnable = false;
}
/// <summary>
/// 周期斜坡打点 继续运行
/// </summary>
public void ContinueSlopExCyclePause()
{
SlopExEnable = true;
}
/// <summary>
/// 周期定时器
/// </summary>
private System.Timers.Timer CycleTimer { get; set; }
/// <summary>
/// 步骤斜率执行使能状态
/// </summary>
public bool SlopExEnable { get; set; } = false;
#endregion
}
}