using AutoMapper; using FLY.FeedbackRenZiJia.Common; using FLY.FeedbackRenZiJia.IService; using FLY.FeedbackRenZiJia.Server.Model; using FLY.OBJComponents.Server.Model; using FObjBase; using Misc; using Newtonsoft.Json; using PropertyChanged; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; namespace FLY.FeedbackRenZiJia.Server { /// <summary> /// 存放一堆厚度与加热数据。 /// 1.用于判断加热是否稳定了; /// 2.加热偏移; /// 3.加热/厚度 /// </summary> public class HeatBuf : IHeatBufService { #region 参数 [DoNotCheckEquality] /// <summary> /// 加热生效曲线,允许为空,则不生效 /// </summary> public int[] HeatEffectCurve { get; set; } = new int[] { 3, 4, 5, 6, 5, 4, 3 }; /// <summary> /// 厚度稳定范围 %, 偏差在范围内,都是稳定的 /// </summary> public int StableRange { get; set; } = 2; /// <summary> /// 对位模式, 加热与厚度相关性阀值, 相关性 >=0.7 /// </summary> public double ThresholdR { get; set; } = 0.7; /// <summary> /// 对位模式 加热%差 的极差 >= 20% 才能触发 单位% /// </summary> public int ThresholdMaxMin { get; set; } = 40; /// <summary> /// % /// 最大sigma, 只有当前的sigma 在 最大sigma 与 最小 sigma 内,才能控制 /// </summary> public int ThresholdSigmaMax { get; set; } = 25; #endregion #region 状态 /// <summary> /// 总延时 =Delay+mRenZiJiaService.FilmLength/mRenZiJiaService.Velocity /// </summary> public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(100); #region 对位 /// <summary> /// 对位查找范围 默认 ±120°, 最大值 180°,最小值 50° /// </summary> public int SearchAngleRange { get; set; } = 120; /// <summary> /// 计算的最佳复位区号 /// </summary> public int BestOrgBoltNo { get; set; } = -1; /// <summary> /// 计算的最佳复位区号 对应的 加热/厚度 /// </summary> public double BestKp { get; set; } = -1; /// <summary> /// 计算的最佳复位区号 对应的 加热与厚度相关性 /// </summary> public double MaxR { get; set; } = -1; /// <summary> /// 计算的最佳复位区号 对应的 加热%变化极差 单位% /// </summary> public int MaxMin { get; set; } = -1; /// <summary> /// MaxR 对应的 数据幅 Id0 /// </summary> public long MaxR_Id0 { get; private set; } = -1; /// <summary> /// MaxR 对应的 数据幅 Id1 /// </summary> public long MaxR_Id1 { get; private set; } = -1; /// <summary> /// MaxR 对应的 时间 /// </summary> public DateTime MaxR_Time { get; private set; } #endregion #region 稳定性 /// <summary> /// 当前检测出来的厚度数据 稳定状态 /// </summary> public STABILITY Stability { get; protected set; } = STABILITY.IDLE; /// <summary> /// 当前检测出来的厚度数据 的稳定性 /// </summary> public double CurrR { get; set; } = -1; /// <summary> /// 当前检测出来的厚度数据 2Sigma /// </summary> public double Curr2Sigma { get; set; } = -1; /// <summary> /// 当前检测出来的厚度数据,时间 /// </summary> public DateTime CurrTime { get; set; } = DateTime.MinValue; #region 每个分区状态 [DoNotCheckEquality] public bool[] BoltIsStable { get; private set; } #endregion #endregion #endregion /// <summary> /// HeatCell 的 Kp /// </summary> public double Kp { get; set; } = 3; /// <summary> /// 厚度数量 /// </summary> int BoltCnt = 88; /// <summary> /// 加热数量 /// </summary> int ChannelCnt = 88; /// <summary> /// 缓存区大小 /// </summary> int BufSize = 60; public List<FlyData_FeedbackHeat> mData; /// <summary> /// 最后一幅数据,单位 channelcnt, 可以有double.NaN /// </summary> public double[] ThickPercents=null; /// <summary> /// 最后一幅数据的复位区号 /// </summary> public int OrgBoltNo = 1; /// <summary> /// 最后一幅加热偏差变化量 /// </summary> public int LastHeatDiffMaxMin = 0; static HeatBuf() { } public HeatBuf() { mData = new List<FlyData_FeedbackHeat>(); if (!Load()) Save(); checkParamErr(); } void checkParamErr() { if (StableRange < 1 || StableRange > 10) { StableRange = 4; } if (ThresholdR < 0.1 || ThresholdR > 0.9) { ThresholdR = 0.6; } if (ThresholdMaxMin < 10 || ThresholdMaxMin > 90) { ThresholdMaxMin = 40; } } public void SetToBestThickPercents() { MoveThickPercents(BestOrgBoltNo); //ThickPercents = Common.MyMath.Move(ThickPercents, (BestOrgBoltNo - OrgBoltNo)* ChannelCnt/BoltCnt); //OrgBoltNo = BestOrgBoltNo; Stability = STABILITY.OK_CORREL; bool[] isStables = new bool[ChannelCnt]; for (int i = 0; i < isStables.Count(); i++) isStables[i] = true; CurrR = MaxR; BoltIsStable = isStables; } public bool MoveThickPercents(int orgBoltNo) { if (ThickPercents != null && OrgBoltNo!=orgBoltNo) { ThickPercents = Common.MyMath.Move(ThickPercents, (orgBoltNo - OrgBoltNo) * ChannelCnt / BoltCnt); OrgBoltNo = orgBoltNo; return true; } return false; } public void Init(int boltCnt, int channelCnt) { BoltCnt = boltCnt; ChannelCnt = channelCnt; mData.Clear(); } public bool IsStable() { if ((int)Stability > 0) return true; else return false; } public bool IsUnstable() { if ((int)Stability < 0) return true; else return false; } /// <summary> /// 1.添加数据到缓冲; /// 2.判断是否稳定 /// 3.根据稳定变化,加热变化,计算偏移 /// </summary> public bool Add(Lc_ThickHeat thickHeat) { //thicks 的数据量一定要是 BoltCnt //heats 的数据量一定要是 ChannelCnt //不是返回 false if (thickHeat.Thicks.Count() != BoltCnt)//异常 return false; if (thickHeat.Heats.Count() != ChannelCnt)//异常 return false; //转为 加热棒数量 var thks = Common.MyMath.ZoomOut(thickHeat.Thicks, BoltCnt / ChannelCnt); var thkpercents = Common.MyMath.GetPercent(thks); //加热%偏差 var heatPercents = Common.MyMath.OffsetAvgBe0(thickHeat.Heats.Select(da=>(double)da).ToArray());//平移到平均值为0 var heatEffectCurve = HeatEffectCurve == null ? null : HeatEffectCurve.Select(da => (double)da); heatPercents = Common.MyMath.Filter(heatPercents, heatEffectCurve); var thkavg = thickHeat.Thicks.AverageNoNull(); CurrTime = thickHeat.Time; ThickPercents = thkpercents; OrgBoltNo = thickHeat.OrgBoltNo; Curr2Sigma = thkpercents.Sigma(); if (!thickHeat.IsStable) { //这幅图还没生效,没有用,不放进mData //需要更新状态 Stability = STABILITY.ERROR_NO_ARRIVE; CurrR = -1; BoltIsStable = new bool[ChannelCnt]; return true; } if (Curr2Sigma > ThresholdSigmaMax) { //这幅图太差,无效,不放进mData //需要更新状态 Stability = STABILITY.ERROR_SIGMA_OVERSIZE; CurrR = -1; BoltIsStable = new bool[ChannelCnt]; return true; } FlyData_FeedbackHeat d = new FlyData_FeedbackHeat() { //thickHeat = thickHeat, Stability = STABILITY.IDLE, ThickAvg = thkavg,//肯定有数据 ThickPercents = thkpercents, HeatPercents = heatPercents }; string json = Newtonsoft.Json.JsonConvert.SerializeObject(thickHeat); d.thickHeat = Newtonsoft.Json.JsonConvert.DeserializeObject<Lc_ThickHeat>(json); //更新每个分区的稳定状态 UpdateStable3(d); return true; } void DataAdd(FlyData_FeedbackHeat d) { if (mData.Count == BufSize) { mData.RemoveAt(0); } mData.Add(d); return; } /// <summary> /// 更新稳定状态,需要2幅数据才能比较的; /// 返回 true:添加进 mData /// </summary> /// <param name="d"></param> /// <returns></returns> void UpdateStable3(FlyData_FeedbackHeat d) { if (mData.Count() == 0)//没有以前的数据 { Stability = STABILITY.IDLE; goto _error; } var d_last = mData.Last(); if (d.thickHeat.RAngle != d_last.thickHeat.RAngle) { //旋转角度不同,无法比较 Stability = STABILITY.ERROR_PARAM_CHANGED; goto _error; } if (d.thickHeat.FilmLength != d_last.thickHeat.FilmLength) { //膜距离不用,无法比较 Stability = STABILITY.ERROR_PARAM_CHANGED; goto _error; } if (d.ThickAvg <= 0) { //厚度为负数异常 Stability = STABILITY.ERROR_THICK_CHANGED; goto _error; } double thk_diff = Math.Abs((double)(d_last.ThickAvg - d.ThickAvg)); if (thk_diff > 10) { if (thk_diff / d.ThickAvg > 0.15) { //厚度变化15%, 不能比较 Stability = STABILITY.ERROR_THICK_CHANGED; goto _error; } } //获取 厚度%偏差,与 加热%偏差,大小都为 ChannelCnt //thickpercents_diff = d.ThickPercents - d_last.ThickPercents //heatpercents_diff = d_last.ThickPercents - d.ThickPercents GetPercentDatas3(d, d_last, out double[] thickpercents_diff, out double[] heatpercents_diff); //通过简单的范围比较计算稳定性 //当Kp, 错位,就会另 稳定性的值很低 CalR(thickpercents_diff, heatpercents_diff, out bool[] isStables); double currR = 1.0 * isStables.Count(b => b) / isStables.Count(); CurrR = currR; BoltIsStable = isStables; LastHeatDiffMaxMin = (int)Common.MyMath.CalMaxMin(heatpercents_diff); if (CurrR >= ThresholdR) { Stability = STABILITY.OK_CORREL; } else { Stability = STABILITY.ERROR_CORREL; } //通过平移 达到最大 加热厚度相关性 double maxOffsetHeatThickR = -1; long maxOffsetHeatThickR_id = -1; double maxOffsetHeatThickR_maxMin = -1; int maxOffsetHeatThickR_orgBoltNo = -1; double maxOffsetHeatThickR_kp = -1; DateTime last_h_t = DateTime.MinValue; //从尾向前找 for (int i = 0; i < mData.Count(); i++) { int index = mData.Count() - 1 - i; d_last = mData[index]; //超过2个小时的数据删除 if (d.thickHeat.Time - d_last.thickHeat.Time > TimeSpan.FromMinutes(120)) { mData.RemoveRange(0, index); //因为是从尾到前找, 所以这个超时的,已经是最前一个,可以结束了 break; } if (last_h_t != DateTime.MinValue) { if (d_last.thickHeat.HTime == last_h_t) { //认为加热一样的,厚度也是一样,不比较了 //加热一样,只用最新的那幅数据 continue; } } last_h_t = d_last.thickHeat.HTime; GetPercentDatas3(d, d_last, out thickpercents_diff, out heatpercents_diff); //加热极差 判断可信度用 double maxMin = Common.MyMath.CalMaxMin(heatpercents_diff); if (maxMin >= ThresholdMaxMin) { //通过平移找最大相关性 double range_percent = 1.0 * SearchAngleRange / 360; if (range_percent < 0.1 || range_percent > 0.5) range_percent = 0.3; //计算平移 Common.MyMath.CalBestMove(heatpercents_diff, thickpercents_diff, range_percent, out int offset, out double heatThickR); if (heatThickR > maxOffsetHeatThickR) { maxOffsetHeatThickR = heatThickR; maxOffsetHeatThickR_orgBoltNo = OrgBoltNoOffset(d.thickHeat.OrgBoltNo, offset * BoltCnt / ChannelCnt); maxOffsetHeatThickR_maxMin = maxMin; maxOffsetHeatThickR_id = d_last.thickHeat.ID; var thickpercents_diff2 = Common.MyMath.Move(thickpercents_diff, offset); //当前数据的加热与厚度 比例 double b; double a; Misc.MyMath.Linest(heatpercents_diff, thickpercents_diff2, out a, out b); maxOffsetHeatThickR_kp = a; } } } if (maxOffsetHeatThickR != -1) { if (maxOffsetHeatThickR >= ThresholdR) { BestOrgBoltNo = maxOffsetHeatThickR_orgBoltNo; BestKp = Math.Round(maxOffsetHeatThickR_kp, 1); MaxR = Math.Round(maxOffsetHeatThickR, 2); MaxMin = (int)Math.Round(maxOffsetHeatThickR_maxMin, 0); MaxR_Id0 = maxOffsetHeatThickR_id; MaxR_Id1 = d.thickHeat.ID; MaxR_Time = DateTime.Now; } } DataAdd(d); return; _error: CurrR = -1; BoltIsStable = new bool[ChannelCnt]; //删除前面的 mData.Clear(); DataAdd(d); } int OrgBoltNoOffset(int orgBoltNo, int offset) { orgBoltNo += offset; if (orgBoltNo > BoltCnt) orgBoltNo -= BoltCnt; else if (orgBoltNo < 1) orgBoltNo += BoltCnt; return orgBoltNo; } void GetPercentDatas3( FlyData_FeedbackHeat d, FlyData_FeedbackHeat d_last, out double[] thickpercents_diff, out double[] heatpercents_diff) { //厚度%偏差 double[] thickpercents_last; //当两幅图复位区号不一致时,移动以前的数据,使之一致 if (d.thickHeat.OrgBoltNo != d_last.thickHeat.OrgBoltNo) { var thks_last = Common.MyMath.Move(d_last.thickHeat.Thicks, d.thickHeat.OrgBoltNo - d_last.thickHeat.OrgBoltNo); //转为 加热棒数量 thks_last = Common.MyMath.ZoomOut(thks_last, BoltCnt / ChannelCnt); thickpercents_last = Common.MyMath.GetPercent(thks_last); } else { thickpercents_last = d_last.ThickPercents; } thickpercents_diff = Common.MyMath.GetDiff(d.ThickPercents, thickpercents_last); //加热%偏差 heatpercents_diff = Common.MyMath.GetDiff(d_last.HeatPercents, d.HeatPercents);//-加热增量, 它是反转的!!!! } void CalR(double[] thickpercents_diff, double[] heatpercents_diff, out bool[] isStable) { //降低要求 //thickpercents_diff = Common.MyMath.Filter(thickpercents_diff, 3); //heatpercents_diff = Common.MyMath.Filter(heatpercents_diff, 3); double kp = Kp; if (kp < 1) kp = 1; int stableRange = StableRange; if (stableRange < 1) stableRange = 1; isStable = new bool[heatpercents_diff.Count()]; for (int i = 0; i < heatpercents_diff.Count(); i++) { if ((!double.IsNaN(thickpercents_diff[i])) && (Math.Abs(thickpercents_diff[i] - heatpercents_diff[i] / kp) <= stableRange)) { //稳定!!!!! isStable[i] = true; } } } public void Apply() { Save(); } string file_path = "heatbuf.json"; bool Load() { if (File.Exists(file_path)) { try { string json = File.ReadAllText(file_path); var jsonDb = JsonConvert.DeserializeObject<HeatBufJsonDb>(json); HeatBufJsonDb.Mapper.Map(jsonDb, this); return true; } catch { //异常,没有json 解码失败 } return false; } return false; } void Save() { try { var jsonDb = HeatBufJsonDb.Mapper.Map<HeatBufJsonDb>(this); string json = JsonConvert.SerializeObject(jsonDb, Formatting.Indented); File.WriteAllText(file_path, json); } catch { //异常,没有json 编码失败 } } #region INotifyPropertyChanged 成员 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname)); } } #endregion } public class HeatBufJsonDb { public static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap<HeatBuf, HeatBufJsonDb>().ReverseMap(); })); #region 参数 /// <summary> /// 对位查找范围 默认 ±120°, 最大值 180°,最小值 50° /// </summary> public int SearchAngleRange = 120; /// <summary> /// 加热生效曲线,允许为空,则不生效 /// </summary> public int[] HeatEffectCurve; /// <summary> /// 厚度稳定范围 %, 偏差在范围内,都是稳定的 /// </summary> public int StableRange= 4; /// <summary> /// 对位模式, 加热与厚度相关性阀值, 相关性 >=0.7 /// </summary> public double ThresholdR = 0.7; /// <summary> /// 对位模式 厚度%差 的极差 >= +4%-4%=8%, 单位% /// </summary> public int ThresholdMaxMin = 20; /// <summary> /// % /// 最大sigma, 只有当前的sigma 在 最大sigma 与 最小 sigma 内,才能控制 /// </summary> public int ThresholdSigmaMax = 20; #endregion } }