|
|
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 { /// <summary>
///
/// </summary>
public class PLCHost : IDisposable, IMPLCProvider, IPLCHost { /// <summary>
/// 建構式
/// </summary>
/// <param name="plcHostInfo"></param>
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); } /// <summary>
///
/// </summary>
private class RawdataInfo { public DateTime Datatime { get; set; } public string Rawdata { get; set; } } /// <summary>
///
/// </summary>
public bool IsConnected => _mplc != null && _mplc.IsConnected; /// <summary>
///
/// </summary>
public PLCHostInfo HostInfo { get; } /// <summary>
///
/// </summary>
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(); } } } /// <summary>
///
/// </summary>
public bool EnableWriteRawData { get; set; } = false; /// <summary>
///
/// </summary>
public bool RawDataUse16BitInteger { get; set; } = false; /// <summary>
///
/// </summary>
public bool RawDataUseCommaSeparated { get; set; } = false; /// <summary>
///
/// </summary>
public bool EnableAutoReconnect { get; set; } = true; /// <summary>
///
/// </summary>
public string LogBaseDirectory { get; set; } = $@"{AppDomain.CurrentDomain.BaseDirectory}LOG"; /// <summary>
///
/// </summary>
public int Interval { get => _heartbeat.Interval; set { if (value < 200) { _heartbeat.Interval = 200; } else if (value > 10000) { _heartbeat.Interval = 10000; } else { _heartbeat.Interval = value; } } } /// <summary>
///
/// </summary>
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; } } } /// <summary>
///
/// </summary>
private readonly ConcurrentQueue<RawdataInfo> _rawdataQueue = new ConcurrentQueue<RawdataInfo>(); /// <summary>
///
/// </summary>
private readonly List<DataBlock> _cachedBlocks = new List<DataBlock>(); /// <summary>
///
/// </summary>
private readonly List<SMDataBlockInt32> _smBlocks = new List<SMDataBlockInt32>(); /// <summary>
/// 讀寫控制器
/// </summary>
private readonly ReadWriteAdapter _mplc; /// <summary>
/// 心跳包執行續
/// </summary>
private readonly ThreadWorker _heartbeat; /// <summary>
/// 讀寫執行續
/// </summary>
private readonly ThreadWorker _writeRawdataWorker; /// <summary>
/// 最後一個控制器字元
/// </summary>
private string _lastMPLCWordRecord = string.Empty; /// <summary>
/// 啟用寫入共享記憶體
/// </summary>
private bool _enableWriteShareMemory = false; /// <summary>
/// 開始處理流程
/// </summary>
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(); } } /// <summary>
/// 讀取控制器資料
/// </summary>
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(); } } /// <summary>
/// 控制器資料寫入共享記憶體
/// </summary>
private void WritePLCDataToSharedMemory() { for (int i = 0; i < _cachedBlocks.Count; i++) { _smBlocks[i].SetRawData(_cachedBlocks[i].GetRawData()); } } /// <summary>
/// 匯出控制器資料
/// </summary>
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; } } /// <summary>
/// 寫入原始資料
/// </summary>
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); } } } /// <summary>
/// 取得位元
/// </summary>
/// <param name="address">位置</param>
/// <returns>True/False</returns>
public bool GetBit(string address) { foreach (DataBlock block in _cachedBlocks) { if (block.TryGetBit(address, out bool value)) return value; } return false; } /// <summary>
/// 設置位元開啟
/// </summary>
/// <param name="address">位置</param>
public void SetBitOn(string address) { _mplc.SetBitOn(address); } /// <summary>
/// 設置位元關閉
/// </summary>
/// <param name="address">位置</param>
public void SetBitOff(string address) { _mplc.SetBitOff(address); } /// <summary>
/// 讀取字元
/// </summary>
/// <param name="address">位置</param>
/// <returns>字元</returns>
public int ReadWord(string address) { foreach (DataBlock block in _cachedBlocks) { if (block.TryGetWord(address, out int value)) return value; } return 0; } /// <summary>
/// 寫入字元
/// </summary>
/// <param name="address">位置</param>
/// <param name="data">字元</param>
public void WriteWord(string address, int data) { _mplc.WriteWord(address, data); } /// <summary>
/// 讀取多個字元
/// </summary>
/// <param name="startAddress">起始位置</param>
/// <param name="length">長度</param>
/// <returns>字元陣列</returns>
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]; } /// <summary>
/// 寫入多個字元
/// </summary>
/// <param name="startAddress">起始位置</param>
/// <param name="data">字元陣列</param>
public void WriteWords(string startAddress, int[] data) { _mplc.WriteWords(startAddress, data); } /// <summary>
/// 取得控制器提供介面
/// </summary>
/// <returns>控制器提供介面</returns>
public IMPLCProvider GetMPLCProvider() { return _mplc; } /// <summary>
/// 暫停
/// </summary>
public void Stop() { _heartbeat.Pause(); _writeRawdataWorker.Pause(); } /// <summary>
/// 開始
/// </summary>
public void Start() { _heartbeat.Start(); _writeRawdataWorker.Start(); }
#region IDisposable Support
private bool _disposedValue = false; /// <summary>
/// 釋放資源
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _heartbeat.Dispose(); _mplc.Dispose(); _writeRawdataWorker.Dispose(); } _disposedValue = true; } } /// <summary>
/// 解構式
/// </summary>
~PLCHost() { Dispose(false); } /// <summary>
/// 釋放資源
/// </summary>
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
#endregion
} }
|