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;
}
}