using System; using System.Collections.Generic; using System.Linq; using System.Text; using FLY.FeedbackRenZiJia.OBJ_INTERFACE; using System.Collections.ObjectModel; using System.ComponentModel; using FLY.FeedbackRenZiJia.Common; using FLY.FeedbackRenZiJia.IService; using PropertyChanged; using AutoMapper; using System.IO; using Newtonsoft.Json; using FLY.Thick.Blowing.IService; using Misc; namespace FLY.FeedbackRenZiJia.Server { /// <summary> /// 以前 正反转加热分离,所以有两个 HeatCell /// 现在不分正反转,只有一个HeatCell /// </summary> public class HeatCell : IHeatCellService { #region IHeatCell 接口 成员变量 #region 参数 /// <summary> /// HeatOffset = ThickPercent * Kp /// </summary> public double Kp { get; set; } = 3; /// <summary> /// 平滑的阀值,只有超出阀值,才平滑 /// </summary> public double ThresholdHeatSigma { get; set; } = 10; /// <summary> /// 自控时,不能调整总体上升下降 /// </summary> public bool IsForbidUpDown { get; set; } = false; /// <summary> /// 厚度控制线 , 厚度% 大于 厚度控制线 自动才会加热控制 /// </summary> public int CtrlLine { get; set; } = 2; /// <summary> /// 最大加热量 默认100% /// </summary> public int MaxHeat { get; set; } = 100; #endregion #region 状态 /// <summary> /// 对应的100% 数据,单位是% 数量是ChannelCnt, 数据肯定是完整的!!!!! /// </summary> [DoNotCheckEquality] public double[] ThickPercents { get; set; } /// <summary> /// 当前方向的加热策略,必须保证 0~100 /// </summary> [DoNotCheckEquality] public int[] Heats { get; protected set; } private int Base { get; set; } = 0; /// <summary> /// 加热偏移量 /// </summary> [DoNotCheckEquality] public int[] Offsets { get; protected set; } /// <summary> /// Heats+Offsets+Base /// </summary> [DoNotCheckEquality] public int[] PreHeats { get; protected set; } /// <summary> /// 加热量 每3个计算的sigma,最大值 /// </summary> public double MaxHeatSigma { get; set; } = 0; /// <summary> /// 当前正在自动对位模式 /// </summary> public bool IsInAutoONo { get; protected set; } #endregion #region 对位 /// <summary> /// 自动对位 /// </summary> public bool IsAutoONo { get; set; } = true; /// <summary> /// 当前正在自动对位模式 /// </summary> public AutoONoStatusEnum AutoONoStatus { get; private set; } /// <summary> /// 自动对位模式 信息 /// </summary> public string AutoONoMsg { get; private set; } /// <summary> /// 自动对位 那幅空的图 /// </summary> public long AutoONoId0 { get; private set; } = -1; /// <summary> /// 自动对位 加热后生效的图 /// </summary> public long AutoONoId1 { get; private set; } = -1; #endregion #endregion public int ChannelCnt { get; set; } int BoltCnt; HeatBuf mHeatBuf; IBlowingService blowingService; public Func<object,bool> PreHeatApplyEvent; public event Action<object> AfterHeatApplyEvent; public event Action<object> AfterClearOffsetsEvent; public HeatCell() { if (!Load()) Save(); checkParamErr(); Init2(88, 88); } void checkParamErr() { if (Kp < 1 || Kp > 10) { Kp = 3; } if (ThresholdHeatSigma < 1 || ThresholdHeatSigma > 50) { ThresholdHeatSigma = 10; } if (CtrlLine < 0 || CtrlLine > 10) { CtrlLine = 2; } } public void Init(HeatBuf heatBuf) { mHeatBuf = heatBuf; } public void Init3(IBlowingService blowingService) { this.blowingService = blowingService; this.blowingService.PropertyChanged += BlowingService_PropertyChanged; } private void BlowingService_PropertyChanged(object sender, PropertyChangedEventArgs e) { //当复位区号改变了,同时也修改heatCell 的 thickPercents if (e.PropertyName == nameof(this.blowingService.OrgBoltNo)) { if (mHeatBuf.MoveThickPercents(this.blowingService.OrgBoltNo)) SetThickPercents(mHeatBuf.ThickPercents); } } public void Init2(int boltCnt, int channelCnt) { ChannelCnt = channelCnt; BoltCnt = boltCnt; Base = 0; MaxHeatSigma = 0; ThickPercents = new double[ChannelCnt]; Heats = new int[ChannelCnt]; PreHeats = new int[ChannelCnt]; Offsets = new int[ChannelCnt]; ResetAuto(); } /// <summary> /// 复位自动状态 /// </summary> public void ResetAuto() { AutoONoStatus = AutoONoStatusEnum.Init; AutoONoMsg = "等待 加热=0,厚度稳定"; AutoONoId0 = -1; AutoONoId1 = 0; } void checkResetAuto() { if (mHeatBuf.Stability != STABILITY.OK_CORREL) return; if (Heats.All(h => h == 0)) { //加热全部为0 //复位状态 ResetAuto(); } } void AutoONoOnPoll() { checkResetAuto(); switch (AutoONoStatus) { case AutoONoStatusEnum.Init: { if (mHeatBuf.Stability != STABILITY.OK_CORREL) break; if (Heats.All(h => h == 0)) { //判断当前加热全部为空, 且稳定性大于>0.7 //转为 加热棒数量 double[] thickPercents = ThickPercents; double autoONoKp = Kp; if (autoONoKp < 1) autoONoKp = 1; int[] offsets = new int[ChannelCnt]; while (true) { for (int i = 0; i < ChannelCnt; i++) { double percent = thickPercents[i]; offsets[i] = (int)Math.Round(percent * autoONoKp); } int[] heatPercents = offsets;//放大数量为分区量 heatPercents = Common.MyMath.Filter(heatPercents, mHeatBuf.HeatEffectCurve);//平滑 int maxMin = Common.MyMath.CalMaxMin(heatPercents); if (maxMin >= mHeatBuf.ThresholdMaxMin) { AutoONoStatus = AutoONoStatusEnum.Step2; AutoONoMsg = "厚度已经稳定,对位加热,等待结果"; AutoONoId0 = mHeatBuf.mData.Last().thickHeat.ID; AutoONoId1 = -1; break; } else { if (maxMin < 2) { //肯定是厚度太好 AutoONoStatus = AutoONoStatusEnum.Fail; AutoONoMsg = "失败,加热变化量太小,转到正常模式"; AutoONoId0 = -1; AutoONoId1 = -1; return; } //加热变化不够多, 变大Kp double k = autoONoKp * mHeatBuf.ThresholdMaxMin / maxMin - autoONoKp; if (k < 0.5) k = 0.5; autoONoKp += k; } } Offsets = offsets; UpdatePreHeats();//这句一定要加!!!! 不然没法平滑,没法加热 //上面已经有平滑,不需要再执行 Smooth() ModifyBase(); HeatApply(); } else { //虽然稳定了,但已经被人加热了,无法启动对位模式 AutoONoStatus = AutoONoStatusEnum.Fail; AutoONoMsg = "加热不为0,无法启动对位模式"; return; } } break; case AutoONoStatusEnum.Step2: { //不需要等 STABILITY.OK_CORREL if (mHeatBuf.MaxR_Id1 == mHeatBuf.mData.Last().thickHeat.ID)//算出结果 { string msg = "对位完成"; msg += $",[相关性]{mHeatBuf.MaxR:F2}"; msg += $",[极差]{mHeatBuf.MaxMin}%"; if (blowingService.OrgBoltNo != mHeatBuf.BestOrgBoltNo) { //调节 mHeatBuf.ThickPercents 移动到 mHeatBuf.BestOrgBoltNo //使mHeatBuf状态为稳定 mHeatBuf.SetToBestThickPercents(); SetThickPercents(mHeatBuf.ThickPercents); msg += $",[复位号]{blowingService.OrgBoltNo}->{mHeatBuf.BestOrgBoltNo}"; blowingService.OrgBoltNo = mHeatBuf.BestOrgBoltNo; blowingService.Apply(); } else { msg += $",[复位号]正确"; } if (Math.Abs(Kp - mHeatBuf.BestKp) > 0.2) { msg += $",[Kp]{Kp:F1}->{mHeatBuf.BestKp:F1}"; Kp = mHeatBuf.BestKp; Apply(); } else { msg += $",[Kp]正确"; } //对位完成 AutoONoStatus = AutoONoStatusEnum.Success; AutoONoMsg = msg; AutoONoId0 = mHeatBuf.MaxR_Id0; AutoONoId1 = mHeatBuf.MaxR_Id1; return; } else { if (AutoONoId0 < mHeatBuf.mData.First().thickHeat.ID) { //失败,对位过程有大波动, 全部数据被清除? AutoONoStatus = AutoONoStatusEnum.Fail; AutoONoMsg = "失败,对位过程有大波动"; return; } //数据稳定了,且加热变化量为0 if (mHeatBuf.Stability == STABILITY.OK_CORREL && mHeatBuf.LastHeatDiffMaxMin == 0) { //数据稳定了, 但没有出结果!!!!! AutoONoStatus = AutoONoStatusEnum.Fail; AutoONoMsg = "失败,对位加热相关性不够"; return; } } } break; } } /// <summary> /// 自动模式!!!!! /// </summary> public void Auto() { if (mHeatBuf.mData.Count() == 0) return; //&& mHeatBuf.IsStable() if (IsAutoONo) { AutoONoOnPoll(); } if ( (!IsAutoONo) || ((IsAutoONo) && (AutoONoStatus == AutoONoStatusEnum.Fail || AutoONoStatus == AutoONoStatusEnum.Success)) ) { if (mHeatBuf.Stability == STABILITY.OK_CORREL)//稳定 与 待定时 加热 { //转为 加热棒数量 var thickPercents = ThickPercents; int[] offsets = new int[ChannelCnt]; for (int i = 0; i < ChannelCnt; i++) { if (mHeatBuf.BoltIsStable[i] && //稳定 (Math.Abs(thickPercents[i]) > CtrlLine))//在控制线已外 { offsets[i] = (int)Math.Round(thickPercents[i] * Kp); } } Offsets = offsets; UpdatePreHeats();//这句一定要加!!!! 不然没法平滑,没法加热 if (!IsForbidUpDown)//禁止整体上移下降 ModifyBase();//自动整体上移下降 Smooth();//平滑,它会导致Offset被修改,<0的都被删除掉 HeatApply();//执行加热 } } } public void SetThickPercents(double[] thkpercents) { thkpercents = MakeThickPercentsFull(thkpercents); for (int i = 0; i < thkpercents.Count(); i++) thkpercents[i] = Math.Round(thkpercents[i], 2); ThickPercents = thkpercents; } #region IHeatCell 接口 成员函数 /// <summary> /// 把空的数据补齐 /// </summary> /// <param name="thicks"></param> double[] MakeThickPercentsFull(double[] _thkpercents) { var thkpercents = _thkpercents.ToArray(); List<Misc.Range> emptys = new List<Misc.Range>(); int start = -1;//double.NaN 的段开始 int end = -1;//double.NaN 的段结束 //变成数列 int thks_cnt = thkpercents.Count(); for (int i = 0; i < thks_cnt; i++) { if (double.IsNaN(thkpercents[i])) { if (start == -1) { start = i; } end = i; } else { if (start != -1) { emptys.Add(new Misc.Range() { Begin = start, End = end }); start = -1; } } } if (start != -1)//添加最后一个 { emptys.Add(new Misc.Range() { Begin = start, End = end }); } if (emptys.Count() > 0) { int e = emptys.Last().End; int b = emptys.First().Begin; if ((b == 0) && e == (thks_cnt - 1)) { //这是同一个 emptys.Last().End = emptys.First().End; emptys.RemoveAt(0); } for (int i = 0; i < emptys.Count(); i++) { Misc.Range r = emptys[i]; int idx = r.Begin - 1; if (idx < 0) idx += thks_cnt; double b_thk = thkpercents[idx]; idx = r.End + 1; if (idx >= thks_cnt) idx -= thks_cnt; double e_thk = thkpercents[idx]; //这个很重要, 当 结尾与开头重合, r.End < r.Begin, r.Width 会<0 int len = r.Width; if (len < 0) len += thks_cnt; for (int j = 0; j < len; j++) { idx = r.Begin + j; if (idx >= thks_cnt) idx -= thks_cnt; thkpercents[idx] = b_thk + (j + 1) * (e_thk - b_thk) / (len + 1); } } } return thkpercents; } /// <summary> /// 手动调整加热增量 /// </summary> /// <param name="idx"></param> /// <param name="change"></param> public void ModifyOffset(int idx, int change) { if (idx < ChannelCnt && idx >= 0) { Offsets[idx] += change; UpdatePreHeats(); } NotifyPropertyChanged(nameof(Offsets)); } /// <summary> /// 参数是 baseheatoffset 的增量 /// </summary> /// <param name="change"></param> public void ModifyBase(int change) { Base += change; UpdatePreHeats(); } /// <summary> /// 调整BaseHeatOffset,令Heats 在范围内 /// </summary> void ModifyBase() { int[] preheat = new int[ChannelCnt]; for (int i = 0; i < ChannelCnt; i++) preheat[i] = Heats[i] + Offsets[i] + Base; int min = preheat.Min(); int max = preheat.Max(); int avg = (int)preheat.Average(); int base_offset; int maxHeat = MaxHeat; if ((min < 0) && (max > maxHeat)) { //功率极限了 //保存平均值为50 base_offset = avg - maxHeat / 2; } else if (max > maxHeat) { //整体往下降 base_offset = max - maxHeat; if (base_offset > (avg - maxHeat / 2))//移得太多 base_offset = (avg - maxHeat / 2); } else { base_offset = min; if (base_offset < 0) { if (base_offset < (avg - maxHeat / 2))//升得太多 base_offset = (avg - maxHeat / 2); } } Base -= base_offset; UpdatePreHeats(); } /// <summary> /// 清空HeatOffset /// </summary> public void ClearOffsets() { Base = 0; for (int i = 0; i < ChannelCnt; i++) { Offsets[i] = 0; } NotifyPropertyChanged(nameof(Offsets)); UpdatePreHeats(); AfterClearOffsetsEvent?.Invoke(this); } /// <summary> /// 清空Heats /// </summary> public void ClearPreHeats() { int[] heats = new int[ChannelCnt]; Array.Clear(heats, 0, ChannelCnt); ModifyPreHeats(heats); } /// <summary> /// 调整PreHeats /// </summary> /// <param name="heats"></param> public void ModifyPreHeats(int[] heats) { if (heats.Count() != ChannelCnt) return; Base = 0; for (int i = 0; i < ChannelCnt; i++) { PreHeats[i] = heats[i]; Offsets[i] = PreHeats[i] - Heats[i]; } NotifyPropertyChanged(nameof(Offsets)); NotifyPropertyChanged(nameof(PreHeats)); UpdateHeatSigmas(); } public void ModifyHeats(int[] heats) { if (heats.Count() != ChannelCnt) return; bool isChanged = false; Base = 0; for (int i = 0; i < ChannelCnt; i++) { if (Heats[i] != heats[i]) { isChanged = true; Heats[i] = heats[i]; Offsets[i] = PreHeats[i] - Heats[i]; } } if (isChanged) { NotifyPropertyChanged(nameof(Offsets)); NotifyPropertyChanged(nameof(Heats)); } } /// <summary> /// 利用测厚仪的数据,计算出HeatOffsets /// </summary> public void Cal() { var thickpercents = ThickPercents;// Common.MyMath.ZoomOut(ThickPercents, BoltCnt / ChannelCnt); int[] offsets = new int[ChannelCnt]; for (int i = 0; i < ChannelCnt; i++) { offsets[i] = (int)Math.Round(thickpercents[i] * Kp); } Offsets = offsets; UpdatePreHeats(); } #region PreHeats 平滑处理 double[] GetHeatSigmas(int[] heats) { var heatsigmas = new double[ChannelCnt]; for (int i = 0; i < ChannelCnt; i++) { var dat = getSubArray(heats, i); heatsigmas[i] = dat.Sigma(); } return heatsigmas; } void UpdateHeatSigmas() { var heatsigmas = GetHeatSigmas(PreHeats); MaxHeatSigma = heatsigmas.Max(); } T[] getSubArray<T>(T[] array, int index) { T[] dat = new T[3]; for (int j = 0; j < 3; j++) { int idx = index - 1 + j; if (idx < 0) idx += array.Count(); else if (idx >= array.Count()) idx -= array.Count(); dat[j] = array[idx]; } return dat; } /// <summary> /// 平滑,不断修改PreHeats 直到HeatsSigma 合格 /// </summary> public void Smooth() { int[] heats = PreHeats.ToArray(); int[] heats2 = new int[ChannelCnt]; bool hasChanged = false; bool hasChanged2 = false; double[] heatsigmas; do { //1.更新sigma heatsigmas = GetHeatSigmas(heats); //2.检测HeatsSigma, 哪里比较大,对该位置 滤波 for (int i = 0; i < heats.Count(); i++) { if (heatsigmas[i] > (ThresholdHeatSigma)) //不行,要滤波!!! { //连续3个滤波 int[] dat = getSubArray(heats, i); heats2[i] = (int)Math.Round(dat.Average()); //if (heats2[i] == heats[i]) //{ // //居然一样,修改旁边的两个的值 // if (i > 0) { // int idx = i-1; // dat = getSubArray(heats, idx); // heats2[idx] = (int)Math.Round(dat.Average()); // } // if (i == heats.Count() - 1) // { // int idx = 0; // dat = getSubArray(heats, idx); // heats2[idx] = (int)Math.Round(dat.Average()); // } //} } else { heats2[i] = heats[i]; } } //3.有滤波行为,继续求循环,求sigma,判断。。。。。 hasChanged = false; for (int i = 0; i < heats.Count(); i++) { if (heats[i]!=heats2[i]) { heats[i] = heats2[i]; hasChanged = true; } } if(hasChanged) hasChanged2 = true; } while (hasChanged); if (hasChanged2) { for (int i = 0; i < ChannelCnt; i++) { PreHeats[i] = heats[i]; Offsets[i] = PreHeats[i] - Heats[i] - Base; } NotifyPropertyChanged(nameof(PreHeats)); NotifyPropertyChanged(nameof(Offsets)); MaxHeatSigma = heatsigmas.Max(); } } #endregion /// <summary> /// 加热输出应用 /// </summary> public void HeatApply() { if (PreHeatApplyEvent != null) { if (!PreHeatApplyEvent(this)) { return; } } for (int i = 0; i < ChannelCnt; i++) { Offsets[i] = 0; int heat = PreHeats[i]; if (heat < 0) { //需要整体提升 heat = 0; } else if (heat > 100) { //需要整体调低 heat = 100; } Heats[i] = heat; } NotifyPropertyChanged("Offsets"); NotifyPropertyChanged("Heats"); Base = 0; AfterHeatApplyEvent?.Invoke(this); } /// <summary> /// 修改Offsets, Base 后,执行PreHeats = Heats + Offsets + Base /// </summary> void UpdatePreHeats() { for (int i = 0; i < ChannelCnt; i++) { int preheat = Heats[i] + Offsets[i] + Base; if (preheat > 100) preheat = 100; else if (preheat < 0) preheat = 0; PreHeats[i] = preheat; } UpdateHeatSigmas(); NotifyPropertyChanged(nameof(PreHeats)); } public void Apply() { Save(); } #endregion string file_path = "heatcell.json"; bool Load() { if (File.Exists(file_path)) { try { string json = File.ReadAllText(file_path); var jsonDb = JsonConvert.DeserializeObject<HeatCellJsonDb>(json); HeatCellJsonDb.Mapper.Map(jsonDb, this); return true; } catch { //异常,没有json 解码失败 } return false; } return false; } void Save() { try { var jsonDb = HeatCellJsonDb.Mapper.Map<HeatCellJsonDb>(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 HeatCellJsonDb { public static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap<HeatCell, HeatCellJsonDb>().ReverseMap(); })); #region 参数 /// <summary> /// HeatOffset = ThickPercent * Kp /// </summary> public double Kp= 3; /// <summary> /// 自动对位 /// </summary> public bool IsAutoONo = true; /// <summary> /// 最大加热量 /// </summary> public int MaxHeat = 100; /// <summary> /// 平滑的阀值,只有超出阀值,才平滑 /// </summary> public double ThresholdHeatSigma= 10; /// <summary> /// 自控时,不能调整总体上升下降 /// </summary> public bool IsForbidUpDown= false; /// <summary> /// 厚度控制线 , 厚度% 大于 厚度控制线 自动才会加热控制 /// </summary> public int CtrlLine= 2; #endregion } }