using AutoMapper; using FLY.Thick.Blowing; using FLY.Thick.Blowing.IService; using FLY.Thick.Blowing360.IService; using FlyADBase; using FObjBase; using Misc; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FLY.Thick.Blowing360.Server { /// /// 1.每次收到限位信号(有->没 and 没->有),都会触发 FilmInfoChangedEvent ;时间为上一次限位信号 /// 2.改变辊周长,膜长,都会触发 FilmInfoChangedEvent ;时间为前3次限位信号 /// 3.每次查找FilmInfo, 时间比 当前的限位信号+RenZiJiaPeriod 还要未来,修正 RenZiJiaPeriod, /// RenZiJiaPeriod预留10s的余量,避免不断修正 /// 并且触发FilmInfoChangedEvent 时间为上一次限位信号 /// public class BlowingDetect360 : IBlowingDetectInServer, IBlowingDetectService, INotifyPropertyChanged { #region 输入口定义 /// /// 旋转架转一圈的定位信号, 正向 /// const int Limit0_bit = 9; /// /// 旋转架转一圈的定位信号, 反向 /// const int Limit1_bit = 10; /// /// 辊信号 对应输入位 /// const int Roll_bit = 11; #endregion #region 无效接口 /// /// 离开限位 到 撞下一个限位 的 旋转架转动总角度 单位° /// [Obsolete("360连续旋转 此接口无效")] public double RAngle { get { return 360; } set { } } /// /// 信号0 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 /// 当为2次撞时: StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime /// [Obsolete("360连续旋转 此接口无效")] public bool IsSign0Double { get; set; } /// /// 信号1 撞2次。 这个能自动设置。 两个撞同一信号 间隔30秒内,算一个换向信号。 /// 当为2次撞时: StartTime = 第1次撞的StartTime, EndTime = 第2次撞的EndTime /// [Obsolete("360连续旋转 此接口无效")] public bool IsSign1Double { get; set; } /// /// 信号模式, 短信号模式(只撞一下),长信号模式(只要是转着,信号就长亮,直到换向) /// [Obsolete("360连续旋转 此接口无效")] public BlowingSignType SignType { get; set; } = BlowingSignType.Short; /// /// 撞限位 到 离开限位 的时间, 需要初始值,以后测量出来的 /// [Obsolete("360连续旋转 此接口无效")] public TimeSpan LimitSignTime { get; set; } = TimeSpan.FromSeconds(4); /// /// 转向信号100ms 滤波 /// [Obsolete("360连续旋转 此接口无效")] public bool IsSignFilter { get; set; } /// /// 缺少信号模式 /// 正反信号线接得不好,有时候会丢失了。 /// 当前的旋转时间是上一次的1.1倍时,在以前的位置增加信号!!!! /// 当有连续两次相同的信号,且信号差刚好是旋转时间的1.9~2.1倍时,证明真的是缺少信号 /// [Obsolete("360连续旋转 此接口无效")] public bool IsLackSignMode { get; set; } /// /// 旋转架为立式,测厚仪安装在二牵引前面,膜距离 与 旋转角度 一起变化, 旋转角度越大 膜距离越大 /// [Obsolete("360连续旋转 此接口无效")] public bool Is3D { get; set; } /// /// 使用编码器检测旋转架旋转位置模式 /// public bool IsRPosMode { get; set; } /// /// 人字架 离开限位 到 撞下一个限位 的 旋转架转动总脉冲 单位 脉冲 /// public int RPosOfR { get; set; } = 50000; /// /// 刚开机,信号列表一个信号都没有,创造一个时间点,作为信号点 /// public DateTime DefaultTime { get; set; } #endregion #region 参数 /// /// 人字架 旋转1周 设置 时间。 /// 刚开机时,RenZiJiaPeriod = DefaultRPeriod; /// 异常时,RenZiJiaPeriod = DefaultRPeriod; /// public TimeSpan DefaultRPeriod { get; set; } /// /// 人字架到测厚仪膜长 单位m /// public double FilmLength { get; set; } = 25;//m /// /// 辊周长,单位mm /// public double RollPerimeter { get; set; } = 314;//mm /// /// 辊周长,单位m /// double MofR => RollPerimeter / 1000.0; /// /// 自定义 转向信号 输入口序号 从0开始 默认9-1 /// public int InNo_Limit0 { get; set; } = 9 - 1; /// /// 自定义 转向信号 输入口序号 从0开始 默认10-1 /// public int InNo_Limit1 { get; set; } = 10 - 1; /// /// 自定义 辊速度 输入口序号 从0开始 默认11-1 /// public int InNo_Roll { get; set; } = 11 - 1; #endregion #region 状态 /// /// 当前旋转架旋转角度 /// public double Angle { get; private set; } /// /// 当前方向 /// public Misc.DIRECTION Direction { get; private set; } /// /// 累计旋转次数 /// public virtual int RotationCnt { get; protected set; } /// /// 最新的限位信号序号 /// public int LastLimitNo { get; private set; } /// /// 最后一个限位信号 到当前已经消耗的时间 /// public TimeSpan PastTime { get; private set; } /// /// 当前线速度,单位 m/min /// public double FilmVelocity { get; private set; } /// /// 离开限位 到 撞下一个限位 的 旋转架转动时间, 需要初始值,以后测量出来的 /// public TimeSpan RenZiJiaPeriod { get; private set; } /// /// 缓冲区拥有数据的时间长度,1s更新一次 /// public TimeSpan BufTotalTime { get; private set; } /// /// 原点旋转脉冲 上一次撞限位开关0时, 旋转架脉冲 /// public int OrgRPos { get; protected set; } /// ///旋转架全局脉冲, 当前旋转架脉冲 = GlobalRPos - OrgRPos; /// public int GlobalRPos { get; protected set; } /// /// 旋转架脉冲速度, 旋转架角速度 = RSpeed * RAngle / RPosOfR /// public int RSpeed { get; protected set; } #endregion #region 成员变量 /// /// 每圈数据列表 /// public RList mLimitList; /// /// 辊信号列表 /// RList mRollList; /// /// 转向信号列表 /// RList mSignList; /// /// 后触发,信号改变推送列表!! /// List mdtFilmInfoChangedEventList = new List(); /// /// 旋转脉冲列表 /// RList mRPosList; /// /// 原点旋转脉冲 是否有效 /// OrgRPosValidLevel OrgRPosValidLv; enum OrgRPosValidLevel { /// /// 刚开机,OrgRPos = 0 /// Invalid, /// /// 没有撞任何一个转向信号,使用列表第1个作为OrgRPos /// JustListHeader, /// /// 撞转向信号0,真正的 OrgRPos = GlobalRPos /// Sign0 } IFlyADClientAdv flyad; #endregion /// /// /// public BlowingDetect360() { DefaultRPeriod = TimeSpan.FromMinutes(8); RenZiJiaPeriod = DefaultRPeriod; RollPerimeter = 314;//mm FilmLength = 25;//m Angle = 0;//° FilmVelocity = 0;//m/min mRollList = new Misc.RList(50 * 60 * 1000 / 314);//以50m/min 线速度,储存大于 60min数据 mLimitList = new Misc.RList(10);//人字架转10次的信号 mSignList = new RList(10);//人字架正反转信号 mRPosList = new RList(60 * 60);//人字架脉冲数据 1秒一个 60分钟数据 RotationCnt = 0; Clear(); PropertyChanged += new PropertyChangedEventHandler(RenZiJiaAndFilmPositionDetect_PropertyChanged); } int RotationCntToLimitIndex(int rotationcnt) { if (RotationCnt == 0) return -1; else return rotationcnt - 1 - (RotationCnt - mLimitList.Count); } /// /// 正反转信号列表中的序号,转旋转次数 /// /// /// int LimitIndexToRotationCnt(int index) { if (RotationCnt == 0) return 0; else return RotationCnt - mLimitList.Count + index + 1; } /// /// 使参数合法 /// public void CheckParamErr() { if ((DefaultRPeriod.TotalMinutes < 4) || (DefaultRPeriod.TotalMinutes > 25)) { DefaultRPeriod = TimeSpan.FromMinutes(5.8); } if ((FilmLength < 0) || (FilmLength > 100)) { FilmLength = 25; } if ((RollPerimeter < 100) || (RollPerimeter > 1000)) { RollPerimeter = 314; } } /// /// 触发膜信息修改事件 /// /// 时间点 void TouchFilmInfoChanged(DateTime dt) { if (FilmInfoChangedEvent == null) return; mdtFilmInfoChangedEventList.Add(dt); TouchFilmInfoChanged(); } /// /// 触发膜信息修改事件列表的全部事件 /// void TouchFilmInfoChanged() { if (FilmInfoChangedEvent == null) return; while (mdtFilmInfoChangedEventList.Count > 0) { DateTime dt = mdtFilmInfoChangedEventList.First(); DateTime dt_future; int ret = GetFuture(dt, out dt_future); if (ret == 1)//发生在信号列表的未来,留着,以后再触发!! { break; } else if (ret == 0)//发生在信号列表的中间 { //只需要触发最老的那个就够了!!!!! mdtFilmInfoChangedEventList.Clear(); FilmInfoChangedEvent(this, new FilmInfoChangedEventArgs(dt_future)); } else//发生在信号列表的以前 { //以前的事件不触发,除非就只有这么一个 mdtFilmInfoChangedEventList.RemoveAt(0); if (mdtFilmInfoChangedEventList.Count == 0) { FilmInfoChangedEvent(this, new FilmInfoChangedEventArgs(dt_future)); } } } } //TODO void RenZiJiaAndFilmPositionDetect_PropertyChanged(object sender, PropertyChangedEventArgs e) { if ((e.PropertyName == nameof(FilmLength)) || (e.PropertyName == nameof(RollPerimeter)) ) { TouchFilmInfoChanged_lastLimit(4); } else if (e.PropertyName == nameof(RenZiJiaPeriod)) { TouchFilmInfoChanged_lastLimit(1); } else if (e.PropertyName == nameof(DefaultRPeriod)) { RenZiJiaPeriod = DefaultRPeriod; } } /// /// 触发膜信息修改事件 /// /// 最后旋转几次 void TouchFilmInfoChanged_lastLimit(int last_cnt) { if (mLimitList.Count < last_cnt) { last_cnt = mLimitList.Count; } int idx = mLimitList.Count - last_cnt - 1; if (idx <= 0) { TouchFilmInfoChanged(DefaultTime); } else { TouchFilmInfoChanged(mLimitList[idx].dt_begin); } } /// /// 创建完对象后,必须赋值 /// /// public void Init(IFlyADClientAdv flyad) { this.flyad = flyad; flyad.IStatusChangedEvent += new FlyADBase.IStatusChangedEventHandler(flyad_IStatusChangedEvent); FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD, new FObjBase.PollModule.PollHandler(OnPoll)); } void update_limit(FlyADBase.IStatusChangedEventArgs e) { bool bchanged = false;//信号更新? //TODO,不使用AD盒的时间 //DateTime dt = e.Time;//信号更新发生的时间 DateTime dt = DateTime.Now; int no = 0; bool status = false;//信号的状态 true开始, false结束 int position2 = e.Position2; if (Misc.MyBase.CHECKBIT(e.IChanged, InNo_Limit0))//信号0 发生变化 { bchanged = true; no = 0; if (!Misc.MyBase.CHECKBIT(e.IStatus, InNo_Limit0)) { status = true; } } else if (Misc.MyBase.CHECKBIT(e.IChanged, InNo_Limit1))//信号1 发生变化 { bchanged = true; no = 1; if (!Misc.MyBase.CHECKBIT(e.IStatus, InNo_Limit1)) { status = true; } } if (!bchanged)//没有信号更新 return; SignData sd = new SignData() { Time = dt, On = status, No = no }; mSignList.RAdd(sd); if (IsRPosMode) { //使用编码器测量旋转角度 //方向 由编码器决定 update_orgRPos(sd); } else { //没有编码器,方向由 信号类型决定 Direction = no == 0 ? Misc.DIRECTION.FORWARD : Misc.DIRECTION.BACKWARD; } LastLimitNo = no; update_limit(dt, status); } void update_orgRPos(SignData sd) { if (!sd.On) return; OrgRPosValidLv = OrgRPosValidLevel.Sign0; OrgRPos = sd.GlobalRPos; int speed = flyad.Speed2; if (speed > 0) Direction = DIRECTION.FORWARD; else if (speed < 0) Direction = DIRECTION.BACKWARD; } /// /// 添加正反转信号到 列表 /// /// 信号更新发生的时间 /// 信号的状态 true开始, false结束 void update_limit(DateTime dt, bool status) { //把信号加入到列表 if (status == true)//添加,写入开始时间 { mLimitList.RAdd(new LimitCell() { dt_begin = dt,dt_end = dt }); RotationCnt++; } else //写入 结束时间 { if (mLimitList.Count > 0) { LimitCell lc = mLimitList.Last(); lc.dt_end = dt;//完成一次信号 } else { //异常,还没开始,就结束了 mLimitList.RAdd(new LimitCell() { dt_begin = dt, dt_end = dt }); RotationCnt++; } } //更新其它参数 update_param_for_limit(dt); } /// /// 更新因为旋转信号的添加导致的变化 /// /// 信号变化的时间 void update_param_for_limit(DateTime dt) { if (mLimitList.Count() > 0) { LimitCell lc = mLimitList.Last(); if (dt == lc.dt_begin)//刚撞转向 { //更新人字架旋转周期 if (mLimitList.Count > 1) { DateTime dt1 = mLimitList[mLimitList.Count - 1].dt_begin; DateTime dt2 = mLimitList[mLimitList.Count - 2].dt_begin; RenZiJiaPeriod = dt1 - dt2;//只有这个是正确的!!! //触发修正事件!!!!! TouchFilmInfoChanged(dt2); } else if (mLimitList.Count == 1) { DateTime dt1 = mLimitList[mLimitList.Count - 1].dt_begin; DefaultTime = dt1 - RenZiJiaPeriod; //触发修正事件!!!!! TouchFilmInfoChanged(DefaultTime); } } } } void update_roll(FlyADBase.IStatusChangedEventArgs e) { //---------------------------------------------------------------------- //更新辊信号 if (!Misc.MyBase.CHECKBIT(e.IChanged, InNo_Roll)) return;//没有信号更新 //信号的状态 bool status = !Misc.MyBase.CHECKBIT(e.IStatus, InNo_Roll); if (status)//1->0 触发!!! { mRollList.RAdd( new RollCell2() { Time = DateTime.Now,//用于时间定位 SysTime = e.Time,//用于算速度 }); //触发修正事件!!!!! TouchFilmInfoChanged(); } } void flyad_IStatusChangedEvent(object sender, FlyADBase.IStatusChangedEventArgs e) { update_limit(e); update_roll(e); } /// /// 1秒触发一次 /// void OnPoll() { if (!flyad.IsReady || !flyad.IsConnected) return; //-------------------------------------------------------------------- //旋转架速度安全检测 OnPoll_checkRVelocity(); //-------------------------------------------------------------------- if (IsRPosMode) { //更新旋转脉冲 OnPoll_updateRPos(); //更新旋转角度 OnPoll_updateAngleByRPos(); } else { //-------------------------------------------------------------------- //当前旋转架角度 OnPoll_updateAngle(); } //-------------------------------------------------------------------- //线速度安全检测 OnPoll_checkFilmV(); //-------------------------------------------------------------------- //更新当前线速度 OnPoll_updateFilmV(); //-------------------------------------------------------------------- //限制列表大小 KeepSize(); } void KeepSize() { DateTime keepTime = DateTime.Now - TimeSpan.FromMinutes(60); //限制数量 List dts = new List(); dts.Add(KeepSize(mRollList, keepTime)); dts.Add(KeepSize(mSignList, keepTime)); dts.Add(KeepSize(mRPosList, keepTime)); var earlyTime = dts.Min(); if (DateTime.Now > earlyTime) { BufTotalTime = DateTime.Now - earlyTime; } else { BufTotalTime = TimeSpan.Zero; } } DateTime KeepSize(List list, DateTime keepTime) where T : ITimeCell { if (list.Count() > 0) { while (true) { var cell = list.First(); if (cell.Time > keepTime) break; list.RemoveAt(0); if (list.Count() == 0) break; } } if (list.Count() > 0) { return list.First().Time; } return DateTime.MaxValue; } void OnPoll_updateRPos() { DateTime now = DateTime.Now; DateTime sysTime = flyad.Now; int globalRPos = flyad.Position2; int speed = flyad.Speed2; if (mRPosList.Count() > 0) { var lastPosData = mRPosList.Last(); if (now - lastPosData.Time < TimeSpan.FromSeconds(1)) { return; } } mRPosList.RAdd(new RPosData() { Time = now, SysTime = sysTime, Speed = speed, GlobalRPos = globalRPos }); if (OrgRPosValidLv == OrgRPosValidLevel.Invalid) { OrgRPos = globalRPos; //第1次, 赋值为第1个全局旋转脉冲 OrgRPosValidLv = OrgRPosValidLevel.JustListHeader; } GlobalRPos = globalRPos; RSpeed = speed; } /// /// 旋转架速度换算 /// void OnPoll_updateAngleByRPos() { //必须在 0~RPosOfR 范围内 Angle = GetAngleByGlobalRPos(GlobalRPos); } public double GetAngleByGlobalRPos(int globalRPos) { //OrgRPos 为 -RAngle/2 int rPos = globalRPos - OrgRPos; //360°对应的旋转脉冲 int rPosOf360 = RPosOfR; while (rPos < 0) rPos += rPosOf360; while (rPos > rPosOf360) rPos -= rPosOf360; double angle = 360 * rPos / RPosOfR; //角度范围是 0°~ 360° return angle; } /// /// 旋转架速度安全检测 /// void OnPoll_checkRVelocity() { DateTime dt = DefaultTime; if (mLimitList.Count > 0) { LimitCell lc = mLimitList.Last(); if (lc.dt_end != DateTime.MinValue) dt = lc.dt_end; else dt = lc.dt_begin; } PastTime = DateTime.Now - dt; if (PastTime > TimeSpan.FromMinutes(25))//25min { //大于25分钟,也没有一个信号,复位 Clear(); } } /// /// 更新旋转架速度 /// void OnPoll_updateAngle() { GetAngle(DateTime.Now, false, out double angle, out int rotationCnt); Angle = angle; } /// /// 更新当前线速度 /// void OnPoll_updateFilmV() { if (mRollList.Count > 1) { DateTime sysTime1 = mRollList[mRollList.Count - 1].SysTime;//最后一次的sysTime int cnt = 5;//5次均值 if (cnt > mRollList.Count) cnt = mRollList.Count; DateTime sysTime2 = mRollList[mRollList.Count - cnt].SysTime;//5次前的sysTime TimeSpan ts_last = TimeSpan.FromTicks((sysTime1 - sysTime2).Ticks / (cnt - 1));//5次的平均滤波, 使用AD盒的时间算出来的时间差,最准 DateTime dt11 = mRollList[mRollList.Count - (cnt - 1)].Time;//4次前的PC时间 //因为没有使用AD盒的时间,所以可以用DateTime.Now 比较 TimeSpan ts_now = TimeSpan.FromTicks((DateTime.Now - dt11).Ticks / (cnt - 1));//使用 电脑的时间,算出来的时间差,大概而已 TimeSpan ts_max; if (ts_now > (ts_last + ts_last))//超过了2倍的时间,肯定是慢下来了 { ts_max = ts_now; } else { ts_max = ts_last; } FilmVelocity = MofR / ts_max.TotalMinutes; } } /// /// 线速度安全检测 /// void OnPoll_checkFilmV() { if (mRollList.Count > 0) { DateTime dt = mRollList[mRollList.Count - 1].Time; TimeSpan ts = DateTime.Now - dt; if (ts.TotalMinutes > 1)//1min { //大于1分钟,也没有一个信号,复位 Clear(); } } } public void Clear() { mLimitList.Clear(); mRollList.Clear(); DefaultTime = DateTime.Now; FilmVelocity = 0; RenZiJiaPeriod = DefaultRPeriod; RotationCnt = 0;//复位次数!!! LastLimitNo = -1; BufTotalTime = TimeSpan.Zero; ClearEvent?.Invoke(this); } //public class FilmInfo //{ // /// // /// 上层 位于膜泡的角度 0~360° // /// // public double angle1; // /// // /// 下层 位于膜泡的角度 0~360° // /// // public double angle2; // /// // /// 膜泡旋转方向 // /// // public Misc.DIRECTION direction; // /// // /// 线速度 // /// // public double filmVelocity; // /// // /// 累计旋转次数 // /// // public int rotationCnt; //} public enum GetLimitTimeResult { /// /// 获取成功 /// OK, /// /// 不存在该点 /// NOEXIST, /// /// 限位已经撞了,但膜还没走完,等吧 /// ISFURTURE } /// /// 计算膜长用; /// 输入膜长,获取对应的 限位点时间 /// /// 旋转次数 /// 膜长 /// 膜长对应限位时间 /// public GetLimitTimeResult GetLimitTime(int rotationcnt, double filmlength, out DateTime timeOfFilmLength) { timeOfFilmLength = DateTime.MinValue; int index = RotationCntToLimitIndex(rotationcnt); if (index == -1) return GetLimitTimeResult.NOEXIST; if (index >= mLimitList.Count) return GetLimitTimeResult.ISFURTURE; DateTime dt = mLimitList[index].dt_begin; int ret = GetFuture(dt, filmlength, out timeOfFilmLength); if (ret == 1) return GetLimitTimeResult.ISFURTURE; return GetLimitTimeResult.OK; } /// /// 获取当前在膜上的测量点,对应于膜泡的角度信息, 当时间点,比LimitList.Last.dt+RenZiJiaPeriod还要后,更新RenZiJiaPeriod /// 返回 -1, 当前的时间点在列表以前; /// 返回 0, 当前的时间点在列表中; /// 返回 1, 当前的时间点在列表的未来; /// /// 被压扁后的膜宽度 /// 探头所在膜的横向位置 /// 测量的时间点 /// public int GetFilmInfo(out FilmInfo filminfo, DateTime dt, double filmWidth, double filmPosH) { double filmlen = FilmLength; return GetFilmInfo(out filminfo, dt, filmWidth, filmPosH, filmlen); } int GetFilmInfo(out FilmInfo filminfo, DateTime dt, double filmWidth, double filmPosH, double filmLen) { filminfo = null; //与0°的偏移 double a = filmPosH / filmWidth * 180; if ((a < 0) || (a > 180)) { return -1;//不在膜的范围内 } if (dt == DateTime.MinValue) { //TODO, BUG return -1;//异常!!! } int ret = GetAgo(dt, out DateTime ago_dt, out int ago_index); bool ret2 = GetAngle(a, ago_dt, out double angle0, out double angle1, out int rotationcnt); if (!ret2) return -1;//异常!!! double velocity = RollList_GetVelocity(ago_index); filminfo = new FilmInfo() { angle1 = angle0, angle2 = angle1, filmVelocity = velocity, direction = Direction, rotationCnt = rotationcnt, inCV = true }; return ret; } #region 人字架时间点<-->24m后膜时间点 /// /// 输入当前时间点,找到 刚出人字架时的时间点, 及那时候的线速度; /// 返回 -1, 当前的时间点在列表以前; /// 返回 0, 当前的时间点在列表中; /// 返回 1, 当前的时间点在列表的未来; /// /// 当前时间 /// 24米真实长度 /// int GetAgo(DateTime dt, out DateTime ago_dt, out int ago_index) { return GetAgoFix(dt, out ago_dt, out ago_index); } /// /// 膜距离固定 获取膜距离前的时间点 /// /// /// /// /// int GetAgoFix(DateTime dt, out DateTime ago_dt, out int ago_index) { ago_dt = DateTime.MinValue; ago_index = -1; int index; int ret = RollList_FindIndex(dt, out index); if (ret == 1)//发生在未来,什么都不干 return ret; //纵向位置 double filmPosition = RollList_GetPosition(index, dt); double filmPos0 = filmPosition - FilmLength; ret = RollList_GetDateTime(filmPos0, out index, out ago_dt); ago_index = index; return ret; } /// /// 输入过去时间,让它再加上膜长时间,得到未来时间; /// 返回 -1, 未来的时间在列表以前; /// 返回 0, 未来的时间在列表中; /// 返回 1, 未来的时间在列表的未来; /// /// 过去时间 /// 未来时间 /// int GetFuture(DateTime dt, out DateTime dt_future) { return GetFuture(dt, FilmLength, out dt_future); } /// /// 输入过去时间,让它再加上膜长时间,得到未来时间; /// 返回 -1, 未来的时间在列表以前; /// 返回 0, 未来的时间在列表中; /// 返回 1, 未来的时间在列表的未来; /// /// 过去时间 /// 膜长 /// 未来时间 /// int GetFuture(DateTime dt, double filmlength, out DateTime dt_future) { dt_future = DateTime.MinValue; double position; int i; int ret = RollList_FindIndex(dt, out i); if (ret == -1) position = RollList_GetPosition(0, dt); else if (ret == 0) position = RollList_GetPosition(i, dt); else return ret; position += filmlength; return RollList_GetDateTime(position, out i, out dt_future); } #endregion /// /// 计算出角度 0~360° 恒速阶段 /// /// 旋转结束时间点 /// 旋转开始时间点 /// 用于转换为角度的时间 /// 输出: 角度 void LimitListCal_ConstantVelocity(DateTime dtEnd, DateTime dtStart, DateTime dt, out double angle) { TimeSpan ts1 = dt - dtStart; TimeSpan ts2 = dtEnd - dtStart; angle = 360 * ts1.Ticks / ts2.Ticks; } /// /// 时间点查找旋转角度 0~360° /// /// 时间 /// 当dt 发生在限位列表的未来,是否修改参数,如RenZiJiaPeriod /// 角度 /// 旋转的方向 /// 旋转的次数 /// 在恒速区 void GetAngle(DateTime dt, bool modifyparam, out double angle, out int rotationCnt) { int index; ListFindIndexResult ret = LimitList_FindIndex(dt, out index); DateTime dt_start; DateTime dt_end; switch (ret) { case ListFindIndexResult.Future://发生在未来 { if (dt < DefaultTime) DefaultTime = dt - TimeSpan.FromSeconds(10); //检测是否需要修正 RenZiJiaPeriod if (mLimitList.Count == 0) { dt_start = DefaultTime; } else { dt_start = mLimitList[index].dt_begin; } rotationCnt = LimitIndexToRotationCnt(index); if (dt > (dt_start + RenZiJiaPeriod)) { if (modifyparam) { //修正!!!! RenZiJiaPeriod = dt - dt_start + TimeSpan.FromSeconds(10); } else { dt = dt_start + RenZiJiaPeriod;//只是为了看,随便了。 时间停止下来 } } dt_end = dt_start + RenZiJiaPeriod; LimitListCal_ConstantVelocity(dt_end, dt_start, dt, out angle); } break; case ListFindIndexResult.BetweenCells: { dt_start = mLimitList[index].dt_begin; dt_end = mLimitList[index + 1].dt_begin; rotationCnt = LimitIndexToRotationCnt(index); LimitListCal_ConstantVelocity(dt_end, dt_start, dt, out angle); } break; //case ListFindIndexResult.Past://过去 default: { dt_start = DefaultTime; dt_end = mLimitList[0].dt_begin; rotationCnt = LimitIndexToRotationCnt(-1); LimitListCal_ConstantVelocity(dt_end, dt_start, dt, out angle); } break; } if (Direction == DIRECTION.BACKWARD) angle = 360 - angle; } #region RollList 查找 /// /// 获取 [idx,idx+1] 之间 的 位置, 单位m /// /// /// /// double RollList_GetPosition(int idx, DateTime dt) { DateTime dt1 = mRollList[idx + 1].Time; double filmPos1 = (idx + 1) * MofR; DateTime dt0 = mRollList[idx].Time; double filmPos0 = (idx) * MofR; return (filmPos1 - filmPos0) * (dt - dt0).Ticks / (dt1 - dt0).Ticks + filmPos0; } /// /// 获取 [idx,idx+1] 之间 的 速度 m/min /// /// /// double RollList_GetVelocity(int idx) { DateTime dt1 = mRollList[idx + 1].SysTime; DateTime dt2 = mRollList[idx].SysTime; return MofR / dt1.Subtract(dt2).TotalMinutes; } /// /// 获取 [idx,idx+1] 之间 的 时间 /// /// /// 纵向位置m /// DateTime RollList_GetDateTime(int idx, double filmPosition) { //TODO 经常出错,position 无限小,idx=0 DateTime dt1 = mRollList[idx + 1].Time; double filmPos1 = (idx + 1) * MofR; DateTime dt2 = mRollList[idx].Time; double filmPos2 = (idx) * MofR; return dt2.Add(new TimeSpan((long)((dt1 - dt2).Ticks * (filmPosition - filmPos2) / (filmPos1 - filmPos2)))); } /// /// 通过 filmPosition 查找时间 /// 返回 -1,这个过去的时间; /// 返回 0,在列表中; /// 返回 1,发生在未来的时间; /// /// 单位m /// int RollList_GetDateTime(double filmPosition, out int idx, out DateTime dt) { idx = 0; dt = DateTime.MinValue; if (filmPosition < 0) return -1; if (mRollList.Count == 0) return 1; idx = (int)(filmPosition / MofR); if (idx >= mRollList.Count - 1) { return 1; } dt = RollList_GetDateTime(idx, filmPosition); return 0; } /// /// dt 这个时间,在是 index 与 index+1 中间发生的 /// 返回 -1,这个过去的时间; /// 返回 0,在列表中; /// 返回 1,发生在未来的时间; /// /// /// int RollList_FindIndex(DateTime dt, out int index) { index = 0; if (mRollList.Count <= 1)//没数据,所以是发生在未来的 return 1; for (int i = mRollList.Count - 1; i >= 0; i--) { if (dt >= mRollList[i].Time) { //找到了 if (i == (mRollList.Count - 1))//这个是未来的时间点,还没发生!!! { index = i; return 1; } else { index = i; return 0; } } } //这个在过去的时间 return -1; } #endregion /// /// 时间对应旋转状态 /// enum DTSTATUS { /// /// 发生在未来的均速阶段 /// Future, /// /// 发生在过去的均速阶段 /// Past, /// /// 发生在均速阶段 /// Normal, /// /// 发生在方向切换阶段 /// Swap, } enum ListFindIndexResult { /// /// 发生在未来, index.dt_end ~ 未来, 可能 index都还没有发生 /// Future, /// /// 发生在 index.dt_end ~ (index+1).dt_begin /// BetweenCells, /// /// 发生在 过去 /// Past } /// /// 找到 列表中 (开始时间,结束时间) 小于 dt 的信号点 /// /// /// /// ListFindIndexResult LimitList_FindIndex(DateTime dt, out int index) { index = 0; if (mLimitList.Count <= 0)//没数据,所以是发生在未来的 return ListFindIndexResult.Future; for (int i = mLimitList.Count - 1; i >= 0; i--) { LimitCell lc = mLimitList[i]; if (dt >= lc.dt_begin) { //找到了 index = i; if (i == (mLimitList.Count - 1))//这个是未来的时间点,还没发生!!! { return ListFindIndexResult.Future; } else { return ListFindIndexResult.BetweenCells; } } } //这个在过去的时间 return ListFindIndexResult.Past; } /// /// 获取膜角度 /// /// 与0°的偏移, 0~180 /// 时间点 /// 上面的膜对应的膜角度 /// 下面的膜对应的膜角度 /// 扫描次数 bool GetAngle(double a, DateTime dt, out double angle0, out double angle1, out int rotationCnt) { angle0 = 0; angle1 = 0; rotationCnt = 0; if (a > 180 || a < 0) return false; //压扁对面的角度 double a_other = 360 - a; double angle; //不修改旋转速度!!!!!!!!!!!! GetAngle(dt, false, out angle, out rotationCnt); a = GetValidAngle(a + angle); a_other = GetValidAngle(a_other + angle); angle0 = a; angle1 = a_other; return true; } /// /// 限制角度在 [0~360) /// /// /// double GetValidAngle(double a) { if (a >= 360) a -= 360; else if (a < 0) a += 360; return a; } public void Apply() { Save(); } //public class FilmInfoChangedEventArgs : EventArgs //{ // public FilmInfoChangedEventArgs(DateTime time) // { // Time = time; // } // /// // /// 开始时间点 // /// // public DateTime Time // { // get; // set; // } //} //public delegate void FilmInfoChangedEventHandler(object sender, FilmInfoChangedEventArgs e); public event FilmInfoChangedEventHandler FilmInfoChangedEvent; //public delegate void ClearEventHandler(object sender); public event ClearEventHandler ClearEvent; /// /// 释放资源 /// public void Dispose() { Clear(); } #region INotifyPropertyChanged 成员 public event PropertyChangedEventHandler PropertyChanged; #endregion /// /// 加载历史数据 /// /// public bool Load() { return BlowingDetect360JsonDb.Load(this, file_path); } string file_path = "blowingdetect360.json"; /// /// 保存 /// public bool Save() { return BlowingDetect360JsonDb.Save(this, file_path); } /// /// 获取 信号列表 /// /// retdata = GetSignListReponse /// public void GetSignList(AsyncCBHandler asyncDelegate, object asyncContext) { GetSignListReponse p = new GetSignListReponse(); p.datas = mSignList; asyncDelegate(asyncContext, p); } /// /// 获取 信号列表 /// /// 开始时间 /// retdata = GetSignListReponse /// public void GetSignList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext) { GetSignListReponse p = new GetSignListReponse(); p.datas = mSignList.FindAll(r => r.Time >= begin); asyncDelegate(asyncContext, p); } /// /// 获取 辊信号列表 /// /// 开始时间 /// retdata = GetRollListReponse /// public void GetRollList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext) { var p = new GetRollListReponse(); p.datas = mRollList.FindAll(r => r.Time > begin).Select(r => new RollCell() { Time = r.Time, SysTime = r.SysTime }).ToList(); asyncDelegate(asyncContext, p); } /// /// 获取 转向信号列表 /// /// 开始时间 /// retdata = GetRollListReponse /// public void GetLimitList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext) { var p = new GetLimitListReponse(); p.datas = mLimitList.FindAll(r => { return (r.dt_end != DateTime.MinValue) && (r.dt_begin > begin); }); asyncDelegate(asyncContext, p); } /// /// 获取 旋转位置列表 /// /// 开始时间 /// /// public void GetRPosList(DateTime begin, AsyncCBHandler asyncDelegate, object asyncContext) { var p = new GetRPosListReponse(); p.datas = mRPosList.FindAll(r => r.Time > begin); asyncDelegate(asyncContext, p); } public void GetFilmLength3D(AsyncCBHandler asyncDelegate, object asyncContext) { //不实现 //throw new NotImplementedException(); } public void SetFilmLength3D(List data) { //不实现 //throw new NotImplementedException(); } } public class BlowingDetect360JsonDb { static Mapper Mapper { get; } = new AutoMapper.Mapper(new MapperConfiguration(c => { c.CreateMap().ReverseMap(); })); /// /// 加载历史数据 /// /// public static bool Load(BlowingDetect360 src, string file_path) { if (File.Exists(file_path)) { try { string json = File.ReadAllText(file_path); var jsonDb = JsonConvert.DeserializeObject(json); Mapper.Map(jsonDb, src); return true; } catch { //异常,没有json 解码失败 } } return false; } /// /// 保存 /// public static bool Save(BlowingDetect360 src, string file_path) { try { var jsonDb = Mapper.Map(src); string json = JsonConvert.SerializeObject(jsonDb, Formatting.Indented); File.WriteAllText(file_path, json); return true; } catch { //异常,没有json 编码失败 } return false; } /// /// 人字架 旋转1周 设置 时间。 /// 刚开机时,RenZiJiaPeriod = DefaultRPeriod; /// 异常时,RenZiJiaPeriod = DefaultRPeriod; /// public TimeSpan DefaultRPeriod; /// /// 人字架到测厚仪膜长 单位m /// public double FilmLength; /// /// 辊周长,单位mm /// public double RollPerimeter; /// /// 旋转反向 /// public bool IsReversed; /// /// 使用编码器检测旋转架旋转位置模式 /// public bool IsRPosMode; /// /// 人字架 离开限位 到 撞下一个限位 的 旋转架转动总脉冲 单位 脉冲 /// public int RPosOfR = 50000; } }