PLCProxySystem.cs 12.6 KB
Newer Older
潘栩锋's avatar
潘栩锋 committed
1 2 3 4 5 6 7
using FLY.OBJComponents.IService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using FLY.Modbus;
8
using System.Windows.Threading;
9 10
using System.IO;
using Newtonsoft.Json;
11
using FObjBase;
潘栩锋's avatar
潘栩锋 committed
12 13 14 15 16 17 18 19

namespace FLY.OBJComponents.Server
{
    /// <summary>
    /// 属性更新计划
    /// </summary>
    public class PLCProxySystem : IPLCProxySystemService, IPropertyOpt
    {
潘栩锋's avatar
潘栩锋 committed
20
        NLog.ILogger logger = NLog.LogManager.GetCurrentClassLogger();
21 22 23 24
        /// <summary>
        /// 用于调试, 在这个表内的属性不输出调试
        /// </summary>
        public List<SenderProperty> IgnoreLogProperties = new List<SenderProperty>();
潘栩锋's avatar
潘栩锋 committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
        class Plan
        {
            /// <summary>
            /// 唯一ID
            /// </summary>
            public long ID;
            /// <summary>
            /// 上一次喂的时间
            /// </summary>
            public DateTime FeedTime;
            /// <summary>
            /// plc
            /// </summary>
            public List<ModbusMapper> mappers = new List<ModbusMapper>();
        }
潘栩锋's avatar
潘栩锋 committed
40

潘栩锋's avatar
潘栩锋 committed
41
        List<Plan> planIDs = new List<Plan>();
42 43
        public List<DataToRegs> DRMap = new List<DataToRegs>();
        public List<Modbus.WithThread.ModbusMapper_Client> PLCs = new List<Modbus.WithThread.ModbusMapper_Client>();
44
        public Dictionary<string, INotifyPropertyChanged> ObjNames { get; private set; } = new Dictionary<string, INotifyPropertyChanged>();
潘栩锋's avatar
潘栩锋 committed
45 46 47 48 49 50 51 52 53 54 55 56 57

        /// <summary>
        /// 与PLC连接成功
        /// </summary>
        public bool IsConnectedWithPLC { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public PLCProxySystem()
        {


        }
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
        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>
潘栩锋's avatar
潘栩锋 committed
107 108
        public void Init()
        {
109
            foreach (var plc in PLCs)
潘栩锋's avatar
潘栩锋 committed
110
            {
111
                plc.NameDataChanged += Plc_NameDataChanged;
潘栩锋's avatar
潘栩锋 committed
112 113
            }

114 115 116 117 118


            //任何一个PLC断开,也会显示连接断开
            IsConnectedWithPLC = PLCs.All(plc => plc.Client.IsConnected);
            foreach (var plc in PLCs)
潘栩锋's avatar
潘栩锋 committed
119
            {
120 121 122 123 124 125 126
                plc.Client.PropertyChanged += (s, e) =>
                {
                    if (e.PropertyName == nameof(Modbus.WithThread.ClientTCP.IsConnected)) {
                        IsConnectedWithPLC = PLCs.All(p => p.Client.IsConnected);
                    }
                };
            }
潘栩锋's avatar
潘栩锋 committed
127 128 129 130 131 132 133 134 135

            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));
136 137 138 139 140

            //启动!!!!
            foreach (var plc in PLCs)
                plc.Start();
            //PLCs[0].Start();
潘栩锋's avatar
潘栩锋 committed
141 142
        }

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

        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})");
        }
潘栩锋's avatar
潘栩锋 committed
173 174 175 176
        private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (isShield)
                return;
177
            List<DataToRegs> drs = DRMap;
潘栩锋's avatar
潘栩锋 committed
178

179
            DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == e.PropertyName) && (_dr.owner == sender); });
潘栩锋's avatar
潘栩锋 committed
180 181 182
            if (dr == null)
                return;//不会刷新PLC

潘栩锋's avatar
潘栩锋 committed
183 184 185

            if (logger.IsInfoEnabled) 
            {
186
                if (!IgnoreLogProperties.Exists(sp => sp.sender == sender && sp.propertyName == e.PropertyName))
潘栩锋's avatar
潘栩锋 committed
187
                {
188
                    LogProperty(sender, e.PropertyName, dr);
潘栩锋's avatar
潘栩锋 committed
189 190 191
                }
            }

192
            //向PLC写入数据,mRegs每次只能写入一个,它有列表保存功能。
潘栩锋's avatar
潘栩锋 committed
193 194 195 196
            //SetNameData可以执行很多次,不过它只能一个个发。
            dr.mapper.SetNameData(dr, Misc.PropertiesManager.GetValue(sender, e.PropertyName));
        }

潘栩锋's avatar
潘栩锋 committed
197 198 199
        /// <summary>
        /// 屏蔽 "属性变化 写入PLC"
        /// </summary>
