You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
15 KiB
471 lines
15 KiB
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
|
|
}
|
|
}
|