using Mirle.Component.MPLC.DataBlocks; using Mirle.Component.MPLC.DataType; using Mirle.Component.MPLC.Interfaces; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Mirle.Component.MPLC.MCProtocol { /// /// /// public class PLCHost : IDisposable, IMPLCProvider, IPLCHost { /// /// 建構式 /// /// public PLCHost(PLCHostInfo plcHostInfo) { HostInfo = plcHostInfo; foreach (var block in HostInfo.BlockInfos) { _cachedBlocks.Add(new DataBlock(block.DeviceRange)); } _mplc = new ReadWriteAdapter(HostInfo.HostID, HostInfo.IPAddress, HostInfo.TcpPort); _mplc.Connect(); _heartbeat = new ThreadWorker(RunProcess, 200, false); _writeRawdataWorker = new ThreadWorker(WriteRawdataProcess, 1000, false); } /// /// /// private class RawdataInfo { public DateTime Datatime { get; set; } public string Rawdata { get; set; } } /// /// /// public bool IsConnected => _mplc != null && _mplc.IsConnected; /// /// /// public PLCHostInfo HostInfo { get; } /// /// /// public bool EnableWriteShareMemory { get { return _enableWriteShareMemory; } set { _enableWriteShareMemory = value; if (_enableWriteShareMemory) { _smBlocks.Clear(); foreach (var block in HostInfo.BlockInfos) { _smBlocks.Add(new SMDataBlockInt32(block.DeviceRange, block.SharedMemoryName)); } } else { _smBlocks.Clear(); } } } /// /// /// public bool EnableWriteRawData { get; set; } = false; /// /// /// public bool RawDataUse16BitInteger { get; set; } = false; /// /// /// public bool RawDataUseCommaSeparated { get; set; } = false; /// /// /// public bool EnableAutoReconnect { get; set; } = true; /// /// /// public string LogBaseDirectory { get; set; } = $@"{AppDomain.CurrentDomain.BaseDirectory}LOG"; /// /// /// public int Interval { get => _heartbeat.Interval; set { if (value < 200) { _heartbeat.Interval = 200; } else if (value > 10000) { _heartbeat.Interval = 10000; } else { _heartbeat.Interval = value; } } } /// /// /// public int MPLCTimeout { get => _mplc?.Timeout ?? 600; set { if (value < 600) { _mplc.Timeout = 600; } else if (value > 60_000) { _mplc.Timeout = 60_000; } else { _mplc.Timeout = value; } } } /// /// /// private readonly ConcurrentQueue _rawdataQueue = new ConcurrentQueue(); /// /// /// private readonly List _cachedBlocks = new List(); /// /// /// private readonly List _smBlocks = new List(); /// /// 讀寫控制器 /// private readonly ReadWriteAdapter _mplc; /// /// 心跳包執行續 /// private readonly ThreadWorker _heartbeat; /// /// 讀寫執行續 /// private readonly ThreadWorker _writeRawdataWorker; /// /// 最後一個控制器字元 /// private string _lastMPLCWordRecord = string.Empty; /// /// 啟用寫入共享記憶體 /// private bool _enableWriteShareMemory = false; /// /// 開始處理流程 /// private void RunProcess() { try { ReadPLCDataFormPLC(); if (EnableWriteShareMemory) { WritePLCDataToSharedMemory(); } if (EnableWriteRawData) { ExportPLCData(); } if (_mplc.IsConnected == false) { _mplc.TestConnectionByPing(); } } catch (Exception ex) { Debug.WriteLine($"{ex}"); Task.Delay(1000).Wait(); } } /// /// 讀取控制器資料 /// private void ReadPLCDataFormPLC() { if (_mplc.IsConnected) { foreach (var block in _cachedBlocks) { block.SetRawData(_mplc.ReadWords(block.DeviceRange.StartAddress, block.DeviceRange.WordLength).ToBytes()); } } else if (EnableAutoReconnect) { _mplc.ReConnect(); } } /// /// 控制器資料寫入共享記憶體 /// private void WritePLCDataToSharedMemory() { for (int i = 0; i < _cachedBlocks.Count; i++) { _smBlocks[i].SetRawData(_cachedBlocks[i].GetRawData()); } } /// /// 匯出控制器資料 /// private void ExportPLCData() { StringBuilder sb = new StringBuilder(); if (RawDataUse16BitInteger) { foreach (DataBlock block in _cachedBlocks) { if (RawDataUseCommaSeparated) sb.Append(string.Join(",", block.GetRawData().To16BitInteger().Select(x => x.ToString("D5")))); else sb.Append(string.Concat(block.GetRawData().To16BitInteger().Select(x => x.ToString("D5")))); sb.Append('|'); } } else { foreach (DataBlock block in _cachedBlocks) { if (BitConverter.IsLittleEndian) { byte[] rawData = block.GetRawData(); byte[] values = new byte[rawData.Length]; for (int i1 = 0, i2 = i1 + 1; i2 < values.Length; i1 += 2, i2 = i1 + 1) { byte tmp = rawData[i1]; values[i1] = rawData[i2]; values[i2] = tmp; } if (RawDataUseCommaSeparated) sb.Append(BitConverter.ToString(values).Replace("-", ",")); else sb.Append(BitConverter.ToString(values).Replace("-", "")); } else { if (RawDataUseCommaSeparated) sb.Append(BitConverter.ToString(block.GetRawData()).Replace("-", ",")); else sb.Append(BitConverter.ToString(block.GetRawData()).Replace("-", "")); } sb.Append('|'); } } string strTemp = sb.ToString(); if (_lastMPLCWordRecord != strTemp) { sb.Append(strTemp); _rawdataQueue.Enqueue(new RawdataInfo() { Datatime = DateTime.Now, Rawdata = strTemp }); _lastMPLCWordRecord = strTemp; } } /// /// 寫入原始資料 /// private void WriteRawdataProcess() { try { if (_rawdataQueue.TryPeek(out var rawdataInfo)) { string logPath = LogBaseDirectory; if (logPath.EndsWith(@"\")) { if (logPath.EndsWith($@"{rawdataInfo.Datatime:yyyy-MM-dd}\") == false) logPath = $@"{logPath}{rawdataInfo.Datatime:yyyy-MM-dd}\{HostInfo.HostID}\"; else logPath = $@"{logPath}{HostInfo.HostID}\"; } else { if (logPath.EndsWith($@"{rawdataInfo.Datatime:yyyy-MM-dd}") == false) logPath = $@"{logPath}\{rawdataInfo.Datatime:yyyy-MM-dd}\{HostInfo.HostID}\"; else logPath = $@"{logPath}\{HostInfo.HostID}\"; } if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); string lastLogFilename = $@"{logPath}{HostInfo.HostID}_PLCR_RawData_{rawdataInfo.Datatime:yyyyMMddHH}.log"; using StreamWriter file = new StreamWriter(File.Open(lastLogFilename, FileMode.Append)); while (_rawdataQueue.TryPeek(out rawdataInfo)) { string logFilename = $@"{logPath}{HostInfo.HostID}_PLCR_RawData_{rawdataInfo.Datatime:yyyyMMddHH}.log"; if (lastLogFilename != logFilename) break; file.WriteLine($"[{rawdataInfo.Datatime:HH:mm:ss.fffff}] {rawdataInfo.Rawdata}"); _rawdataQueue.TryDequeue(out var result); } } } catch (Exception ex) { Debug.WriteLine($"{ex}"); if (_rawdataQueue.Count > 1000) { _rawdataQueue.TryDequeue(out var result); } } } /// /// 取得位元 /// /// 位置 /// True/False public bool GetBit(string address) { foreach (DataBlock block in _cachedBlocks) { if (block.TryGetBit(address, out bool value)) return value; } return false; } /// /// 設置位元開啟 /// /// 位置 public void SetBitOn(string address) { _mplc.SetBitOn(address); } /// /// 設置位元關閉 /// /// 位置 public void SetBitOff(string address) { _mplc.SetBitOff(address); } /// /// 讀取字元 /// /// 位置 /// 字元 public int ReadWord(string address) { foreach (DataBlock block in _cachedBlocks) { if (block.TryGetWord(address, out int value)) return value; } return 0; } /// /// 寫入字元 /// /// 位置 /// 字元 public void WriteWord(string address, int data) { _mplc.WriteWord(address, data); } /// /// 讀取多個字元 /// /// 起始位置 /// 長度 /// 字元陣列 public int[] ReadWords(string startAddress, int length) { foreach (DataBlock block in _cachedBlocks) { if (block.TryGetWords(startAddress, out int[] data, length)) return data; } return new int[length]; } /// /// 寫入多個字元 /// /// 起始位置 /// 字元陣列 public void WriteWords(string startAddress, int[] data) { _mplc.WriteWords(startAddress, data); } /// /// 取得控制器提供介面 /// /// 控制器提供介面 public IMPLCProvider GetMPLCProvider() { return _mplc; } /// /// 暫停 /// public void Stop() { _heartbeat.Pause(); _writeRawdataWorker.Pause(); } /// /// 開始 /// public void Start() { _heartbeat.Start(); _writeRawdataWorker.Start(); } #region IDisposable Support private bool _disposedValue = false; /// /// 釋放資源 /// /// protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _heartbeat.Dispose(); _mplc.Dispose(); _writeRawdataWorker.Dispose(); } _disposedValue = true; } } /// /// 解構式 /// ~PLCHost() { Dispose(false); } /// /// 釋放資源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } }