using System ; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Net; using System.Text; namespace FLY.Modbus { /// <summary> /// 学习HMI, 根据 property 与 PLC寄存器 映射表 自动更新 /// /// 使用说明: /// 1.注册 NameDataChanged 事件, 当从PLC读取的数据更新后,通知property修改 /// NameDataChanged += ...... /// /// 2.填写 property 与 PLC寄存器 映射表 /// DRMap /// /// 3.通过FindDataMap() 找到 映射 注册需要更新的属性 /// dr = FindDataMap(obj, propertyName) /// key = 页面对应obj /// PlanAdd(key, dr) /// /// 4.创建更新计划 /// PlanMake() /// /// 5.页面关闭,不需要再更新 property, 注销更新计划 /// key = 页面对应obj /// PlanRemove(key) /// /// 6.当property 修改后,调用 SetNameData 设置PLC /// SetNameData(obj, propertyName, propertyValue) /// </summary> public class ModbusMapper_Client : ModbusMapper { /// <summary> /// 更新周期,单位ms, /// </summary> public int UpdateInterval = 200; TimeSpan actUpdateInterval; /// <summary> /// 实际更新间隔 /// </summary> public TimeSpan ActUpdateInterval { get; protected set; } //{ // get { return actUpdateInterval; } // protected set { // if (actUpdateInterval != value) // { // actUpdateInterval = value; // NotifyPropertyChanged("ActUpdateInterval"); // } // } //} private Stopwatch mStopwatch = new Stopwatch(); class RegWrite { public PLCAddressArea dataArea; public int addr; public object val; public RegWrite(PLCAddressArea dataArea, int addr, object val) { this.dataArea = dataArea; this.addr = addr; this.val = val; } } List<RegWrite> rws = new List<RegWrite>(); /// <summary> /// tcp client /// </summary> public ClientTCP mclient; /// <summary> /// /// </summary> /// <param name="clienttcp"></param> public ModbusMapper_Client(ClientTCP clienttcp) { mclient = clienttcp; FObjBase.PollModule.Current.Poll_Config(OnPoll_update); mclient.PropertyChanged += Mclient_PropertyChanged; } public override string ToString() { return mclient.ToString(); } private void Mclient_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "IsConnected") { if (!mclient.IsConnected) { coils.ClearPlanState(); registers.ClearPlanState(); //复位全部状态 isInCommunication = false; p_doing = null; isUpdating = false; curr_area_idx = 0; // DRMap 的数据 与 下端 属性的数据一致 foreach (RegWrite rw in rws) { //更新 本地的 PLC 数据 SetModbusData(rw.dataArea, rw.addr, rw.val); } rws.Clear(); mStopwatch.Stop(); } } } void OnPoll_update() { if (!mclient.IsConnected) return; if (!mStopwatch.IsRunning) mStopwatch.Restart(); if (mStopwatch.ElapsedMilliseconds >= UpdateInterval) { //检查上次更新周期是否完成 if (!isUpdating) { isUpdating = true; ActUpdateInterval = mStopwatch.Elapsed; mStopwatch.Restart(); } } NextPlan(); } int curr_area_idx = 0; void NextPlan() { if (isInCommunication)//已经在通讯中 return; if (rws.Count() > 0) UpdateWriteData(); if (isUpdating) { p_doing = mAreaManager[curr_area_idx].GetNextPlan(); if (p_doing != null) { p_doing.isDoing = false; UpdateReadData(); } else { //读完 curr_area_idx++; if (curr_area_idx >= mAreaManager.Count()) { isUpdating = false; //读完一轮了 curr_area_idx = 0; } else { NextPlan(); } } } } /// <summary> /// 读 多个线圈回调 /// </summary> /// <param name="val"></param> /// <param name="obj"></param> void Do_01_callback(bool[] val, object obj) { ModbusMapper.Plan p = obj as ModbusMapper.Plan; p.isDoing = false; SetModbusData(PLCAddressArea.Coil, p.addr, val); isInCommunication = false; NextPlan(); } /// <summary> /// 读多个寄存器回调 /// </summary> /// <param name="val"></param> /// <param name="obj"></param> void Do_03_callback(UInt16[] val, object obj) { ModbusMapper.Plan p = obj as ModbusMapper.Plan; p.isDoing = false; SetModbusData(PLCAddressArea.Register, p.addr, val); isInCommunication = false; NextPlan(); } void Do_0F_callback(object obj) { isInCommunication = false; NextPlan(); } void Do_10_callback(object obj) { isInCommunication = false; NextPlan(); } /// <summary> /// 正在通讯中 /// </summary> bool isInCommunication = false; /// <summary> /// 读更新流程中 /// </summary> bool isUpdating = false; /// <summary> /// 读更新 的计划 /// </summary> ModbusMapper.Plan p_doing = null; ///// <summary> ///// ///// </summary> ///// <param name="propertyname"></param> //protected void NotifyPropertyChanged(string propertyname) //{ // PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname)); //} ///// <summary> ///// ///// </summary> //public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// PC -> PLC /// 为命名数据赋值,会改变Modbus的寄存器值,并产生通知 /// </summary> /// <param name="dr">数据映射</param> /// <param name="val">数据值,不能为空</param> /// <returns></returns> public override bool SetNameData(DataToRegs dr, object val) { RegWrite rw; switch (dr.dataArea) { case PLCAddressArea.Coil: rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Coil); }); if (rw != null) rws.Remove(rw); rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToBools(val))); return true; case PLCAddressArea.Register: rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Register); }); if (rw != null) rws.Remove(rw); rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToRegs(val))); return true; } return false; } /// <summary> /// 更新写命令缓冲区 /// </summary> void UpdateWriteData() { RegWrite rw = rws.First(); switch (rw.dataArea) { case PLCAddressArea.Coil: if (mclient.Do_0F((UInt16)rw.addr, (bool[])(rw.val), Do_0F_callback, null) != ModbusClient_Errno.OK) return; SetModbusData(rw.dataArea, rw.addr, rw.val); break; case PLCAddressArea.Register: if (mclient.Do_10((UInt16)rw.addr, (UInt16[])rw.val, Do_10_callback, null) != ModbusClient_Errno.OK) return; SetModbusData(rw.dataArea, rw.addr, rw.val); break; } //更新 本地的 PLC 数据 SetModbusData(rw.dataArea, rw.addr , rw.val); rws.Remove(rw); isInCommunication = true; } void UpdateReadData() { switch (p_doing.area) { case PLCAddressArea.Coil: mclient.Do_01((UInt16)p_doing.addr, (UInt16)p_doing.num, Do_01_callback, p_doing); break; case PLCAddressArea.Register: mclient.Do_03((UInt16)p_doing.addr, (UInt16)p_doing.num, Do_03_callback, p_doing); break; } p_doing.isDoing = true; isInCommunication = true; } } }