PLCProxySystem.cs 14.1 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
        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#类型");
                }
89 90
                try
                {
91 92 93 94 95 96 97
                    object obj = null;
                    if (this.ObjNames.Count()!=0)
                    {
                        if(!this.ObjNames.ContainsKey(var.OwnerName))
                            throw new Exception($"配置文件出错,不能找到 对象 {var.OwnerName}");
                        obj = this.ObjNames[var.OwnerName];
                    }
98 99 100 101 102
                    DataToRegs dr = plc.MapDataToRegs(
                        ModbusMapper.TranslateToPLCAddressArea(var.Mode),
                        var.Addr,
                        reg_type,
                        var.Scale,
103
                        obj,
104 105 106 107 108 109 110 111 112
                        var.PropertyName,
                        var.RegAddr,
                        var.OwnerName);
                    if (dr != null)
                        drs.Add(dr);
                }
                catch (Exception e) {
                    throw e;
                }
113 114 115 116 117 118 119 120 121
            }

            foreach (var plc in this.PLCs)
                plc.Build();
        }

        /// <summary>
        /// PLC 更新计划服务初始化
        /// </summary>
潘栩锋's avatar
潘栩锋 committed
122 123
        public void Init()
        {
124
            foreach (var plc in PLCs)
潘栩锋's avatar
潘栩锋 committed
125
            {
126
                plc.NameDataChanged += Plc_NameDataChanged;
潘栩锋's avatar
潘栩锋 committed
127 128
            }

129 130 131 132 133


            //任何一个PLC断开,也会显示连接断开
            IsConnectedWithPLC = PLCs.All(plc => plc.Client.IsConnected);
            foreach (var plc in PLCs)
潘栩锋's avatar
潘栩锋 committed
134
            {
135 136 137 138 139 140 141
                plc.Client.PropertyChanged += (s, e) =>
                {
                    if (e.PropertyName == nameof(Modbus.WithThread.ClientTCP.IsConnected)) {
                        IsConnectedWithPLC = PLCs.All(p => p.Client.IsConnected);
                    }
                };
            }
潘栩锋's avatar
潘栩锋 committed
142 143 144 145 146 147 148 149

            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));
150 151 152 153 154

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

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

        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
187 188 189 190
        private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (isShield)
                return;
191
            List<DataToRegs> drs = DRMap;
潘栩锋's avatar
潘栩锋 committed
192

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

潘栩锋's avatar
潘栩锋 committed
197 198 199

            if (logger.IsInfoEnabled) 
            {
200
                if (!IgnoreLogProperties.Exists(sp => sp.sender == sender && sp.propertyName == e.PropertyName))
潘栩锋's avatar
潘栩锋 committed
201
                {
202
                    LogProperty(sender, e.PropertyName, dr);
潘栩锋's avatar
潘栩锋 committed
203 204 205
                }
            }

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

潘栩锋's avatar
潘栩锋 committed
211 212 213
        /// <summary>
        /// 屏蔽 "属性变化 写入PLC"
        /// </summary>
潘栩锋's avatar
潘栩锋 committed
214 215 216 217
        bool isShield = false;



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

224 225 226 227 228 229 230 231 232
            //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(() =>
233 234
            {
                isShield = true;
235 236 237 238
                if (dr.owner != null)
                {
                    Misc.PropertiesManager.SetValue(dr.owner, dr.propertyName, dr.value);
                }
239
                isShield = false;
240
            });
241
        }
242
        //delegate void SetValueHandler(object owner, string propertyname, object value);
潘栩锋's avatar
潘栩锋 committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
        /// <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;
            }
261
            
潘栩锋's avatar
潘栩锋 committed
262

263
            List<DataToRegs> drs = DRMap;
潘栩锋's avatar
潘栩锋 committed
264 265
            foreach (string propertyname in propertynames)
            {
266 267 268
                if (!ObjNames.ContainsKey(objname))
                    continue;
                DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == propertyname && _dr.owner == ObjNames[objname]); });
潘栩锋's avatar
潘栩锋 committed
269
                if (dr == null)
270
                {
潘栩锋's avatar
潘栩锋 committed
271
                    continue;
272 273 274
                    //throw new Exception($"PLCProxySystem.SetPlan 不能找到 {objname}.{propertyname}");
                }

潘栩锋's avatar
潘栩锋 committed
275 276 277 278
                dr.mapper.PlanAdd(planID, dr);
                if (!plan.mappers.Contains(dr.mapper))
                    plan.mappers.Add(dr.mapper);
            }
279
            foreach (var mapper in plan.mappers)
潘栩锋's avatar
潘栩锋 committed
280 281 282 283 284 285
            {
                mapper.PlanMake();
            }

        }

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
        /// <summary>
        /// 全更新
        /// </summary>
        /// <param name="planID"></param>
        public void SetPlanAll(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 (DataToRegs dr in drs)
            {
                dr.mapper.PlanAdd(planID, dr);
                if (!plan.mappers.Contains(dr.mapper))
                    plan.mappers.Add(dr.mapper);
            }
            foreach (var mapper in plan.mappers)
            {
                mapper.PlanMake();
            }
        }


318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
        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>
334
        public void SetPlan(string objname, IEnumerable<string> propertynames, AsyncCBHandler asyncDelegate, object asyncContext)
335 336 337
        {
            long planID = GetFreePlanID();
            SetPlan(objname, propertynames, planID);
338
            asyncDelegate?.Invoke(asyncContext, planID);
339
        }
潘栩锋's avatar
潘栩锋 committed
340 341 342 343 344 345 346 347 348 349 350 351 352 353
        /// <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>
354
        /// 主动删除某个计划
潘栩锋's avatar
潘栩锋 committed
355
        /// </summary>
356 357
        /// <param name="planID"></param>
        public void RemovePlan(long planID)
潘栩锋's avatar
潘栩锋 committed
358
        {
359
            ClearPlan((plan) => (plan.ID == planID));
潘栩锋's avatar
潘栩锋 committed
360
        }
361

潘栩锋's avatar
潘栩锋 committed
362
        /// <summary>
363
        /// 清除 非0 全部计划
潘栩锋's avatar
潘栩锋 committed
364
        /// </summary>
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
        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
380 381 382 383 384
        {
            if (planIDs.Count() > 0)
            {
                var _remove =
                    from plan in planIDs
385
                    where condition(plan)
潘栩锋's avatar
潘栩锋 committed
386
                    select plan;
387

潘栩锋's avatar
潘栩锋 committed
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
                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();
                    }
                }
            }
        }
410 411 412 413 414 415 416 417 418
        
        /// <summary>
        /// 1s 一次,看出哪个令牌过期
        /// </summary>
        void OnPoll_plans()
        {
            DateTime now = DateTime.Now;
            ClearPlan((plan) => (plan.ID != 0) && (now - plan.FeedTime) > TimeSpan.FromMinutes(2));
        }
潘栩锋's avatar
潘栩锋 committed
419 420 421

        public string[] GetSyncPropNames()
        {
422
            return new string[] { nameof(IsConnectedWithPLC) };
潘栩锋's avatar
潘栩锋 committed
423 424 425 426 427 428 429
        }

        public string[] GetNoSyncPropNames()
        {
            return null;
        }
    }
430 431 432 433 434 435

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