using FLY.OBJComponents.IService; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using FLY.Modbus; using System.Windows.Threading; using System.IO; using Newtonsoft.Json; namespace FLY.OBJComponents.Server { /// /// 属性更新计划 /// public class PLCProxySystem : IPLCProxySystemService, IPropertyOpt { NLog.ILogger logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 用于调试, 在这个表内的属性不输出调试 /// public List IgnoreLogProperties = new List(); class Plan { /// /// 唯一ID /// public long ID; /// /// 上一次喂的时间 /// public DateTime FeedTime; /// /// plc /// public List mappers = new List(); } List planIDs = new List(); public List DRMap = new List(); public List PLCs = new List(); public Dictionary ObjNames { get; private set; } = new Dictionary(); /// /// 与PLC连接成功 /// public bool IsConnectedWithPLC { get; private set; } public event PropertyChangedEventHandler PropertyChanged; public PLCProxySystem() { } public void AddConfigFile( string configfile, Func> addObjNames) { PLCGroup plcgroup = new PLCGroup(); string json = File.ReadAllText(configfile); JsonConvert.PopulateObject(json, plcgroup); foreach (PLCGroup.PLCDevice device in plcgroup.Devices) { var plc = new Modbus.WithThread.ModbusMapper_Client(device.EP); this.PLCs.Add(plc); } var objnames = addObjNames(plcgroup); this.ObjNames = objnames; foreach (PLCGroup.PLCVariable var in plcgroup.Variables) { if (var.DeviceIndex < 0 || var.DeviceIndex >= this.PLCs.Count) continue; List drs = this.DRMap; var plc = this.PLCs[var.DeviceIndex]; var reg_type = ModbusMapper.TranslateToREG_TYPE(var.Type); if (reg_type == REG_TYPE.Unknown) { throw new Exception($"DeviceIndex = {var.DeviceIndex} var.Addr={var.Addr} var.Type={var.Type} 不能转换为 C#类型"); } DataToRegs dr = plc.MapDataToRegs( ModbusMapper.TranslateToPLCAddressArea(var.Mode), var.Addr, reg_type, var.Scale, this.ObjNames[var.OwnerName], var.PropertyName); if (dr != null) drs.Add(dr); } foreach (var plc in this.PLCs) plc.Build(); } /// /// PLC 更新计划服务初始化 /// public void Init() { foreach (var plc in PLCs) { plc.NameDataChanged += Plc_NameDataChanged; } Misc.BindingOperations.SetBinding(PLCs[0].Client, "IsConnected", () => { IsConnectedWithPLC = PLCs[0].Client.IsConnected; //if (!IsConnectedWithPLC) // ClearPlan(); }); foreach (var obj in ObjNames.Values) { obj.PropertyChanged += Obj_PropertyChanged; } FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD, OnPoll_plans, TimeSpan.FromSeconds(1)); //启动!!!! foreach (var plc in PLCs) plc.Start(); //PLCs[0].Start(); } void LogProperty(object sender, string propertyName, DataToRegs dr) { //注解 var property = sender.GetType().GetProperty(propertyName); var attrs = property.GetCustomAttributes(typeof(DescriptionAttribute), false); string desp = ""; if (attrs.Count() > 0) { desp = (attrs.First() as DescriptionAttribute).Description; } //新值 var new_value = property.GetValue(sender, null); //旧值 var old_value = dr.mapper.GetNameData(sender, propertyName); //PLC地址 string plcAddr = $"{(dr.dataArea == PLCAddressArea.Coil ? "M" : "D")}{dr.addr}"; //对象名称 string objName = ""; var names = from kv in ObjNames where kv.Value == sender select kv.Key; if (names.Count() > 0) objName = names.First(); logger.Info($"{objName}.{propertyName} [{desp}] [{plcAddr}] ({old_value})->({new_value})"); } private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (isShield) return; List drs = DRMap; DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == e.PropertyName) && (_dr.owner == sender); }); if (dr == null) return;//不会刷新PLC if (logger.IsInfoEnabled) { if (!IgnoreLogProperties.Exists(sp => sp.sender == sender && sp.propertyName == e.PropertyName)) { LogProperty(sender, e.PropertyName, dr); } } //向PLC写入数据,mRegs每次只能写入一个,它有列表保存功能。 //SetNameData可以执行很多次,不过它只能一个个发。 dr.mapper.SetNameData(dr, Misc.PropertiesManager.GetValue(sender, e.PropertyName)); } /// /// 屏蔽 "属性变化 写入PLC" /// bool isShield = false; void Plc_NameDataChanged(object sender, DataToRegs dr)// NameDataChangedEventArgs e) { //var plc = sender as Modbus.WithThread.ModbusMapper_Client; //object value = plc.GetNameData(e.Owener, e.PropertyName); //这是从PLC 更新过来的,不需要向PLC写入 FObjBase.PollModule.Current.Dispatcher.Invoke(new SetValueHandler((owner, propertyname, value) => { isShield = true; Misc.PropertiesManager.SetValue(dr.owner, dr.propertyName, dr.value); isShield = false; }), dr.owner, dr.propertyName, dr.value); } delegate void SetValueHandler(object owner, string propertyname, object value); /// /// 设置更新计划 /// /// 对象名称 /// /// 计划的编号,应该全局唯一,建议使用时间ticks public void SetPlan(string objname, IEnumerable propertynames, long planID) { Plan plan = planIDs.Find(p => p.ID == planID); if (plan == null) { plan = new Plan() { ID = planID, FeedTime = DateTime.Now }; planIDs.Add(plan); } else { plan.FeedTime = DateTime.Now; } List drs = DRMap; foreach (string propertyname in propertynames) { if (!ObjNames.ContainsKey(objname)) continue; DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == propertyname && _dr.owner == ObjNames[objname]); }); if (dr == null) { continue; //throw new Exception($"PLCProxySystem.SetPlan 不能找到 {objname}.{propertyname}"); } dr.mapper.PlanAdd(planID, dr); if (!plan.mappers.Contains(dr.mapper)) plan.mappers.Add(dr.mapper); } foreach (var mapper in plan.mappers) { mapper.PlanMake(); } } long freeplanid = 1; long GetFreePlanID() { long planid = freeplanid; freeplanid++; if (freeplanid == 0) freeplanid = 1; return planid; } /// /// 设置更新计划 /// /// 对象名称 /// /// 计划的编号,应该全局唯一,建议使用时间ticks public void SetPlan(string objname, IEnumerable propertynames, SetPlanReponseHandler reponseHandler, object context) { long planID = GetFreePlanID(); SetPlan(objname, propertynames, planID); reponseHandler(planID, context); } /// /// 更新计划持续,如果不喂狗,20s后停止更新数据 /// /// 计划的编号 public void FeedPlan(long planID) { Plan plan = planIDs.Find(p => p.ID == planID); if (plan != null) { plan.FeedTime = DateTime.Now; } } /// /// 主动删除某个计划 /// /// public void RemovePlan(long planID) { ClearPlan((plan) => (plan.ID == planID)); } /// /// 清除 非0 全部计划 /// void ClearPlan() { //if (planIDs.Count() > 0) //{ // foreach (var plc in PLCs) // { // plc.PlanClear(); // plc.PlanMake(); // } // planIDs.Clear(); //} ClearPlan((plan) => (plan.ID != 0)); } void ClearPlan(Func condition) { if (planIDs.Count() > 0) { var _remove = from plan in planIDs where condition(plan) select plan; if (_remove.Count() > 0) { List remove = new List(_remove); List mappers = new List(); foreach (var plan in remove) { planIDs.Remove(plan); foreach (var mapper in plan.mappers) { mapper.PlanRemove(plan.ID); if (!mappers.Contains(mapper)) mappers.Add(mapper); } } foreach (var mapper in mappers) { mapper.PlanMake(); } } } } /// /// 1s 一次,看出哪个令牌过期 /// void OnPoll_plans() { DateTime now = DateTime.Now; ClearPlan((plan) => (plan.ID != 0) && (now - plan.FeedTime) > TimeSpan.FromMinutes(2)); } public string[] GetSyncPropNames() { return new string[] { "IsConnectedWithPLC" }; } public string[] GetNoSyncPropNames() { return null; } } public class SenderProperty { public object sender; public string propertyName; } }