using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; namespace FLY.Modbus { /// <summary> /// 命名数据到Modbus数据的映射 /// </summary> public abstract class ModbusMapper : INotifyPropertyChanged { /// <summary> /// 一次读取的数据量 /// </summary> const int MaxRegsOfOneRead = 100; const int MaxCoilsOfOneRead = 900; #region 数据变化通知 /// <summary> /// 命名数据改变事件 /// </summary> public event NameDataChangedEventHandler NameDataChanged; protected void NotifyNameDataChanged(DataToRegs dr)// object dataclass, string dataname) { NameDataChanged?.Invoke(this, dr);// new NameDataChangedEventArgs(dataclass, dataname)); } public event PropertyChangedEventHandler PropertyChanged; #endregion /// <summary> /// 数据映射表,根据这个表,以后自动更新PLC寄存器,或更新property /// </summary> public List<DataToRegs> DRmap = new List<DataToRegs>(); /// <summary> /// /// </summary> public ModbusMapper() { coilData = new CoilData(PLCAddressArea.Coil, MaxCoilsOfOneRead); registerData = new RegisterData(PLCAddressArea.Register, MaxRegsOfOneRead); mAreaManager = new List<AreaManager> { coilData, registerData }; } #region 数据映射 /// <summary> /// 查找数据-寄存器对应表 /// </summary> public DataToRegs FindDataMap(object owner, string propertyName) { return DRmap.Find((dr) => { return (dr.propertyName == propertyName && dr.owner == owner); }); } /// <summary> /// 数据映射到寄存器 /// </summary> /// <param name="dataArea">modbus 寄存器类型</param> /// <param name="addr">modbus 寄存器首地址</param> /// <param name="type">plc 数据对应类型</param> /// <param name="scale">plc 数据放大倍数</param> /// <param name="obj">Property 所属对象</param> /// <param name="propertyname">Property Name</param> /// <returns></returns> public DataToRegs MapDataToRegs(PLCAddressArea dataArea, int addr, REG_TYPE type, double scale, object obj, string propertyname) { DataToRegs dr = FindDataMap(obj, propertyname); if (dr == null) { dr = new DataToRegs(this, dataArea, addr, type, scale, obj, propertyname); DRmap.Add(dr); DRmap.Sort(delegate (DataToRegs X, DataToRegs Y) { int ct = X.dataArea.CompareTo(Y.dataArea); if (ct != 0) return ct; return X.addr.CompareTo(Y.addr); }); return dr; } return null; } #endregion #region Modbus数据 /// <summary> /// 就是 coils 与 registers 的组合 /// </summary> protected List<AreaManager> mAreaManager; protected CoilData coilData; protected RegisterData registerData; #endregion #region 数据读写 /// <summary> /// 为命名数据赋值,会改变Modbus的寄存器值,并产生通知 /// </summary> /// <param name="obj">属性所属的对象, 可为null</param> /// <param name="propertyName">属性名,不能为空</param> /// <param name="val">数据值,不能为空</param> /// <returns>赋值的结果</returns> public virtual bool SetNameData(object obj, string propertyName, object val) { DataToRegs dr = FindDataMap(obj, propertyName); if (dr == null) return false; SetNameData(dr, val); return true; } /// <summary> /// PC想向PLC写入数据; /// 为命名数据赋值,会改变Modbus的寄存器值,并产生通知 /// </summary> /// <param name="dr">数据映射</param> /// <param name="val">数据值,不能为空</param> /// <returns></returns> public abstract void SetNameData(DataToRegs dr, object val); /// <summary> /// 取命名数据的值 /// </summary> /// <param name="obj">属性所属 的对象</param> /// <param name="propertyName">属性名</param> /// <returns>属性的值</returns> public virtual object GetNameData(object obj, string propertyName) { DataToRegs dr = FindDataMap(obj, propertyName); if (dr == null) return null; return dr.value; } #region IModbusRegister实现 /// <summary> /// 是从PLC读取数据后; /// 设置Modbus的值,会改变相应命名数据的值,并产生通知 /// </summary> /// <param name="dataarea">coil or register</param> /// <param name="addr">地址</param> /// <param name="value">值</param> /// <returns></returns> public virtual void SetModbusData(PLCAddressArea dataarea, int addr, object value) { switch (dataarea) { case PLCAddressArea.Register: { IEnumerable<UInt16> vals = (IEnumerable<UInt16>)value; //找寄存器 int endAddr = addr + vals.Count() - 1; RegisterData area_regs = registerData; for (int i = 0; i < area_regs.regs.Count(); i++) { DataToRegs dr = area_regs.regs[i]; if (endAddr < dr.addr) { //在前面 break; } else if (addr <= dr.endAddr)//有交集 { //计算交集 int addr_act = Math.Max(addr, dr.addr); int endAddr_act = Math.Min(endAddr, dr.endAddr); int num_act = endAddr_act - addr_act + 1;//不需要检测<=0, 当为0,下面的循环跑不动 for (int j = 0; j < num_act; j++) { int idx1 = addr_act - dr.addr + j; int idx2 = addr_act - addr + j; UInt16[] plc_value = (UInt16[])dr.plcValue; if (plc_value[idx1] != vals.ElementAt(idx2)) { plc_value[idx1] = vals.ElementAt(idx2); area_regs.regs[i].isPlcValueChanged = true; } } if (endAddr_act == dr.endAddr) { if (dr.isPlcValueChanged) { dr.isPlcValueChanged = false; dr.ToPropertyObj(); //触发事件 NotifyNameDataChanged(dr); } } } else { //在后面 } } }break; case PLCAddressArea.Coil: { IEnumerable<bool> vals = (IEnumerable<bool>)value; //找寄存器 int endAddr = addr + vals.Count() - 1; CoilData area_coils = coilData; for (int i = 0; i < area_coils.regs.Count(); i++) { DataToRegs dr = area_coils.regs[i]; if (endAddr < dr.addr) { //在前面 break; } else if (addr <= dr.addr)//有交集 { int addr_act = dr.addr; int idx2 = addr_act - addr; if ((bool)dr.plcValue != vals.ElementAt(idx2)) { dr.plcValue = vals.ElementAt(idx2); dr.isPlcValueChanged = true; } //isPlcValueChanged 可能是上面值不一样 被设为true, 也可能是 客户端 发送数据到 PLC ,所以被强制设置为 true if (dr.isPlcValueChanged) { dr.ToPropertyObj(); //触发事件 NotifyNameDataChanged(dr); } } else { //在后面 } } }break; } } #endregion #endregion #region 类的静态函数 /// <summary> /// C#类型,对应PLC寄存器个数 /// </summary> /// <param name="type"></param> /// <returns></returns> public static int RegSize(REG_TYPE type) { switch (type) { case REG_TYPE.FLOAT: return sizeof(float) / sizeof(Int16); case REG_TYPE.UINT16: return sizeof(UInt16) / sizeof(Int16); case REG_TYPE.INT32: return sizeof(Int32) / sizeof(Int16); case REG_TYPE.INT16: return sizeof(Int16) / sizeof(Int16); case REG_TYPE.UINT32: return sizeof(Int32) / sizeof(Int16); default: { throw new NotImplementedException("不支持类型 " + type); } } } /// <summary> /// string 转 C#类型 /// </summary> /// <param name="typename"></param> /// <returns></returns> public static REG_TYPE TranslateToREG_TYPE(string typename) { typename = typename.ToLower(); if (typename == "int32") return REG_TYPE.INT32; if (typename == "uint32") return REG_TYPE.UINT32; if (typename == "bool") return REG_TYPE.BOOL; if (typename == "int16") return REG_TYPE.INT16; if (typename == "uint16") return REG_TYPE.UINT16; if (typename == "float") return REG_TYPE.FLOAT; return REG_TYPE.Unknown; } /// <summary> /// string 转modbus类型 /// </summary> /// <param name="dm"></param> /// <returns></returns> public static PLCAddressArea TranslateToPLCAddressArea(string dm) { if (dm.CompareTo("0") == 0) return PLCAddressArea.Coil; if (dm.CompareTo("1") == 0) return PLCAddressArea.Coil; if (dm.CompareTo("3") == 0) return PLCAddressArea.Register; if (dm.CompareTo("4") == 0) return PLCAddressArea.Register; else { throw new Exception("{0} 不是合法的 PLCAddressArea类型"); } } #endregion #region 寄存器读取更新计划 Dictionary<object, List<DataToRegs>> plans = new Dictionary<object, List<DataToRegs>>(); /// <summary> /// 登记寄存器更新计划 /// </summary> public void PlanAdd(object key, DataToRegs dr) { if (plans.ContainsKey(key)) { if (!plans[key].Contains(dr)) plans[key].Add(dr); } else { plans.Add(key, new List<DataToRegs>()); plans[key].Add(dr); } } /// <summary> /// 删除寄存器更新计划 /// </summary> /// <param name="key"></param> public void PlanRemove(object key) { plans.Remove(key); } /// <summary> /// 删除全部寄存器更新计划 /// </summary> public void PlanClear() { plans.Clear(); } /// <summary> /// 需要被更新的 寄存器数量 /// </summary> public int DRNeedUpdateCnt { get; private set; } /// <summary> /// 总寄存器数量 /// </summary> public int DRCnt { get; private set; } /// <summary> /// DRmap分配到 每个寄存器区 /// </summary> public void Build() { DRCnt = DRmap.Count; //TODO, 应该提前做!!!! foreach (var areaManager in mAreaManager) areaManager.BuildArray(DRmap); } /// <summary> /// 创建寄存器更新计划 /// </summary> public void PlanMake() { foreach (DataToRegs dr in DRmap) { dr.isNeedUpdate = false; } foreach (KeyValuePair<object, List<DataToRegs>> kv in plans) { foreach (DataToRegs dr in kv.Value) { dr.isNeedUpdate = true; } } //foreach (DataToRegs dr in DRmap) //{ // dr.isNeedUpdate = true; //} DRNeedUpdateCnt = DRmap.Count((dr) => dr.isNeedUpdate); foreach (var areaManager in mAreaManager) areaManager.MakePlan(); } #endregion } /// <summary> /// PLC 地址空间, 线圈 or 寄存器 /// </summary> public enum PLCAddressArea { /// <summary> /// 线圈 /// </summary> Coil, /// <summary> /// 寄存器 /// </summary> Register } /// <summary> /// 通知命名数据改变是的参数 /// </summary> public class NameDataChangedEventArgs : EventArgs { /// <summary> /// 初始化 NameDataChangedEventArgs 类的新实例。 /// </summary> /// <param name="owner">数据类</param> /// <param name="propertyName">已更改的数据名。</param> public NameDataChangedEventArgs(object owner, string propertyName) { this.owner = owner; this.propertyName = propertyName; } object owner; string propertyName; /// <summary> /// 获取数据所属类 /// </summary> public object Owener { get { return owner; } } /// <summary> /// 获取已更改的数据名名。 /// </summary> public string PropertyName { get { return propertyName; } } } /// <summary> /// NameData 改变事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public delegate void NameDataChangedEventHandler(object sender, DataToRegs e);// NameDataChangedEventArgs e); }