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

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