潘栩锋's avatar
潘栩锋 committed
200 201 202 203
        bool isShield = false;



204
        void Plc_NameDataChanged(object sender, DataToRegs dr)// NameDataChangedEventArgs e)
潘栩锋's avatar
潘栩锋 committed
205
        {
206 207 208
            //var plc = sender as Modbus.WithThread.ModbusMapper_Client;
            //object value = plc.GetNameData(e.Owener, e.PropertyName);
            //这是从PLC 更新过来的,不需要向PLC写入
潘栩锋's avatar
潘栩锋 committed
209

210 211 212 213 214 215 216 217 218
            //FObjBase.PollModule.Current.Dispatcher.Invoke(new SetValueHandler((owner, propertyName, value) =>
            //{
            //    isShield = true;
            //    Misc.PropertiesManager.SetValue(owner, propertyName, value);
            //    isShield = false;

            //}), dr.owner, dr.propertyName, dr.value);

            FObjBase.PollModule.Current.Dispatcher.Invoke(() =>
219 220 221 222
            {
                isShield = true;
                Misc.PropertiesManager.SetValue(dr.owner, dr.propertyName, dr.value);
                isShield = false;
223
            });
224
        }
225
        //delegate void SetValueHandler(object owner, string propertyname, object value);
潘栩锋's avatar
潘栩锋 committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
        /// <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;
            }
244
            
潘栩锋's avatar
潘栩锋 committed
245

246
            List<DataToRegs> drs = DRMap;
潘栩锋's avatar
潘栩锋 committed
247 248
            foreach (string propertyname in propertynames)
            {
249 250 251
                if (!ObjNames.ContainsKey(objname))
                    continue;
                DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == propertyname && _dr.owner == ObjNames[objname]); });
潘栩锋's avatar
潘栩锋 committed
252
                if (dr == null)
253
                {
潘栩锋's avatar
潘栩锋 committed
254
                    continue;
255 256 257
                    //throw new Exception($"PLCProxySystem.SetPlan 不能找到 {objname}.{propertyname}");
                }

潘栩锋's avatar
潘栩锋 committed
258 259 260 261
                dr.mapper.PlanAdd(planID, dr);
                if (!plan.mappers.Contains(dr.mapper))
                    plan.mappers.Add(dr.mapper);
            }
262
            foreach (var mapper in plan.mappers)
潘栩锋's avatar
潘栩锋 committed
263 264 265 266 267 268
            {
                mapper.PlanMake();
            }

        }

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
        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>
285
        public void SetPlan(string objname, IEnumerable<string> propertynames, AsyncCBHandler asyncDelegate, object asyncContext)
286 287 288
        {
            long planID = GetFreePlanID();
            SetPlan(objname, propertynames, planID);
289
            asyncDelegate?.Invoke(asyncContext, planID);
290
        }
潘栩锋's avatar
潘栩锋 committed
291 292 293 294 295 296 297 298 299 300 301 302 303 304
        /// <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>
305
        /// 主动删除某个计划
潘栩锋's avatar
潘栩锋 committed
306
        /// </summary>
307 308
        /// <param name="planID"></param>
        public void RemovePlan(long planID)
潘栩锋's avatar
潘栩锋 committed
309
        {
310
            ClearPlan((plan) => (plan.ID == planID));
潘栩锋's avatar
潘栩锋 committed
311
        }
312

潘栩锋's avatar
潘栩锋 committed
313
        /// <summary>
314
        /// 清除 非0 全部计划
潘栩锋's avatar
潘栩锋 committed
315
        /// </summary>
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
        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)
潘栩锋's avatar
潘栩锋 committed
331 332 333 334 335
        {
            if (planIDs.Count() > 0)
            {
                var _remove =
                    from plan in planIDs
336
                    where condition(plan)
潘栩锋's avatar
潘栩锋 committed
337
                    select plan;
338

潘栩锋's avatar
潘栩锋 committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
                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();
                    }
                }
            }
        }
361 362 363 364 365 366 367 368 369
        
        /// <summary>
        /// 1s 一次,看出哪个令牌过期
        /// </summary>
        void OnPoll_plans()
        {
            DateTime now = DateTime.Now;
            ClearPlan((plan) => (plan.ID != 0) && (now - plan.FeedTime) > TimeSpan.FromMinutes(2));
        }
潘栩锋's avatar
潘栩锋 committed
370 371 372

        public string[] GetSyncPropNames()
        {
373
            return new string[] { nameof(IsConnectedWithPLC) };
潘栩锋's avatar
潘栩锋 committed
374 375 376 377 378 379 380
        }

        public string[] GetNoSyncPropNames()
        {
            return null;
        }
    }
381 382 383 384 385 386

    public class SenderProperty 
    {
        public object sender;
        public string propertyName;
    }
潘栩锋's avatar
潘栩锋 committed
387
}