using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Collections.ObjectModel; using System.IO; using Newtonsoft.Json; using AutoMapper; namespace FLY.Simulation.Blowing { public class Blowing:INotifyPropertyChanged { #region 参数 #region 旋转架 /// /// 360° 连续旋转 /// public bool Is360 { get; set; } /// /// 正向旋转 /// public bool IsForw { get; set; } /// /// 旋转架复位中 /// public bool IsOrg { get; set; } /// /// 旋转架启动中 /// public bool IsRotationOn { get; set; } /// /// 牵引启动中 /// public bool IsTractionOn { get; set; } /// /// 1圈总脉冲 /// public double PosOfR { get; set; } /// /// 两个限位间的角度范围 != 旋转的总角度,限位值 ° /// public double AngleRange { get; set; } /// /// 复位信号角度范围° /// public double OrgAngleRange { get; set; } /// /// 旋转的速度, min/R 1R = 360° /// public double AngleVelocity { get; set; } /// /// 加速时间 单位 s /// public double AngleAccTime { get; set; } /// /// 减速时间 单位 s /// public double AngleDecTime { get; set; } /// /// 换方向冷却时间 单位s /// public double AngleCoolTime { get; set; } #endregion #region 膜 /// /// 膜泡压扁后总宽度,单位 mm /// public double FilmWidth { get; set; } /// /// 膜走带速度, m/min /// public double FilmVelocity { get; set; } /// /// 人字架到测厚仪的距离 23m /// public double FilmDistance { get; set; } /// /// FilmDistance+mFilmLength3D[CurrAngle] /// public double CurrFilmDistance { get; set; } public bool Is3D { get; set; } /// /// 辊轴承,单位mm /// [JsonProperty] public double RollPerimeter { get; set; } /// /// 测辊信号,信号长,单位mm /// public double RollSignLength { get; set; } #endregion #region 风环 /// /// 加热棒数量 /// public int ChannelCnt { get; set; } /// /// 第1根加热棒对应角度 /// public double Channel1stAngle { get; set; } ///// ///// 温度影响厚度变化比例, 1厚度,对应多少加热 ///// //public int Factor;// #endregion public bool IsShieldI9 { get; set; } #endregion #region 动态数据 /// /// 当前人字架角度 ° /// public double CurrAngle { get; private set; } /// /// 全局角度 ° /// public double GlobalAngle { get; private set; } /// /// 当前速度,单位 °/s /// public double CurrAngleVelocity { get; private set; } /// /// 当前加速度, 单位 °/s^2 /// public double CurrAngleAccV { get; private set; } /// /// 当前已经走到长度, 通过编码器获取 单位 m /// public double CurrFilmLength { get; private set; } /// /// 当前线速度 m/min /// public double CurrFilmVelocity { get; private set; } #endregion public IPlcLink plcLink; public int Avg { get; set; } public double[] mFilmLength3D; double lastFilmDistance; /// /// 原始数据 /// public ObservableCollection BeforeDatas = new ObservableCollection(); /// /// 系统扰动后 数据 /// public ObservableCollection BeforeDatas2 = new ObservableCollection(); /// /// 系统扰动后 再 加热数据 /// public ObservableCollection AfterDatas = new ObservableCollection(); class PositionAngleInfo { /// /// 时间点 /// public DateTime dt; /// /// 测厚仪所在位置 已经跑了的膜长度位置 /// public double position; /// /// 这个时刻的角度 /// public double angle; /// /// 膜距离,应该找为N米前 PositionAngleInfo.angle 得出厚度信息 /// public double filmDistance; /// /// position+filmDistance /// public double pos24m => position + filmDistance; } /// /// 10ms 1个记录点 /// List mPositionAngleList = new List(); int ListCap = 30000;//300s public Blowing() { //撞了限位后,限位信号一直在。。。。 PosOfR = 50000; OrgAngleRange = 5;//复位信号角度° AngleRange = 350;//两个限位间的角度范围350° AngleVelocity = 6;//出限位 到 撞另一个限位的时间 8min/ 360° AngleAccTime = 1;//加速时间1s AngleDecTime = 5;//减速时间5s AngleCoolTime = 5;//正反转延迟时间5s FilmWidth = 1500;//膜宽1500mm FilmVelocity = 20;//线速度m/min FilmDistance = 26;//膜距离m RollPerimeter = 314;//辊周长mm RollSignLength = 30;//辊信号长度mm ChannelCnt = 88;//加热棒数量 Channel1stAngle = 100;//第1根加热棒对应角度 if (!Load()) Save(); //整个膜片1000个数据 int[] datas = OrgData.GetData(1000); for(int i=0;i<1000;i++) { BeforeDatas.Add(datas[i]);//原始数据 BeforeDatas2.Add(datas[i]);//系统扰动后数据 AfterDatas.Add(datas[i]);//系统扰动后 再 加热数据 } //3d膜距离 mFilmLength3D = FilmLength3D.GetData(); mAirRing = new AirRing(ChannelCnt);//88支加热棒 mAirRing.Init(BeforeDatas, BeforeDatas2, 0); mAirRing2 = new AirRing(ChannelCnt);//88支加热棒; mAirRing2.Init(BeforeDatas2, AfterDatas, (int)(1000*Channel1stAngle/360));//加热偏移了 mAirRing2.AfterDatasUpdateEvent += new Action>(mAirRing2_AfterDatasUpdateEvent); Avg = (int)(BeforeDatas.Average());//原始平均值 mAirRing2.Bads[2] = true; mAirRing2.Bads[30] = true; mAirRing2.Bads[31] = true; TestPos = 500; UpdateTestHeat(); //AfterDatas.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(AfterDatas_CollectionChanged); PropertyChanged += new PropertyChangedEventHandler(Blowing_PropertyChanged); //plcLink = new HMI(); //plcLink.Init(mAirRing2); var plc = new PlcLink(); plc.Init(mAirRing2); plcLink = plc; Is360 = false; IsForw = true; IsOrg = false; IsRotationOn = true; IsTractionOn = true; } void mAirRing2_AfterDatasUpdateEvent(ObservableCollection obj) { NotifyPropertyChanged(nameof(AfterDatas)); } public int TestPos { get; set; } public int TestHeat { get; set; } public void Test() { int idx = mAirRing.ChannelCnt * TestPos / BeforeDatas.Count(); int offset = TestHeat - mAirRing.Heats[idx]; mAirRing.Test(idx, offset); mAirRing2.HeatApply(); } public class AirRing { /// /// 加热通过 /// public int ChannelCnt; /// /// 加热量, 数据量为 ChannelCnt /// public int[] Heats; /// /// 坏的加热棒 /// public bool[] Bads; public bool HasElectricCurrent; /// /// 第1个加热棒,对应的 数据序号, 数据量为 1000, BeforeData.Count /// public int Channel1stIndex; /// /// 加热 对 厚度的影响 /// public double Factor; //需要别人赋值 /// /// 原始数据!! 数据量为1000 /// public ObservableCollection BeforeDatas; /// /// 加热后的数据!!!! 数据量为BeforeDatas.Count /// public ObservableCollection AfterDatas; double[] p;// 一个凸起来的数组 0 1 4 9 16 9 4 1 0 void p_init() { int size = ChannelCnt / 10; p = new double[size]; for (int i = 0; i < size; i++) { int index1 = i; int index2 = size - 1 - i; double d = i * i; p[index1] = d; p[index2] = d; if (Math.Abs(index2-index1)<=1) break; } double sum_p = p.Max(); for (int i = 0; i < size; i++) { p[i] = p[i] / sum_p; } } public AirRing(int channelcnt) { SetChannelCnt(channelcnt); p_init(); Factor = 5; } public void Init(ObservableCollection before_datas, ObservableCollection after_datas, int channel1stIndex) { BeforeDatas = before_datas; AfterDatas = after_datas; Channel1stIndex = channel1stIndex; } public void SetChannelCnt(int channelcnt) { ChannelCnt = channelcnt; Heats = new int[ChannelCnt]; Bads = new bool[ChannelCnt]; Array.Clear(Heats, 0, ChannelCnt); } public event Action> AfterDatasUpdateEvent; public void HeatApply() { bool current = false; for (int i = 0; i < ChannelCnt; i++) { if ((Bads[i] == false) && (Heats[i] > 0)) { current = true; break; } } HasElectricCurrent = current; int boltcnt = BeforeDatas.Count(); double b_c = (double)(boltcnt)/ChannelCnt; int b = Channel1stIndex; double beforeavg = BeforeDatas.Average(); int[] datas = new int[boltcnt]; for (int i = 0; i < ChannelCnt; i++) { int heat = Heats[i]; int e = (int)((i+1) * b_c)+ Channel1stIndex; if (e >= boltcnt) e -= boltcnt; else if (e < 0) e += boltcnt; int j=b; while (j != e) { datas[j] = (int)(BeforeDatas[j] - beforeavg * heat / Factor/100); j++; if (j >= boltcnt) j -= boltcnt; } b = e; } double afteravg = datas.Average(); for (int j = 0; j < boltcnt; j++) { AfterDatas[j] = (int)(beforeavg * datas[j] / afteravg); } if (AfterDatasUpdateEvent != null) { AfterDatasUpdateEvent(AfterDatas); } } public void Test(int idx,int offset) { idx -= p.Count() / 2; if (idx < 0) idx += ChannelCnt; else if (idx >= ChannelCnt) idx -= ChannelCnt; for (int i = 0; i < p.Count(); i++) { int index = idx+i; if (index < 0) index += ChannelCnt; else if (index>= ChannelCnt) index -= ChannelCnt; int heat = Heats[index] + (int)(offset * p[i]); if (heat > 100) heat = 100; else if (heat < 0) heat = 0; Heats[index] = heat; } HeatApply(); } } /// /// 1级加热, 模拟系统扰动 /// private AirRing mAirRing; /// /// 2级加热, 通过HMI 控制的加热 /// public AirRing mAirRing2; void Blowing_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Avg)) { int avg = (int)(BeforeDatas.Average()); int offset = Avg - avg; if (offset != 0) { for (int i = 0; i < BeforeDatas.Count(); i++) { BeforeDatas[i] += offset; } } } else if (e.PropertyName == nameof(TestPos)) { UpdateTestHeat(); } } void UpdateTestHeat() { int idx = mAirRing.ChannelCnt * TestPos / BeforeDatas.Count(); TestHeat = mAirRing.Heats[idx]; } /// /// 找到一个时间点,它的位置+膜距离=输入的纵向位置, 返回对应的时间点的角度 /// /// /// /// bool SearchAngleFromPositionList24m(double position, out double angle) { for (int i = 1; i < mPositionAngleList.Count; i++) { int idx1 = i; int idx0 = i - 1; double pos24m1 = mPositionAngleList[idx1].pos24m; double pos24m0 = mPositionAngleList[idx0].pos24m; double angle1 = mPositionAngleList[idx1].angle; double angle0 = mPositionAngleList[idx0].angle; if (pos24m1 >= position ) { //刚过了,就是它与上一个之间的值 double percent = (position - pos24m0) / (pos24m1 - pos24m0); if (percent < 0) { angle = 0; return true; } //找到了 angle = (angle1 - angle0) * percent + angle0; return true; } } angle = 0; return true; } /// /// 通过纵向位置,查找角度 /// /// /// /// bool SearchAngleFromPositionList(double position, out double angle) { for (int i = 1; i < mPositionAngleList.Count; i++) { int idx1 = i; int idx2 = i - 1; if ((mPositionAngleList[idx1].position >= position)) { double percent = (position - mPositionAngleList[idx2].position) / (mPositionAngleList[idx1].position - mPositionAngleList[idx2].position); if (percent < 0) { angle = 0; return true; } //找到了 angle = (mPositionAngleList[idx1].angle - mPositionAngleList[idx2].angle) * percent + mPositionAngleList[idx2].angle; return true; } } angle = 0; return true; } /// /// 给定相对于膜横向位置, 输出当前对应的厚度 /// /// /// public FilmData GetData(double pos_mm) { //double ago_filmlength = CurrFilmLength - CurrFilmDistance; double angle; if (!SearchAngleFromPositionList24m(CurrFilmLength, out angle)) return null; //从膜位置列表找时间 //if (!SearchAngleFromPositionList(ago_filmlength, out angle)) // return null; return GetData(pos_mm, angle); } double GetValidAngle(double a) { while (a >= 360) a -= 360; while (a < 0) a += 360; return a; } /// /// 获取膜泡数据 /// /// 相对于膜泡位置,横向 /// 人字架角度 /// FilmData GetData(double position_mm, double angle) { if ((position_mm < 0) || (position_mm > FilmWidth)) { return null; } //与0°的偏移 double a = position_mm / FilmWidth * 180; //压扁对面的角度 double a_other = 360 - a; a = GetValidAngle( a + angle ); a_other = GetValidAngle(a_other + angle); int dat_idx1 = (int)(a / 360 * AfterDatas.Count()); int dat_idx2 = (int)(a_other / 360 * AfterDatas.Count()); FilmData fd = new FilmData() { angle1 = a, angle2 = a_other, data1 = AfterDatas[dat_idx1], data2 = AfterDatas[dat_idx2] }; return fd; } /// /// 1.28ms 1次 /// public void OnPoll(DateTime now) { UpdatePositionAndAngle(now); } enum STATE { /// /// 加速 /// Acc, /// /// 减速 /// Dec, /// /// 恒速 /// Constant, /// /// 方向交换,冷却期 /// Wrap } STATE mState; DateTime mDTLast = DateTime.MinValue; /// /// 停下来的时间 /// DateTime mDTWrap = DateTime.MinValue; /// /// 更新旋转 /// /// /// void UpdateAngleV(DateTime now, TimeSpan ts) { if (IsOrg) { IsRotationOn = false; UpdateAngleV_Org(now, ts); return; } if (IsRotationOn) { if (Is360) { UpdateAngleV_360(now, ts); } else { UpdateAngleV_ForwBackw(now, ts); } } } /// /// 正反向旋转 /// /// /// void UpdateAngleV_ForwBackw(DateTime now, TimeSpan ts) { double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds; switch (mState) { case STATE.Constant: if (((CurrAngleVelocity > 0) && (angle >= AngleRange / 2)) || ((CurrAngleVelocity < 0) && (angle <= (-AngleRange / 2)))) { mState = STATE.Dec; double dv = 0 - CurrAngleVelocity; CurrAngleAccV = dv / AngleDecTime; } break; case STATE.Dec: { bool isForw = false; if (CurrAngleVelocity > 0) isForw = true; CurrAngleVelocity += CurrAngleAccV * ts.TotalSeconds; if (((isForw) && (CurrAngleVelocity < 0)) || ((!isForw) && (CurrAngleVelocity > 0))) { CurrAngleVelocity = 0; mState = STATE.Wrap; mDTWrap = now; } } break; case STATE.Wrap: { if ((now - mDTWrap).TotalSeconds >= AngleCoolTime) { //冷静完 mState = STATE.Acc; double anglev = 360.0 / (AngleVelocity * 60); if (angle >= AngleRange / 2) { //反转 anglev = -anglev; IsForw = false; } else { IsForw = true; } CurrAngleAccV = anglev / AngleAccTime; } } break; case STATE.Acc: { CurrAngleVelocity += CurrAngleAccV * ts.TotalSeconds; double anglev = 360.0 / (AngleVelocity * 60); if (CurrAngleAccV > 0) { if (CurrAngleVelocity >= anglev) { CurrAngleVelocity = anglev; mState = STATE.Constant; } } else { if (CurrAngleVelocity <= -anglev) { CurrAngleVelocity = -anglev; mState = STATE.Constant; } } } break; } double delta = angle - CurrAngle; GlobalAngle += delta; CurrAngle = angle; } /// /// 连续旋转 /// /// /// void UpdateAngleV_360(DateTime now, TimeSpan ts) { double anglev = 360.0 / (AngleVelocity * 60); if (IsForw) CurrAngleVelocity = anglev; else CurrAngleVelocity = -anglev; double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds; double delta = angle - CurrAngle; GlobalAngle += delta; while (angle >= 360) angle -= 360; while (angle < 0) angle +=360; CurrAngle = angle; } /// /// 复位 /// /// /// void UpdateAngleV_Org(DateTime now, TimeSpan ts) { double anglev = 360.0 / (AngleVelocity * 60); if (CurrAngle > 0){ CurrAngleVelocity = -anglev; } else if (CurrAngle < 0) { CurrAngleVelocity = anglev; } else { CurrAngleVelocity = 0; IsOrg = false; return; } double angle = CurrAngle + CurrAngleVelocity * ts.TotalSeconds; double delta = angle - CurrAngle; if (((CurrAngle > 0) && (angle <= 0))|| ((CurrAngle < 0) && (angle >= 0))) { delta = 0 - CurrAngle; CurrAngle = 0; CurrAngleVelocity = 0; IsOrg = false; return; } GlobalAngle += delta; CurrAngle = angle; } /// /// 驱动器,根据时间,更新angle, /// /// void UpdatePositionAndAngle(DateTime now) { if (mDTLast == DateTime.MinValue) { //提前把膜距离耗完,不用等 double position = FilmDistance; DateTime time = now; while (position > 0) { mPositionAngleList.Insert(0, new PositionAngleInfo() { dt = time, position = position, angle = 0 }); TimeSpan ts1 = TimeSpan.FromMilliseconds(10); time -= ts1; position -= FilmVelocity * ts1.TotalMinutes; } mDTLast = now; CurrFilmLength = FilmDistance; CurrFilmDistance = FilmDistance; lastFilmDistance = CurrFilmDistance; CurrAngle = 0;//在中间 mState = STATE.Constant; double anglev = 360.0 / (AngleVelocity * 60); CurrAngleVelocity = anglev; return; } DateTime dt = now; TimeSpan ts = dt.Subtract(mDTLast); if (ts.TotalMilliseconds < 10) return; mDTLast = dt; //线速度。。。。 UpdateAngleV(dt, ts); if (Is3D && !Is360)//立体旋转架,肯定不是360°连续旋转的 { double index1_5 = CurrAngle + 180; //提高精度。 线性插值 int index1 = (int)Math.Floor(index1_5); int index2 = (int)Math.Ceiling(index1_5); while (index1 < 0) index1 = 0; while (index1 >= 360) index1 = 360 - 1; while (index2 < 0) index2 = 0; while (index2 >= 360) index2 = 360 - 1; double filmLength3d; if (index2 == index1) filmLength3d = mFilmLength3D[index1]; else if (index1 <= index1_5 && index1_5 <= index2) { filmLength3d = (index1_5-index1)/(index2- index1) * (mFilmLength3D[index2]- mFilmLength3D[index1])+ mFilmLength3D[index1]; } else { filmLength3d = mFilmLength3D[index1]; } CurrFilmDistance = FilmDistance + filmLength3d; } if (IsTractionOn)//启动牵引 { //膜距离增量 膜长了,2牵引速度要降 double d = CurrFilmDistance - lastFilmDistance; lastFilmDistance = CurrFilmDistance; //2牵引速度 double fv2 = FilmVelocity - d / ts.TotalMinutes; CurrFilmVelocity = fv2; CurrFilmLength += CurrFilmVelocity * ts.TotalMinutes; } mPositionAngleList.Add( new PositionAngleInfo() { dt = mDTLast, angle = CurrAngle, position = CurrFilmLength, filmDistance = CurrFilmDistance }); if (mPositionAngleList.Count > ListCap) mPositionAngleList.RemoveAt(0); } public bool CheckOrg() { if (CurrAngle <= OrgAngleRange / 2 && CurrAngle >= -OrgAngleRange / 2) return true; else return false; } public bool CheckLimit1() { if (Is360) { if (CurrAngleVelocity < 0) return true; else return false; } else { if (CurrAngle >= AngleRange / 2) return true; else return false; } } public bool CheckLimit0() { if (Is360) { if (CurrAngleVelocity > 0) return true; else return false; } else { if (CurrAngle <= (-AngleRange / 2)) return true; else return false; } } public bool CheckRoll() { int rollcnt = (int)(CurrFilmLength*1000 / RollPerimeter); double p = CurrFilmLength*1000 - rollcnt * RollPerimeter; if (p <= RollSignLength) return true; else return false; } protected void NotifyPropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname)); } } public event PropertyChangedEventHandler PropertyChanged; private string param_path = "simulation_blowing.json"; public bool Save() { return JsonDbHelper.Save(this, param_path); } bool Load() { return JsonDbHelper.Load(this, param_path); } } public class FilmData { public double angle1; public double angle2; public int data1; public int data2; } public class BlowingJsonDb { #region 参数 #region 旋转架 /// /// 360° 连续旋转 /// public bool Is360 { get; set; } /// /// 1圈总脉冲 /// public double PosOfR { get; set; } /// /// 两个限位间的角度范围 != 旋转的总角度,限位值 ° /// public double AngleRange { get; set; } /// /// 复位信号角度范围° /// public double OrgAngleRange { get; set; } /// /// 旋转的速度, min/R 1R = 360° /// public double AngleVelocity { get; set; } /// /// 加速时间 单位 s /// public double AngleAccTime { get; set; } /// /// 减速时间 单位 s /// public double AngleDecTime { get; set; } /// /// 换方向冷却时间 单位s /// public double AngleCoolTime { get; set; } #endregion #region 膜 /// /// 膜走带速度, m/min /// public double FilmVelocity { get; set; } /// /// 人字架到测厚仪的距离 23m /// public double FilmDistance { get; set; } /// /// 立体收卷 /// public bool Is3D { get; set; } /// /// 辊轴承,单位mm /// public double RollPerimeter { get; set; } /// /// 测辊信号,信号长,单位mm /// public double RollSignLength { get; set; } #endregion #region 风环 /// /// 加热棒数量 /// public int ChannelCnt { get; set; } /// /// 第1根加热棒对应角度 /// public double Channel1stAngle { get; set; } #endregion #endregion } public static class JsonDbHelper { static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap().ReverseMap(); })); public static bool Save(TSource src, string filePath) { try { var jsondb = Mapper.Map(src); string json = Newtonsoft.Json.JsonConvert.SerializeObject(jsondb, Formatting.Indented); File.WriteAllText(filePath, json); return true; } catch { return false; } } public static bool Load(TSource src, string filePath) { if (!File.Exists(filePath)) return false; try { string json = File.ReadAllText(filePath); var jsondb = Newtonsoft.Json.JsonConvert.DeserializeObject(json); Mapper.Map(jsondb, src); return true; } catch { return false; } } } }