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; namespace FLY.Simulation.Blowing { [JsonObject(MemberSerialization.OptIn)] public class Blowing:INotifyPropertyChanged { #region 参数 #region 旋转架 /// <summary> /// 360° 连续旋转 /// </summary> [JsonProperty] public bool Is360 { get; set; } /// <summary> /// 正向旋转 /// </summary> public bool IsForw { get; set; } /// <summary> /// 旋转架复位中 /// </summary> public bool IsOrg { get; set; } /// <summary> /// 旋转架启动中 /// </summary> public bool IsRotationOn { get; set; } /// <summary> /// 牵引启动中 /// </summary> public bool IsTractionOn { get; set; } /// <summary> /// 1圈总脉冲 /// </summary> [JsonProperty] public double PosOfR { get; set; } /// <summary> /// 两个限位间的角度范围 != 旋转的总角度,限位值 ° /// </summary> [JsonProperty] public double AngleRange { get; set; } /// <summary> /// 复位信号角度范围° /// </summary> [JsonProperty] public double OrgAngleRange { get; set; } /// <summary> /// 旋转的速度, min/R 1R = 360° /// </summary> [JsonProperty] public double AngleVelocity { get; set; } /// <summary> /// 加速时间 单位 s /// </summary> [JsonProperty] public double AngleAccTime { get; set; } /// <summary> /// 减速时间 单位 s /// </summary> [JsonProperty] public double AngleDecTime { get; set; } /// <summary> /// 换方向冷却时间 单位s /// </summary> [JsonProperty] public double AngleCoolTime { get; set; } #endregion #region 膜 /// <summary> /// 膜泡压扁后总宽度,单位 脉冲 /// </summary> public double FilmWidth { get; set; } /// <summary> /// 膜走带速度, m/min /// </summary> [JsonProperty] public double FilmVelocity { get; set; } /// <summary> /// 人字架到测厚仪的距离 23m /// </summary> [JsonProperty] public double FilmDistance { get; set; } /// <summary> /// 辊轴承,单位mm /// </summary> [JsonProperty] public double RollPerimeter { get; set; } /// <summary> /// 测辊信号,信号长,单位mm /// </summary> [JsonProperty] public double RollSignLength { get; set; } #endregion #region 风环 /// <summary> /// 加热棒数量 /// </summary> [JsonProperty] public int ChannelCnt { get; set; } ///// <summary> ///// 加热功率, 1~100 ///// </summary> //public int[] Heats; /// <summary> /// 第1根加热棒对应角度 /// </summary> [JsonProperty] public double Channel1stAngle { get; set; } ///// <summary> ///// 温度影响厚度变化比例, 1厚度,对应多少加热 ///// </summary> //public int Factor;// #endregion public bool IsShieldI9 { get; set; } #endregion #region 动态数据 /// <summary> /// 当前人字架角度 ° /// </summary> public double CurrAngle { get; private set; } /// <summary> /// 全局角度 ° /// </summary> public double GlobalAngle { get; private set; } /// <summary> /// 当前速度,单位 °/s /// </summary> public double CurrAngleVelocity { get; private set; } /// <summary> /// 当前加速度, 单位 °/s^2 /// </summary> public double CurrAngleAccV { get; private set; } /// <summary> /// 当前已经走到长度, 通过编码器获取 单位 m /// </summary> public double CurrFilmLength { get; private set; } #endregion public HMI mHMI; public int Avg { get; set; } /// <summary> /// 原始数据 /// </summary> public ObservableCollection<int> BeforeDatas = new ObservableCollection<int>(); /// <summary> /// 系统扰动后 数据 /// </summary> public ObservableCollection<int> BeforeDatas2 = new ObservableCollection<int>(); /// <summary> /// 系统扰动后 再 加热数据 /// </summary> public ObservableCollection<int> AfterDatas = new ObservableCollection<int>(); class PositionAngleInfo { public DateTime dt; public double position; public double angle; } /// <summary> /// 10ms 1个记录点 /// </summary> List<PositionAngleInfo> mPositionAngleList = new List<PositionAngleInfo>(); 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 = 2.3;//膜宽2.3m 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]);//系统扰动后 再 加热数据 } 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<ObservableCollection<int>>(mAirRing2_AfterDatasUpdateEvent); Avg = (int)(BeforeDatas.Average());//原始平均值 TestPos = 500; UpdateTestHeat(); //AfterDatas.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(AfterDatas_CollectionChanged); PropertyChanged += new PropertyChangedEventHandler(Blowing_PropertyChanged); mHMI = new HMI(); mHMI.mAirRing = mAirRing2; Is360 = false; IsForw = true; IsOrg = false; IsRotationOn = true; IsTractionOn = true; } void mAirRing2_AfterDatasUpdateEvent(ObservableCollection<int> 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 { /// <summary> /// 加热通过 /// </summary> public int ChannelCnt; /// <summary> /// 加热量, 数据量为 ChannelCnt /// </summary> public int[] Heats; /// <summary> /// 第1个加热棒,对应的 数据序号, 数据量为 1000, BeforeData.Count /// </summary> public int Channel1stIndex; /// <summary> /// 加热 对 厚度的影响 /// </summary> public double Factor; //需要别人赋值 /// <summary> /// 原始数据!! 数据量为1000 /// </summary> public ObservableCollection<int> BeforeDatas; /// <summary> /// 加热后的数据!!!! 数据量为BeforeDatas.Count /// </summary> public ObservableCollection<int> 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<int> before_datas, ObservableCollection<int> after_datas, int channel1stIndex) { BeforeDatas = before_datas; AfterDatas = after_datas; Channel1stIndex = channel1stIndex; } public void SetChannelCnt(int channelcnt) { ChannelCnt = channelcnt; Heats = new int[ChannelCnt]; Array.Clear(Heats, 0, ChannelCnt); } public event Action<ObservableCollection<int>> AfterDatasUpdateEvent; public void HeatApply() { 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(); } } /// <summary> /// 1级加热, 模拟系统扰动 /// </summary> private AirRing mAirRing; /// <summary> /// 2级加热, 通过HMI 控制的加热 /// </summary> 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 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 position) { double ago_filmlength = CurrFilmLength - FilmDistance; double angle; //从膜位置列表找时间 if (!SearchAngleFromPositionList(ago_filmlength, out angle)) return null; return GetData(position, angle); } double GetValidAngle(double a) { while (a >= 360) a -= 360; while (a < 0) a += 360; return a; } /// <summary> /// 获取膜泡数据 /// </summary> /// <param name="position">相对于膜泡位置,横向</param> /// <param name="angle">人字架角度</param> /// <returns></returns> FilmData GetData(double position, double angle) { if ((position < 0) || (position > FilmWidth)) { return null; } //与0°的偏移 double a = position / 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; } /// <summary> /// 1.28ms 1次 /// </summary> public void OnPoll(DateTime now) { UpdatePositionAndAngle(now); } enum STATE { /// <summary> /// 加速 /// </summary> Acc, /// <summary> /// 减速 /// </summary> Dec, /// <summary> /// 恒速 /// </summary> Constant, /// <summary> /// 方向交换,冷却期 /// </summary> Wrap } STATE mState; DateTime mDTLast = DateTime.MinValue; /// <summary> /// 停下来的时间 /// </summary> DateTime mDTWrap = DateTime.MinValue; /// <summary> /// 更新旋转 /// </summary> /// <param name="now"></param> /// <param name="ts"></param> 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); } } } /// <summary> /// 正反向旋转 /// </summary> /// <param name="now"></param> /// <param name="ts"></param> 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; } /// <summary> /// 连续旋转 /// </summary> /// <param name="now"></param> /// <param name="ts"></param> 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; } /// <summary> /// 复位 /// </summary> /// <param name="now"></param> /// <param name="ts"></param> 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; } /// <summary> /// 驱动器,根据时间,更新angle, /// </summary> /// <param name="now"></param> 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; 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; //线速度。。。。 if(IsTractionOn)//启动牵引 CurrFilmLength += FilmVelocity * ts.TotalMinutes; UpdateAngleV(dt, ts); mPositionAngleList.Add( new PositionAngleInfo() { dt = mDTLast, angle = CurrAngle, position = CurrFilmLength }); 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 void Save() { string json = Newtonsoft.Json.JsonConvert.SerializeObject(this, Formatting.Indented); File.WriteAllText(param_path, json); } bool Load() { if (!File.Exists(param_path)) return false; try { string json = File.ReadAllText(param_path); Newtonsoft.Json.JsonConvert.PopulateObject(json, this); return true; } catch { return false; } } } public class FilmData { public double angle1; public double angle2; public int data1; public int data2; } }