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 { /// <summary> /// 属性更新计划 /// </summary> public class PLCProxySystem : IPLCProxySystemService, IPropertyOpt { NLog.ILogger logger = NLog.LogManager.GetCurrentClassLogger(); /// <summary> /// 用于调试, 在这个表内的属性不输出调试 /// </summary> public List<SenderProperty> IgnoreLogProperties = new List<SenderProperty>(); class Plan { /// <summary> /// 唯一ID /// </summary> public long ID; /// <summary> /// 上一次喂的时间 /// </summary> public DateTime FeedTime; /// <summary> /// plc /// </summary> public List<ModbusMapper> mappers = new List<ModbusMapper>(); } List<Plan> planIDs = new List<Plan>(); public List<DataToRegs> DRMap = new List<DataToRegs>(); public List<Modbus.WithThread.ModbusMapper_Client> PLCs = new List<Modbus.WithThread.ModbusMapper_Client>(); public Dictionary<string, INotifyPropertyChanged> ObjNames { get; private set; } = new Dictionary<string, INotifyPropertyChanged>(); /// <summary> /// 与PLC连接成功 /// </summary> public bool IsConnectedWithPLC { get; private set; } public event PropertyChangedEventHandler PropertyChanged; public PLCProxySystem() { } public void AddConfigFile( string configfile, Func<PLCGroup,Dictionary<string,INotifyPropertyChanged>> 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<DataToRegs> 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(); } /// <summary> /// PLC 更新计划服务初始化 /// </summary> 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<DataToRegs> 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)); } /// <summary> /// 屏蔽 "属性变化 写入PLC" /// </summary> 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); /// <summary> /// 设置更新计划 /// </summary> /// <param name="objname">对象名称</param> /// <param name="propertynames"></param> /// <param name="planID">计划的编号,应该全局唯一,建议使用时间ticks</param> public void SetPlan(string objname, IEnumerable<string> 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<DataToRegs> 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; } /// <summary> /// 设置更新计划 /// </summary> /// <param name="objname">对象名称</param> /// <param name="propertynames"></param> /// <param name="planID">计划的编号,应该全局唯一,建议使用时间ticks</param> public void SetPlan(string objname, IEnumerable<string> propertynames, SetPlanReponseHandler reponseHandler, object context) { long planID = GetFreePlanID(); SetPlan(objname, propertynames, planID); reponseHandler(planID, context); } /// <summary> /// 更新计划持续,如果不喂狗,20s后停止更新数据 /// </summary> /// <param name="planID">计划的编号</param> public void FeedPlan(long planID) { Plan plan = planIDs.Find(p => p.ID == planID); if (plan != null) { plan.FeedTime = DateTime.Now; } } /// <summary> /// 主动删除某个计划 /// </summary> /// <param name="planID"></param> public void RemovePlan(long planID) { ClearPlan((plan) => (plan.ID == planID)); } /// <summary> /// 清除 非0 全部计划 /// </summary> 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<Plan, bool> condition) { if (planIDs.Count() > 0) { var _remove = from plan in planIDs where condition(plan) select plan; if (_remove.Count() > 0) { List<Plan> remove = new List<Plan>(_remove); List<ModbusMapper> mappers = new List<ModbusMapper>(); 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(); } } } } /// <summary> /// 1s 一次,看出哪个令牌过期 /// </summary> 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; } }