using HslCommunication;
using HslCommunication.Core;
using HslCommunication.LogNet;
using HslCommunication.ModBus;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace WIDESEAWCS_Communicator
{
///
/// ModbusTcp通讯类
///
[Description("ModbusTcp")]
public class ModbusTcp : BaseCommunicator
{
#region PrivateMember
private ModbusTcpNet plc;
private string _ipAddress;
private int _port;
private bool _connected;
private string _name;
private ILogNet _logNet;
private bool _isPing = true;
#endregion
#region PublicMember
///
/// 日志记录实例对象
///
public override ILogNet LogNet => _logNet;
///
/// 设备名称
///
public override string Name => _name;
///
/// 获取当前通讯器是否已连接到PLC。
///
public override bool IsConnected => _connected;
///
/// 是否在写入数据后读取数据确认。
///
public override bool IsReadAfterWrite { get; set; } = true;
#endregion
#region Constructor Function
///
/// 构造函数
///
/// 设备的IP地址
/// 连接使用的端口号
/// 设备名称
public ModbusTcp(string ipAddress, int port, string name)
{
string path = AppDomain.CurrentDomain.BaseDirectory + $"Log_PLCReadWrite\\{name}";
_logNet = new LogNetFileSize(path, 10 * 1024 * 1024, 100);
bool ipCheck = IPAddress.TryParse(ipAddress, out IPAddress? address);
if (!ipCheck)
{
_logNet.WriteError(name, string.Format(CommunicationExceptionMessage.IpAddressErrorException, ipAddress));
throw new CommunicationException(string.Format(CommunicationExceptionMessage.IpAddressErrorException, ipAddress), CommunicationErrorType.IpAddressError);
}
_ipAddress = ipAddress;//通过构造函数赋值设备的IP地址
_port = port;//通过构造函数赋值连接使用的端口号
_name = name;
}
#endregion
#region Private Method
///
/// 从OperateResult对象中获取读取的数据。
///
/// 读取的数据类型。
/// HSLCommunication读取的OperateResult对象
/// 如果读取成功,返回读取结果,读取失败,抛出自定义通讯异常
/// 自定义通讯异常类
private object GetContent(OperateResult operateResult, string address)
{
try
{
if (!operateResult.IsSuccess)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadFailedException, typeof(T).Name, address, operateResult.Message), CommunicationErrorType.ReadFailed);
}
return operateResult.Content ?? throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadDataIsNull, address), CommunicationErrorType.ReadFailed);
}
catch (Exception ex)
{
LogNet.WriteException(Name, ex.Message, ex);
throw new CommunicationException(ex.Message, CommunicationErrorType.ReadFailed, innerException: ex);
}
}
///
///
///
///
///
///
///
///
///
private bool GetResult(OperateResult operateResult, string address, T value) where T : notnull
{
StringBuilder stringBuilder = new StringBuilder();
try
{
stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteData, address, value));
if (!operateResult.IsSuccess)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, value, operateResult.Message), CommunicationErrorType.WriteFailed);
}
else
{
if (IsReadAfterWrite)
{
object? obj = null;
for (int i = 0; i < 5; i++)
{
T readValue = Read(address);
stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAfterRead, readValue, value));
obj = readValue;
if (readValue.Equals(value))
{
stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAndReadCheckSuccess, address, value, readValue));
return true;
}
else if (i < 4)
{
Write(address, value);
}
}
stringBuilder.AppendLine(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, value, obj));
throw new CommunicationException(stringBuilder.ToString(), CommunicationErrorType.WriteFailed);
}
else
{
return true;
}
}
}
catch (Exception ex)
{
LogNet.WriteException(Name, ex.Message, ex);
throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex);
}
finally
{
LogNet.WriteInfo(Name, stringBuilder.ToString());
}
}
///
/// 写入数据
///
///
///
///
///
private OperateResult Write(string address, object value)
{
try
{
Type type = value.GetType();
switch (Type.GetTypeCode(type))
{
case TypeCode.Int32:
return plc.Write(address, Convert.ToInt32(value));
case TypeCode.UInt32:
return plc.Write(address, Convert.ToUInt32(value));
case TypeCode.Int16:
return plc.Write(address, Convert.ToInt16(value));
case TypeCode.UInt16:
return plc.Write(address, Convert.ToUInt16(value));
case TypeCode.Single:
return plc.Write(address, Convert.ToSingle(value));
case TypeCode.Boolean:
return plc.Write(address, Convert.ToBoolean(value));
case TypeCode.Byte:
return plc.Write(address, Convert.ToByte(value));
case TypeCode.String:
return plc.Write(address, Convert.ToString(value));
case TypeCode.Char:
return plc.Write(address, Convert.ToChar(value));
default:
if (value is int[])
{
return plc.Write(address, value as int[]);
}
else if (value is uint[])
{
return plc.Write(address, value as uint[]);
}
else if (value is short[])
{
return plc.Write(address, value as short[]);
}
else if (value is ushort[])
{
return plc.Write(address, value as ushort[]);
}
else if (value is bool[])
{
return plc.Write(address, value as bool[]);
}
else if (value is float[])
{
return plc.Write(address, value as float[]);
}
else if (value is double[])
{
return plc.Write(address, value as double[]);
}
else if (value is byte[])
{
return plc.Write(address, value as byte[]);
}
throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, type.Name, address), CommunicationErrorType.TypeError);
}
}
catch (CommunicationException ex)
{
throw new CommunicationException(ex.Message, ex.ErrorType);
}
catch (Exception ex)
{
//读取异常时抛出自定义通讯异常类
throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, address, value), CommunicationErrorType.TypeError, innerException: ex);
}
}
private object Read(string address, TypeCode typeCode, ushort length = 1)
{
try
{
switch (typeCode)
{
case TypeCode.Int32:
if (length > 1)
return (int[])GetContent(plc.ReadInt32(address, length), address);
else
return (int)GetContent(plc.ReadInt32(address), address);
case TypeCode.UInt32:
if (length > 1)
return (uint[])GetContent(plc.ReadUInt32(address, length), address);
else
return (uint)GetContent(plc.ReadUInt32(address), address);
case TypeCode.Int16:
if (length > 1)
return (short[])GetContent(plc.ReadInt16(address, length), address);
else
return (short)GetContent(plc.ReadInt16(address), address);
case TypeCode.UInt16:
if (length > 1)
return (ushort[])GetContent(plc.ReadUInt16(address, length), address);
else
return (ushort)GetContent(plc.ReadUInt16(address), address);
case TypeCode.Single:
if (length > 1)
return (float[])GetContent(plc.ReadFloat(address, length), address);
else
return (float)GetContent(plc.ReadFloat(address), address);
case TypeCode.Boolean:
if (length > 1)
return (bool[])GetContent(plc.ReadBool(address, length), address);
else
return (bool)GetContent(plc.ReadBool(address), address);
case TypeCode.Byte:
return (byte[])GetContent(plc.Read(address, length), address);
case TypeCode.String:
return (string)GetContent(plc.ReadString(address, length), address);
case TypeCode.Char:
return (char[])GetContent(plc.Read(address, length), address);
default:
throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, typeCode.ToString(), address), CommunicationErrorType.TypeError);
}
}
catch (CommunicationException ex)
{
//读取异常时抛出自定义通讯异常类
throw new CommunicationException(ex.Message, ex.ErrorType);
}
catch (Exception ex)
{
//读取异常时抛出自定义通讯异常类
throw new CommunicationException($"读取数据异常,错误信息:{ex.Message}", CommunicationErrorType.ReadException, innerException: ex);
}
}
private void Ping()
{
Task.Run(() =>
{
while (_isPing)
{
try
{
IPStatus status = plc.IpAddressPing();
if (status == IPStatus.Success)
_connected = true;
else
_connected = false;
}
finally
{
Thread.Sleep(100);
}
}
});
}
#endregion
#region PublicMethod
///
/// 连接到PLC。
///
/// 如果连接成功则返回true,否则返回false。
public override bool Connect()
{
try
{
plc = new ModbusTcpNet(_ipAddress, _port);
OperateResult operateResult = plc.ConnectServer();//连接PLC
_connected = operateResult.IsSuccess;//将连接是否成功赋值给当前通讯器是否已连接到PLC
if (_connected)
LogNet.WriteInfo(Name, string.Format(CommunicationInfoMessage.ConnectSuccess, _ipAddress, _port));
else
LogNet.WriteError(Name, string.Format(CommunicationExceptionMessage.ConnectFaild, _ipAddress, _port, operateResult.Message));
Ping();
return operateResult.IsSuccess;
}
catch (Exception ex)
{
LogNet.WriteException(Name, string.Format(CommunicationExceptionMessage.ConnectFaild, _ipAddress, _port, ex.Message), ex);
//连接异常时抛出自定义异常类
throw new CommunicationException(ex.Message, CommunicationErrorType.ConnectionFailed, innerException: ex);
}
}
///
/// 断开与工业设备的连接。
///
/// 如果成功断开连接则返回true,如果已经是断开状态则返回false。
public override bool Disconnect()
{
try
{
if (plc != null)
{
OperateResult operateResult = plc.ConnectClose();//断开与PLC的连接
return operateResult.IsSuccess;
}
return false;
}
catch (Exception ex)
{
return false;
}
finally
{
_connected = false;
}
}
///
/// 释放对象资源的接口。
///
public override void Dispose()
{
_isPing = false;
Disconnect();
plc.Dispose();
GC.SuppressFinalize(this);
}
///
/// 从PLC读取数据。
///
/// 源地址,具体格式取决于使用的工业协议。
/// 要读取的数据长度。
/// 读取到的数据,如果读取失败则可能返回null或空数组。
public override byte[] Read(string address, int length)
{
return (byte[])GetContent(plc.Read(address, (ushort)length), address);
}
///
/// 从PLC读取数据。
///
/// 读取数据的类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 读取到的数据,如果读取失败则可能返回null或抛出异常。
public override T Read(string address)
{
Type type = typeof(T);
return (T)Read(address, Type.GetTypeCode(type));
}
///
/// 从PLC读取数据。
///
/// 读取数据的类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 读取的长度。
/// 读取到的数据,如果读取失败则可能返回null或抛出异常。
public override T[] Read(string address, ushort length)
{
Type type = typeof(T);
return (T[])Read(address, Type.GetTypeCode(type), length);
}
///
/// 从PLC读取数据。
///
/// 源地址,具体格式取决于使用的工业协议。
/// 数据类型。
/// 读取到的数据,如果读取失败则可能返回null或抛出异常。
public override object ReadAsObj(string address, string dataType)
{
return Read(address, SiemensDBDataType.GetTypeCode(dataType));
}
///
/// 读取自定义的数据类型,需要继承自IDataTransfer接口,返回一个新的类型的实例对象。
///
/// 自定义的数据类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 成功返回自定义类型数据,失败抛出异常。
public override T ReadCustomer(string address)
{
throw new Exception("该方法暂不可用");
//try
//{
// return plc.ReadCustomer(address).Content;
//}
//catch (Exception ex)
//{
// LogNet.WriteException(Name, $"【{Name}】PLC读取异常,地址:【{address}】,错误信息:【{ex.Message}】", ex);
// throw new CommunicationException(ex.Message, CommunicationErrorType.ReadException, innerException: ex);
//}
}
///
/// 等待指定地址的泛型类型值为指定的值
///
/// 指定的值的类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 读取的频率。
/// 等待的超时时间,如果超时时间为-1的话,则是无期限等待。
/// 等待检测的值
/// 是否等待成功的结果对象,一旦通信失败,或是等待超时就返回失败。否则返回成功,并告知调用方等待了多久。
public override OperateResult Wait(string address, int readInterval, int waitTimeout, T value)
{
TypeCode typeCode = Type.GetTypeCode(typeof(T));
switch (typeCode)
{
case TypeCode.Byte:
DateTime start = DateTime.Now;
while (true)
{
OperateResult read = plc.Read(address, 1);
if (!read.IsSuccess) return OperateResult.CreateFailedResult(read);
if (read.Content[0] == Convert.ToByte(value)) return OperateResult.CreateSuccessResult(DateTime.Now - start);
if (waitTimeout > 0 && (DateTime.Now - start).TotalMilliseconds > waitTimeout)
{
return new OperateResult(StringResources.Language.CheckDataTimeout + waitTimeout);
}
HslHelper.ThreadSleep(readInterval);
}
case TypeCode.Int16:
OperateResult operateResultShort = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout);
return operateResultShort;
case TypeCode.Int32:
OperateResult operateResultInt = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout);
return operateResultInt;
case TypeCode.UInt16:
OperateResult operateResultUShort = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout);
return operateResultUShort;
case TypeCode.UInt32:
OperateResult operateResultUInt = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout);
return operateResultUInt;
default:
throw new NotSupportedException();
}
}
///
/// 向PLC写入数据。
///
/// 源地址,具体格式取决于使用的工业协议。
/// 要写入的数据。
/// 如果写入成功则返回true,否则抛出异常。
public override bool Write(string address, byte[] data)
{
try
{
OperateResult result = plc.Write(address, data);
if (result.IsSuccess)
{
return result.IsSuccess;
}
else
{
//todo 写入失败
return false;
}
}
catch (Exception ex)
{
//写入异常时抛出自定义通讯异常类
throw new CommunicationException($"写入数据异常,地址:【{address}】,错误信息: {ex.Message}", CommunicationErrorType.ReadFailed, innerException: ex);
}
}
///
/// 向PLC写入数据。
///
/// 写入数据的类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 要写入的数据。
/// 如果写入成功则返回true,否则抛出异常。
public override bool Write(string address, T value)
{
return GetResult(Write(address, value), address, value);
}
///
/// 向PLC写入数据。
///
/// 写入数据的类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 要写入的数据。
/// 如果写入成功则返回true,否则抛出异常。
public override bool Write(string address, T[] values)
{
return GetResult(Write(address, values), address, values);
}
///
/// 写入自定义类型的数据,该类型必须继承自IDataTransfer接口。
///
/// 自定义的数据类型泛型。
/// 源地址,具体格式取决于使用的工业协议。
/// 要写入数据。
/// 如果写入成功则返回true,失败则抛出异常。
public override bool WriteCustomer(string address, [NotNull] T value)
{
throw new Exception("该方法暂不可用");
//StringBuilder stringBuilder = new StringBuilder();
//try
//{
// OperateResult operateResult = plc.WriteCustomer(address, value);
// stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteData, address, JsonConvert.SerializeObject(value)));
// if (operateResult.IsSuccess)
// {
// object? obj = null;
// for (int i = 0; i < 5; i++)
// {
// T readValue = ReadCustomer(address);
// stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAfterRead, address, JsonConvert.SerializeObject(readValue)));
// obj = readValue;
// PropertyInfo[] propertyInfos = typeof(T).GetProperties();
// for (int j = 0; j < propertyInfos.Length; j++)
// {
// object? writeValueItem = propertyInfos[j].GetValue(value);
// object? readValueItem = propertyInfos[j].GetValue(readValue);
// if (writeValueItem.Equals(readValueItem))
// {
// stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAndReadCheckSuccess, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(readValue)));
// }
// else
// {
// break;
// }
// if (j == propertyInfos.Length - 1)
// return true;
// }
// plc.WriteCustomer(address, value);
// }
// stringBuilder.AppendLine(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(obj)));
// throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(obj)), CommunicationErrorType.WriteFailed);
// }
// else
// {
// throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, JsonConvert.SerializeObject(value), operateResult.Message), CommunicationErrorType.WriteFailed);
// }
//}
//catch (Exception ex)
//{
// LogNet.WriteException(Name, ex.Message, ex);
// throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex);
//}
//finally
//{
// LogNet.WriteInfo(Name, stringBuilder.ToString());
//}
}
///
/// 向PLC写入数据。
///
/// 源地址,具体格式取决于使用的工业协议。
/// 要写入的数据类型。
/// 要写入的数据。
/// 如果写入成功则返回true,失败则抛出异常。
///
public override bool WriteObj(string address, string dataType, object value)
{
bool obj = false;
switch (dataType.ToLower())
{
case SiemensDBDataType.DataType_DInt:
{
int writeVal;
try
{
writeVal = Convert.ToInt32(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_DW:
{
uint writeVal;
try
{
writeVal = Convert.ToUInt32(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_Int:
{
short writeVal;
try
{
writeVal = Convert.ToInt16(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_W:
{
ushort writeVal;
try
{
writeVal = Convert.ToUInt16(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_Float:
{
float writeVal;
try
{
writeVal = Convert.ToSingle(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_Bool:
{
bool writeVal;
try
{
writeVal = Convert.ToBoolean(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_Byte:
{
byte writeVal;
try
{
writeVal = Convert.ToByte(value);
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_String:
{
string writeVal;
try
{
writeVal = value.ToString();
}
catch (Exception ex)
{
throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex);
}
obj = GetResult(Write(address, writeVal), address, writeVal);
}
break;
case SiemensDBDataType.DataType_Char:
break;
default:
throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, dataType, address), CommunicationErrorType.TypeError);
}
return obj;
}
#endregion
#region Destruction Function
///
/// 析构函数,确保在不再需要时关闭连接
///
~ModbusTcp()
{
Dispose();
}
#endregion
}
}