using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; namespace FLY.Modbus.WithThread { /// <summary> /// 使用线程版本 /// 支持自定义逻辑 /// </summary> public class ModbusMapper_Client : ModbusMapper { /// <summary> /// 更新周期,单位ms, /// </summary> public int UpdateInterval = 100; /// <summary> /// 实际更新间隔 /// </summary> public TimeSpan ActUpdateInterval { get; private set; } /// <summary> /// 工作中 /// </summary> public bool IsRunning { get; private set; } /// <summary> /// 异常次数 /// </summary> public int ErrorCnt { get; private set; } class RegWrite { public DataToRegs dr; public object val; public RegWrite(DataToRegs dr, object val) { this.dr = dr; this.val = val; } } List<RegWrite> rws = new List<RegWrite>(); /// <summary> /// tcp client /// </summary> public ClientTCP Client { get; set; } CancellationTokenSource cts; /// <summary> /// /// </summary> /// <param name="ep">服务器ep</param> public ModbusMapper_Client(IPEndPoint ep) { Client = new ClientTCP(ep); } /// <summary> /// /// </summary> /// <returns></returns> public override string ToString() { return Client.ToString(); } /// <summary> /// 启动 /// </summary> public void Start() { lock (this) { if (IsRunning) return; IsRunning = true; cts = new CancellationTokenSource(); Task.Factory.StartNew(OnPoll_update, cts.Token); } } /// <summary> /// 停止 /// </summary> public void Stop() { lock (this) { if (!IsRunning) return; cts.Cancel(); Client.Close(); } } void OnPoll_update() { Stopwatch stopwatch = new Stopwatch(); Stopwatch stopwatch_act = new Stopwatch(); bool isTimeForRead = true; _connect: while (true) { if (cts.IsCancellationRequested) goto _end; bool isconnected = Client.Connect(); if (cts.IsCancellationRequested) goto _end; if (isconnected) break; else Thread.Sleep(1000); } //刚连接成功。 //从本地PC 属性获取value 更新到 本地PLC 寄存器, //目的: 当从PLC读取寄存器值时,能比较不同,产生变化事件 foreach (var areaManager in mAreaManager) areaManager.SetAllIsPlcValueChanged(); //清空 写缓存 lock (rws) { rws.Clear(); } stopwatch.Restart(); isTimeForRead = true; while (true) { if (cts.IsCancellationRequested) goto _end; if (stopwatch.ElapsedMilliseconds >= UpdateInterval) { isTimeForRead = true; } if (isTimeForRead) { isTimeForRead = false; stopwatch.Restart(); if (!stopwatch_act.IsRunning) { stopwatch_act.Restart(); } else { ActUpdateInterval = stopwatch_act.Elapsed; stopwatch_act.Restart(); } if (!UpdateReadData()) { if (cts.IsCancellationRequested)//可能是外部强制关闭 goto _end; goto _error;//连接断开,终止更新线程 } } //输出写入数据 if (!UpdateWriteData()) { if (cts.IsCancellationRequested)//可能是外部强制关闭 goto _end; goto _error;//连接断开,终止更新线程 } Thread.Sleep(30); } _error: ErrorCnt++; goto _connect; _end: Client.Close(); IsRunning = false; } /// <summary> /// 更新一次读全部数据 /// </summary> /// <returns></returns> bool UpdateReadData() { foreach (var areaManager in mAreaManager) { areaManager.MakePlanReal(); } //int curr_area_idx = 0; foreach (var areaManager in mAreaManager) { foreach(Plan p_doing in areaManager.plan) { switch (p_doing.area) { case PLCAddressArea.Coil: { if (!Client.Do_01(p_doing.addr, p_doing.num, out IEnumerable<bool> values)) return false; SetModbusData(PLCAddressArea.Coil, p_doing.addr, values); } break; case PLCAddressArea.Register: { if (!Client.Do_03(p_doing.addr, p_doing.num, out IEnumerable<UInt16> values)) return false; SetModbusData(PLCAddressArea.Register, p_doing.addr, values); } break; } } } return true; } /// <summary> /// PC -> PLC /// 为命名数据赋值,会改变Modbus的寄存器值,并产生通知 /// </summary> /// <param name="dr">数据映射</param> /// <param name="val">数据值,不能为空</param> /// <returns></returns> public override void SetNameData(DataToRegs dr, object val) { RegWrite rw = new RegWrite(dr, val); lock (rws) { rws.Add(rw); } } /// <summary> /// 提取全部写命令缓冲区 输出 /// </summary> bool UpdateWriteData() { while (true) { RegWrite rw; lock (rws) { if (rws.Count() == 0) break; rw = rws.First(); rws.RemoveAt(0); } //强制令读时,推送数据 rw.dr.isPlcValueChanged = true; object plc_value = rw.dr.ToPLCObj(rw.val); switch (rw.dr.dataArea) { case PLCAddressArea.Coil: if (!Client.Do_0F((UInt16)rw.dr.addr, new bool[] {(bool)plc_value })) return false; break; default: //case PLCAddressArea.Register: if (!Client.Do_10((UInt16)rw.dr.addr, (UInt16[])plc_value)) return false; break; } } return true; } } }