using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.IO; using System.Xml.Serialization; using System.Collections.ObjectModel; using FObjBase; using FLY.FeedbackRenZiJia.OBJ_INTERFACE; using FLY.FeedbackRenZiJia.Common; using FLY.FeedbackRenZiJia.IService; using FLY.OBJComponents.Server; using FLY.OBJComponents.Common; using System.Net; using FLY.FeedbackRenZiJia.Server.Model; using PropertyChanged; using FLY.Thick.Blowing.IService; using AutoMapper; using Newtonsoft.Json; namespace FLY.FeedbackRenZiJia.Server { public class FeedbackHeat : IFeedbackHeatService { #region markno const int MARKNO_LOAD = 2; const int MARKNO_DELAY_ISCONNECTED = 3; #endregion #region IFeedbackHeat 成员 #region 参数 /// /// 作为客户端模式 /// public bool IsClient { get; set; } public IPEndPoint PLCep { get; set; } /// /// 自动控制使能 /// public bool IsAuto { get; set; } /// /// 加热后起效延时(s) /// public int Delay { get; set; } = 10; /// /// 检测功能使能 /// public bool HasCheck { get; set; } /// /// 手动加热步进 /// public int Step { get; set; } = 5; #endregion #region 动态数据 /// /// 通道数, 从测厚仪获取的!!!! /// public int ChannelCnt { get; protected set; } = 88; /// /// 分区数, 从测厚仪获取的!!!! /// public int NBolts { get; protected set; } = 88; /// /// 保存的加热策略名称 /// public string HeatsProductName { get; protected set; }="default"; /// 连接到PLC? /// public bool IsConnectedWithPLC { get; set; } /// /// 上一次加热修改时间 /// public DateTime LastChangedTime { get; set; } = DateTime.MinValue; /// /// 当前 /// //public int[] Currs { get; set; } #endregion #region 异常检测 /// /// 烧了的加热棒 /// [DoNotCheckEquality] public bool[] Bads { get; set; } /// /// 当前风机启动中 /// public bool HasFan { get; set; } /// /// 加热棒有电流 /// public bool HasElectricity { get; set; } /// /// 线速度 大于 3m/min /// public bool HasFilmVelocity { get; set; } /// /// 检测线速度 使能 /// public bool HasCheckFilmVelocity { get; set; } /// /// 当前正在检测的加热通道 /// public int CheckNo { get; set; } = -2; /// /// 正在检测中 /// public bool CheckEnable { get; set; } = false; /// /// 1块加热控制板 接多少路加热 /// public int HeatsOfGroup { get; set; } = 12; #endregion #endregion private IPLCLink plc; FLY.Thick.Blowing.IService.IBlowingService blowingService; FLY.Thick.Blowing.IService.IBulkDbService bulkDbService; private FLY.Thick.Blowing.IService.IBlowingDetectService bDetect; public HeatCell mHeatCell = new HeatCell(); HeatChanged mHeatChanged = new HeatChanged(); public HeatBuf mHeatBuf = new HeatBuf(); HeatCheck mHeatCheck = new HeatCheck(); HistoryDb mHistoryDb; /// /// 报警系统 /// FLY.OBJComponents.Server.WarningSystem2 mWarning; /// /// 加热记录文件夹 /// string heats_dir = "feedbackheats"; public FeedbackHeat() { PLCep = Misc.StringConverter.ToIPEndPoint("192.168.50.60:502"); Load(); if (IsClient) plc = new PLCLink(PLCep); else plc = new HMI(); plc.PropertyChanged += new PropertyChangedEventHandler(hmi_PropertyChanged); HasCheckFilmVelocity = plc.HasElectricity; HasFan = plc.HasFan; InitBuf(); //------------------------------------------------------------------- PropertyChanged += new PropertyChangedEventHandler(FeedbackHeat_PropertyChanged); //------------------------------------------------------------------- //HeatBuf //UpdateTotalDelay(); //------------------------------------------------------------------- //HeatCell mHeatCell.Init(mHeatBuf); mHeatCell.PreHeatApplyEvent = (c) => { //有开风机才能加热 if ((HasCheck) && (!HasFan)) return false;//返回false,不能加热 //检测时,也是使用 HeatApply 确定加热的,要把下面注解掉。所以只能寄望于客户不要按HeatApply //检测中,不能改变加热 //if (mHeatCheck.Enable) // return false; return true; }; mHeatCell.AfterHeatApplyEvent += (c) => { //检测中,不保存加热 if (mHeatCheck.Enable) return; //保存最后一次加热 SaveHeatsLast(); //记录改变!!!! mHeatChanged.Add(mHeatCell.Heats); //复位undo模块 resetUndo(); //记录到数据库 if (lastLcThickHeat != null) { HeatChanged.ITEM heat = mHeatChanged.mItem.Last(); lastLcThickHeat.HTime = heat.Time; lastLcThickHeat.Heats = heat.Heats; setLcThickHeatIsStable(lastLcThickHeat); mHistoryDb.AddThickHeat(lastLcThickHeat); } }; mHeatCell.AfterClearOffsetsEvent += (c) => { //复位undo模块 resetUndo(); }; mHeatCell.PropertyChanged += new PropertyChangedEventHandler(mHeatCell_PropertyChanged);//当HeatCell.Heats 修改后,设置到HMI Misc.BindingOperations.SetBinding(mHeatCell, nameof(mHeatCell.Kp), mHeatBuf, nameof(mHeatBuf.Kp), Misc.BindingOperations.BindingMode.TwoWay); if (!Directory.Exists(heats_dir)) Directory.CreateDirectory(heats_dir); //------------------------------------------------------------------- //HeatChanged Misc.BindingOperations.SetBinding(this.mHeatChanged, nameof(mHeatChanged.Last), this, nameof(LastChangedTime)); //------------------------------------------------------------------- //HeatCheck mHeatCheck.Init(mHeatCell); mHeatCheck.PropertyChanged += new PropertyChangedEventHandler(mHeatCheck_PropertyChanged); Misc.BindingOperations.SetBinding(this, nameof(HasElectricity), mHeatCheck, nameof(mHeatCheck.HasElectricity)); Misc.BindingOperations.SetBinding(this, nameof(HasCheck), mHeatCheck, nameof(mHeatCheck.Has)); Misc.BindingOperations.SetBinding(mHeatCheck, nameof(mHeatCheck.Enable), this, nameof(CheckEnable), Misc.BindingOperations.BindingMode.TwoWay); Misc.BindingOperations.SetBinding(mHeatCheck, nameof(mHeatCheck.CheckNo), this, nameof(CheckNo)); Misc.BindingOperations.SetBinding(mHeatCheck, nameof(mHeatCheck.Bads), this, nameof(Bads)); Misc.BindingOperations.SetBinding(this, nameof(HeatsOfGroup), mHeatCheck, nameof(mHeatCheck.HeatsOfGroup)); if (plc is PLCLink) { ((PLCLink)plc).Start(); } } void mHeatCheck_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(mHeatCheck.Enable)) { if (mHeatCheck.Enable) { if (!IsConnectedWithPLC) { //连接断开 mHeatCheck.Enable = false; } else if ((HasCheck) && (!HasFan)) { //风机没启动,要关闭加热 mHeatCheck.Enable = false; } else { //需要关闭自控 IsAuto = false; } } } } #region 异常 void InitError() { //-------------------------------------------------------------------------------- //连接断开事件 FObjBase.PollModule.Current.Poll_Config(PollModule.POLL_CONFIG.ADD, () => { Misc.BindingOperations.SetBinding(this, nameof(IsConnectedWithPLC), () => { bool b = !IsConnectedWithPLC; ERR_STATE state = b ? ERR_STATE.ON : ERR_STATE.OFF; ERRNO errno = ERRNOs.ERRNO_PLC_DISCONNECTED; mWarning.Add(errno.Code, errno.Descrption, state); }); }, TimeSpan.FromSeconds(3), true, false, this, MARKNO_DELAY_ISCONNECTED, true); //------------------------------------------------------------------------ //检测功能的结果 this.PropertyChanged += (s, e) => { if (e.PropertyName == "HasCheck") { if (!HasCheck) { ERRNO[] eRRNOs = new ERRNO[] { ERRNOs.ERRNO_NO_FAN, ERRNOs.ERRNO_OPEN_CIRCUIT, ERRNOs.ERRNO_SHORT_CIRCUIT, ERRNOs.ERRNO_POWER_OFF }; foreach (ERRNO eRRNO in eRRNOs) { mWarning.Add(eRRNO.Code, eRRNO.Descrption, ERR_STATE.OFF); } } return; } else if (e.PropertyName == "HasFan") { if (HasCheck) { ERR_STATE state = (!HasFan) ? ERR_STATE.ON : ERR_STATE.OFF; ERRNO errno = ERRNOs.ERRNO_NO_FAN; mWarning.Add(errno.Code, errno.Descrption, state); } } }; //------------------------------------------------------------------------ //检测功能的结果 Dictionary error_property = new Dictionary(); error_property.Add("OpenCircuit", ERRNOs.ERRNO_OPEN_CIRCUIT); error_property.Add("ShortCircuit", ERRNOs.ERRNO_SHORT_CIRCUIT); error_property.Add("PowerOff", ERRNOs.ERRNO_POWER_OFF); mHeatCheck.PropertyChanged += (s, e) => { if (HasCheck) { if (error_property.ContainsKey(e.PropertyName)) { bool b = (bool)Misc.PropertiesManager.GetValue(mHeatCheck, e.PropertyName); ERR_STATE state = b ? ERR_STATE.ON : ERR_STATE.OFF; ERRNO errno = error_property[e.PropertyName]; mWarning.Add(errno.Code, errno.Descrption, state); } } }; //------------------------------------------------------------------------ //线速度异常 this.PropertyChanged += (s, e) => { if (e.PropertyName == "HasCheckFilmVelocity") { if (!HasCheckFilmVelocity) { ERRNO eRRNO = ERRNOs.ERRNO_NO_VELOCITY; mWarning.Add(eRRNO.Code, eRRNO.Descrption, ERR_STATE.OFF); } } else if (e.PropertyName == "HasFilmVelocity") { if (HasCheckFilmVelocity) { ERR_STATE b = (!HasFilmVelocity) ? ERR_STATE.ON : ERR_STATE.OFF; ERRNO eRRNO = ERRNOs.ERRNO_NO_VELOCITY; mWarning.Add(eRRNO.Code, eRRNO.Descrption, b); } } }; } #endregion void InitBuf() { plc.ChannelCnt = (UInt16)ChannelCnt; //Currs = new int[ChannelCnt]; mHeatChanged.Init(ChannelCnt); mHeatBuf.Init(NBolts, ChannelCnt); mHeatCell.Init2(NBolts, ChannelCnt); //复位undo模块 resetUndo(); } void hmi_PropertyChanged(object s, PropertyChangedEventArgs _e) { FObjBase.PollModule.Current.Dispatcher.Invoke( new PropertyChangedEventHandler( (sender, e)=> { if (e.PropertyName == nameof(plc.Errno)) { if (plc.Errno == -1) { IsConnectedWithPLC = false; } else { IsConnectedWithPLC = true; //刚成功连接上 //重新复制数据到 PLC RecoverPLC(); } } else if (e.PropertyName == nameof(plc.HasElectricity)) { HasElectricity = plc.HasElectricity; } else if (e.PropertyName == nameof(plc.HasFan)) { HasFan = plc.HasFan; } }), s, _e); } void mHeatCell_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(mHeatCell.Heats)) { Output(mHeatCell.Heats.ToArray()); } } void FeedbackHeat_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ChannelCnt)) { //重新初始化!!!! InitBuf(); //滞后触发 PollModule.Current.Poll_JustOnce(new PollModule.PollHandler(delegate () { LoadHeatsLast(); }), this, MARKNO_LOAD); } else if (e.PropertyName == nameof(NBolts)) { //重新初始化!!!! InitBuf(); } else if (e.PropertyName == nameof(Delay)) { //重新计算总延时!!!! UpdateTotalDelay(); } else if (e.PropertyName == nameof(HasFan)) { if ((HasCheck) &&(!HasFan)) { //风机没开,停止一切动作。 StopAnyHeating(); } } else if (e.PropertyName == nameof(IsAuto)) { if (IsAuto) { //启动了自控,应该关闭检测 mHeatCheck.Enable = false; //复位自动对位状态 //mHeatCell.ResetAuto(); } } else if (e.PropertyName == nameof(IsConnectedWithPLC)) { if (!IsConnectedWithPLC) { mHeatCheck.Enable = false; IsAuto = false; } } } DateTime lastScanDataEndTime; public void Init( FLY.Thick.Blowing.IService.IBlowingService blowingService, FLY.Thick.Blowing.IService.IBlowingDetectService bDetect, FLY.Thick.Blowing.IService.IBulkDbService bulkDbService, FLY.OBJComponents.Server.WarningSystem2 warning, HistoryDb historyDb ) { this.blowingService = blowingService; this.bulkDbService = bulkDbService; this.bDetect = bDetect; mWarning = warning; mHistoryDb = historyDb; mHeatCell.Init3(blowingService); Misc.BindingOperations.SetBinding(blowingService, nameof(blowingService.NBolts), this, nameof(NBolts)); Misc.BindingOperations.SetBinding(blowingService, nameof(blowingService.ChannelCnt), this, nameof(ChannelCnt)); bulkDbService.PropertyChanged += BulkDbService_PropertyChanged; this.bDetect.PropertyChanged += MBDetect_PropertyChanged; mHistoryDb.GetFrame(0, (asyncContext, retData) => { var reponse = retData as GetFrameReponse; if (reponse.thickHeat != null) { lastScanDataEndTime = reponse.thickHeat.EndTime; } }, null); InitError(); } private void BulkDbService_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(bulkDbService.LastScanDataId)) { //有新数据,向bulkDb获取 bulkDbService.GetFrame( new Pack_GetFrameRequest() { Id = 0, Mix = 1 }, (asyncContext, retData) => { var reponse = retData as Pack_GetFrameReponse; if (reponse.scanData == null) return; var scanData = reponse.scanData; //读取数据库, if (lastScanDataEndTime == scanData.EndTime) { //这是以前的数据,不要 return; } lastScanDataEndTime = scanData.EndTime; Add( scanData.Time, scanData.EndTime, scanData.IsBackw, scanData.RPeriod, scanData.RCnt, scanData.OrgBoltNo, scanData.RAngle, scanData.FilmLength, scanData.FilmVelocity, scanData.K, scanData.Thicks, scanData.Boltmap ); }, this); } } private void MBDetect_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(bDetect.FilmLength)) { UpdateTotalDelay(); } else if (e.PropertyName == nameof(bDetect.FilmVelocity)) { UpdateTotalDelay(); HasFilmVelocity = (bDetect.FilmVelocity > 3);//大于3m/min 正常生产 //当线速度 很小,肯定是没生产,停止加热,停止自控 if (HasCheckFilmVelocity && (!HasFilmVelocity)) { StopAnyHeating(); //停止自控 IsAuto = false; } } } /// /// 停止任何加热行为 /// void StopAnyHeating() { //停止自控 IsAuto = false; //备份 加热策略 int[] preheats = mHeatCell.PreHeats.ToArray(); bool needrestore = true; //停止检测加热棒 if (mHeatCheck.Enable) { mHeatCheck.Enable = false; needrestore = false; } //停止加热 mHeatCell.ClearPreHeats(); mHeatCell.HeatApply(); //恢复 加热策略 if(needrestore) mHeatCell.ModifyPreHeats(preheats); } void UpdateTotalDelay() { if (bDetect == null) return; if (bDetect.FilmVelocity < 10) return; int totaldelay = (int)(Delay + bDetect.FilmLength / bDetect.FilmVelocity * 60); int t = (int)(mHeatBuf.Delay.TotalSeconds); if (Math.Abs(totaldelay - t) > 5) { mHeatBuf.Delay = TimeSpan.FromSeconds(totaldelay); } } UInt16[] last_heat; UInt16 last_heatupdate; void Output(int[] heats) { List list = new List(); for (int i = 0; i < ChannelCnt; i++) { UInt16 heat = (UInt16)heats[i]; if (heat < 0) heat = 0; else if (heat > 100) heat = 100; list.Add(heat); } plc.SetHeat(list); last_heat = list.ToArray(); UInt16 heatupdate = (UInt16)(plc.HeatUpdate_R + 1); if (heatupdate >100) heatupdate = 1; plc.HeatUpdate = heatupdate; last_heatupdate = heatupdate; } void RecoverPLC() { plc.ChannelCnt = (UInt16)ChannelCnt; if (last_heat != null) { plc.SetHeat(last_heat); plc.HeatUpdate = last_heatupdate; } else { //设置全部0进去 plc.SetHeat(new UInt16[ChannelCnt]); plc.HeatUpdate = plc.HeatUpdate; } } void setLcThickHeatIsStable(Lc_ThickHeat lcThickHeat) { DateTime time = lcThickHeat.Time; DateTime htime = lcThickHeat.HTime; double filmLength = lcThickHeat.FilmLength; double filmVelocity = lcThickHeat.FilmVelocity; if (filmVelocity < 2) { //速度太慢,没生产 lcThickHeat.IsStable = false; return; } //TODO if (lcThickHeat.RCnt < 1) { //刚开机,肯定不稳定 lcThickHeat.IsStable = false; return; } lcThickHeat.IsStable = time > (htime + TimeSpan.FromSeconds(Delay) + TimeSpan.FromMinutes(filmLength / filmVelocity)); } /// /// 最后一次保存的记录, /// 用于当加热改变时,保存记录到数据库 /// Lc_ThickHeat lastLcThickHeat=null; void Add(DateTime time, DateTime endtime, bool isBackw, TimeSpan period, int rotateCnt, int orgboltno, double rAngle, double filmLength, double filmVelocity, double K, double[] frame, List boltMaps) { //1.求平均值 //2.每个螺丝,加热量的增量 = (Thick - Avg) * Factor; //3.求出输出量, 输出值 = 当前值 + 增量 if (frame.Count() != NBolts) return; //检测数据有效性 int invalid_cnt = frame.Count( (_d) => {return double.IsNaN(_d);} ); if (invalid_cnt > NBolts / 2)//有一半数据都是无效的 return; if (filmVelocity < 2) { //速度太慢,异常 return; } if (filmLength < 1) { //膜距离太短,异常 return; } HeatChanged.ITEM heat = mHeatChanged.mItem.Last(); var lcThickHeat = new Lc_ThickHeat() { Time = time, EndTime = endtime, IsBackw = isBackw, RPeriod = period, RCnt = rotateCnt, OrgBoltNo = orgboltno, RAngle = rAngle, FilmLength = filmLength, FilmVelocity = filmVelocity, K = K, Thicks = frame, Boltmap = boltMaps, HTime = heat.Time, Heats = heat.Heats }; setLcThickHeatIsStable(lcThickHeat); mHistoryDb.AddThickHeat(lcThickHeat); lastLcThickHeat = lcThickHeat; //添加到缓冲区, 计算分区平移 if (!mHeatBuf.Add(lcThickHeat)) { //数据有问题,不需要在处理 return; } mHeatCell.SetThickPercents(mHeatBuf.ThickPercents); if (IsAuto)//如果自控中!!! { mHeatCell.Auto(); } } string file_path = "feedback.json"; bool Load() { if (File.Exists(file_path)) { try { string json = File.ReadAllText(file_path); var jsonDb = JsonConvert.DeserializeObject(json); FeedbackHeatJsonDb.Mapper.Map(jsonDb, this); return true; } catch { //异常,没有json 解码失败 } return false; } return false; } void Save() { try { var jsonDb = FeedbackHeatJsonDb.Mapper.Map(this); string json = JsonConvert.SerializeObject(jsonDb, Formatting.Indented); File.WriteAllText(file_path, json); } catch { //异常,没有json 编码失败 } } #region 撤销 /// /// undo 的序号 /// public int UndoIdx { get; private set; } /// /// 在撤销中 /// public bool IsInUndo { get; private set; } /// /// 这次撤销动作的时间 /// public DateTime UndoTime { get; private set; } //读取 UndoIdx 的 加热 public void Undo() { updateUodo(UndoIdx - 1); } public void Redo() { updateUodo(UndoIdx + 1); } void updateUodo(int idx) { if (idx < 0) return; else if (idx >= mHeatChanged.mItem.Count()) return; mHeatCell.ModifyPreHeats(mHeatChanged.mItem[idx].Heats); UndoTime = mHeatChanged.mItem[idx].Time; IsInUndo = idx <= mHeatChanged.mItem.Count() - 1; UndoIdx = idx; } void resetUndo() { UndoIdx = mHeatChanged.mItem.Count() - 1; UndoTime = DateTime.MinValue; IsInUndo = false; } #endregion #region IFeedbackHeat 成员 public void Apply() { Save(); } private void SaveHeatsLast() { string path = $@"{heats_dir}\最后记录.xml"; FeedbackHeatsMark mark = new FeedbackHeatsMark(); mark.heats = mHeatCell.Heats.ToArray(); mark.Save(path); } private void LoadHeatsLast() { string path = $@"{heats_dir}\最后记录.xml"; FeedbackHeatsMark mark = FeedbackHeatsMark.Load(path); if ((mark != null) && (mark.heats != null)) { mHeatCell.ModifyPreHeats(mark.heats); } } #region 加热配置读写 public void SaveHeats(string productname) { string path = $@"{heats_dir}\{productname}.xml"; HeatsProductName = productname; FeedbackHeatsMark mark = new FeedbackHeatsMark(); mark.heats = mHeatCell.Heats.ToArray(); mark.Save(path); } /// /// 获取保存的加热量文件列表 返回类型为 List<string> /// /// /// public void GetHeatsFileList(AsyncCBHandler asyncDelegate, object asyncContext) { DirectoryInfo dinfo = new DirectoryInfo(heats_dir); List filenames = new List(); if (dinfo.Exists) { foreach (FileInfo info in dinfo.GetFiles()) { if (Path.GetExtension(info.Name) == ".xml") { filenames.Add(Path.GetFileNameWithoutExtension(info.Name)); } } } asyncDelegate(asyncContext, filenames); } /// /// 删除 加热量文件 /// /// public void DelHeatsFile(string productname) { string path = $@"{heats_dir}\{productname}.xml"; if (System.IO.File.Exists(path)) System.IO.File.Delete(path); } /// /// 加载 加热量文件 /// /// public void LoadHeatsFile(string productname) { string path = $@"{heats_dir}\{productname}.xml"; HeatsProductName = productname; FeedbackHeatsMark mark = FeedbackHeatsMark.Load(path); if ((mark != null) && (mark.heats != null)) { mHeatCell.ModifyPreHeats(mark.heats); } } #endregion #endregion #region INotifyPropertyChanged 成员 public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname)); } } #endregion } public class FeedbackHeatJsonDb { public static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap() .ForMember(d => d.PLCep, opt => { opt.MapFrom(s => s.PLCep.ToString()); }) .ReverseMap() .ForMember(d => d.PLCep, opt => { opt.MapFrom(s => Misc.StringConverter.ToIPEndPoint(s.PLCep)); }); })); #region 参数 /// /// 作为客户端模式 /// public bool IsClient; public string PLCep; /// /// 加热后起效延时(s) /// public int Delay= 10; /// /// 检测功能使能 /// public bool HasCheck; /// /// 手动加热步进 /// public int Step = 5; /// 保存的加热策略名称 /// public string HeatsProductName = "default"; /// /// 检测线速度 使能 /// public bool HasCheckFilmVelocity; /// /// 1块加热控制板 接多少路加热 /// public int HeatsOfGroup = 12; #endregion } }