将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
using HslCommunication.Profinet.Siemens;
|
||||
using HslCommunication;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.Plc.Abstractions;
|
||||
using XP.Hardware.Plc.Exceptions;
|
||||
using XP.Hardware.PLC.Configs;
|
||||
using XP.Hardware.PLC.Helpers;
|
||||
using PlcType = XP.Hardware.PLC.Configs.PlcType;
|
||||
|
||||
namespace XP.Hardware.Plc.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 西门子 S7 PLC 客户端实现 | Siemens S7 PLC client implementation
|
||||
/// </summary>
|
||||
public class S7PlcClient : IPlcClient
|
||||
{
|
||||
private SiemensS7Net _siemensTcpNet;
|
||||
private PlcConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
private bool _isConnected = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 PLC 连接状态 | Get PLC connection status
|
||||
/// </summary>
|
||||
public bool IsConnected => _siemensTcpNet != null && _isConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public S7PlcClient(ILoggerService logger)
|
||||
{
|
||||
_logger = logger.ForModule("S7PlcClient");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到 PLC | Connect to PLC
|
||||
/// </summary>
|
||||
/// <param name="config">PLC 配置 | PLC configuration</param>
|
||||
/// <returns>连接是否成功 | Whether connection succeeded</returns>
|
||||
public async Task<bool> ConnectAsync(PlcConfig config)
|
||||
{
|
||||
_config = config;
|
||||
|
||||
// 释放旧连接 | Release old connection
|
||||
Disconnect();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Info("正在连接 PLC: {IpAddress}:{Port}, Rack={Rack}, Slot={Slot}, 型号={PlcType}",
|
||||
config.IpAddress, config.Port, config.Rack, config.Slot, config.PlcType);
|
||||
|
||||
// 根据西门子 PLC 型号创建连接对象 | Create connection object based on Siemens PLC model
|
||||
SiemensPLCS plcType = ConvertPlcType(config.PlcType);
|
||||
_siemensTcpNet = new SiemensS7Net(plcType);
|
||||
_siemensTcpNet.IpAddress = _config.IpAddress;
|
||||
_siemensTcpNet.Port = _config.Port;
|
||||
|
||||
// 配置超时参数 | Configure timeout parameters
|
||||
_siemensTcpNet.ConnectTimeOut = _config.ConnectTimeoutMs;
|
||||
_siemensTcpNet.ReceiveTimeOut = _config.ReadTimeoutMs;
|
||||
|
||||
var connectResult = await Task.Run(() => _siemensTcpNet.ConnectServer());
|
||||
|
||||
if (connectResult.IsSuccess)
|
||||
{
|
||||
_isConnected = true;
|
||||
_logger.Info("PLC 连接成功: {IpAddress}", config.IpAddress);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "PLC 连接失败: {IpAddress}, 错误: {Message}",
|
||||
config.IpAddress, connectResult.Message);
|
||||
throw new PlcException($"连接失败: {connectResult.Message}");
|
||||
}
|
||||
}
|
||||
catch (PlcException)
|
||||
{
|
||||
// 重新抛出 PlcException | Re-throw PlcException
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 包装异常 | Wrap exception
|
||||
_logger.Error(ex, "连接 PLC 时发生错误: {IpAddress}, 错误: {Message}",
|
||||
config.IpAddress, ex.Message);
|
||||
throw new PlcException($"连接PLC时发生错误: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开 PLC 连接 | Disconnect from PLC
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (_siemensTcpNet != null)
|
||||
{
|
||||
_logger.Info("正在断开 PLC 连接");
|
||||
_siemensTcpNet.ConnectClose();
|
||||
_siemensTcpNet = null;
|
||||
_isConnected = false;
|
||||
_logger.Info("PLC 连接已断开");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换 PLC 类型枚举到 HslCommunication 的 PLC 类型 | Convert PLC type enum to HslCommunication PLC type
|
||||
/// </summary>
|
||||
/// <param name="plcType">PLC 类型 | PLC type</param>
|
||||
/// <returns>HslCommunication PLC 类型 | HslCommunication PLC type</returns>
|
||||
private SiemensPLCS ConvertPlcType(PlcType plcType)
|
||||
{
|
||||
return plcType switch
|
||||
{
|
||||
PlcType.S200Smart => SiemensPLCS.S200Smart,
|
||||
PlcType.S300 => SiemensPLCS.S300,
|
||||
PlcType.S400 => SiemensPLCS.S400,
|
||||
PlcType.S1200 => SiemensPLCS.S1200,
|
||||
PlcType.S1500 => SiemensPLCS.S1500,
|
||||
_ => throw new PlcException($"不支持的 PLC 类型: {plcType}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 PLC 数据 | Read PLC data
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型 | Data type</typeparam>
|
||||
/// <param name="address">PLC 地址 | PLC address</param>
|
||||
/// <returns>读取的数据 | Read data</returns>
|
||||
public async Task<T> ReadAsync<T>(string address)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "读取失败: PLC 未连接, 地址={Address}", address);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("读取 PLC 数据: 地址={Address}, 类型={Type}", address, typeof(T).Name);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
OperateResult result;
|
||||
object content = null;
|
||||
|
||||
// 根据类型调用不同的读取方法 | Call different read methods based on type
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadBool(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(byte))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadByte(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(short))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadInt16(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(ushort))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadUInt16(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(int))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadInt32(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(uint))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadUInt32(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(float))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadFloat(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadDouble(address);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else if (typeof(T) == typeof(string))
|
||||
{
|
||||
// 字符串需要指定长度,这里使用默认长度 10
|
||||
var readResult = _siemensTcpNet.ReadString(address, 10);
|
||||
result = readResult;
|
||||
content = readResult.Content;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlcException($"不支持的数据类型: {typeof(T).Name}");
|
||||
}
|
||||
|
||||
PlcHelper.CheckSuccess(result);
|
||||
|
||||
_logger.Debug("读取成功: 地址={Address}, 值={Value}", address, content);
|
||||
return (T)content;
|
||||
});
|
||||
}
|
||||
catch (PlcException)
|
||||
{
|
||||
_logger.Error(null, "读取失败: 地址={Address}", address);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "读取 PLC 数据时发生错误: 地址={Address}, 错误={Message}", address, ex.Message);
|
||||
throw new PlcException($"读取失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入 PLC 数据 | Write PLC data
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型 | Data type</typeparam>
|
||||
/// <param name="address">PLC 地址 | PLC address</param>
|
||||
/// <param name="value">要写入的值 | Value to write</param>
|
||||
/// <returns>写入是否成功 | Whether write succeeded</returns>
|
||||
public async Task<bool> WriteAsync<T>(string address, T value)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "写入失败: PLC 未连接, 地址={Address}", address);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("写入 PLC 数据: 地址={Address}, 值={Value}", address, value);
|
||||
|
||||
OperateResult writeResult;
|
||||
|
||||
// 根据类型调用不同的写入方法 | Call different write methods based on type
|
||||
if (typeof(T) == typeof(bool))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToBoolean(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(byte))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToByte(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(short))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToInt16(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(ushort))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToUInt16(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(int))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToInt32(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(uint))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToUInt32(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(float))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToSingle(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToDouble(value)));
|
||||
}
|
||||
else if (typeof(T) == typeof(string))
|
||||
{
|
||||
writeResult = await Task.Run(() => _siemensTcpNet.Write(address, value.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlcException($"不支持的数据类型: {typeof(T).Name}");
|
||||
}
|
||||
|
||||
if (writeResult.IsSuccess)
|
||||
{
|
||||
_logger.Debug("写入成功: 地址={Address}", address);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "写入失败: 地址={Address}, 错误={Message}", address, writeResult.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "写入 PLC 数据时发生错误: 地址={Address}, 错误={Message}", address, ex.Message);
|
||||
throw new PlcException($"写入失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量读取字节数组 | Read byte array in bulk
|
||||
/// </summary>
|
||||
/// <param name="dbBlock">数据块 | Data block</param>
|
||||
/// <param name="startAddress">起始地址 | Start address</param>
|
||||
/// <param name="length">读取长度 | Read length</param>
|
||||
/// <returns>读取的字节数组 | Read byte array</returns>
|
||||
/// <summary>
|
||||
/// 按指定长度读取字符串 | Read string with specified length
|
||||
/// </summary>
|
||||
public async Task<string> ReadStringAsync(string address, ushort length)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "读取字符串失败: PLC 未连接, 地址={Address}", address);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("读取 PLC 字符串: 地址={Address}, 长度={Length}", address, length);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var readResult = _siemensTcpNet.ReadString(address, length);
|
||||
PlcHelper.CheckSuccess(readResult);
|
||||
_logger.Debug("读取字符串成功: 地址={Address}, 值={Value}", address, readResult.Content);
|
||||
return readResult.Content;
|
||||
});
|
||||
}
|
||||
catch (PlcException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "读取字符串时发生错误: 地址={Address}, 错误={Message}", address, ex.Message);
|
||||
throw new PlcException($"读取字符串失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按指定长度写入字符串 | Write string with specified length
|
||||
/// </summary>
|
||||
public async Task<bool> WriteStringAsync(string address, string value, ushort length)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "写入字符串失败: PLC 未连接, 地址={Address}", address);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("写入 PLC 字符串: 地址={Address}, 值={Value}, 长度={Length}", address, value, length);
|
||||
|
||||
// 截断或填充字符串到指定长度 | Truncate or pad string to specified length
|
||||
var bytes = System.Text.Encoding.ASCII.GetBytes(value ?? string.Empty);
|
||||
var buffer = new byte[length];
|
||||
Array.Copy(bytes, 0, buffer, 0, Math.Min(bytes.Length, length));
|
||||
|
||||
var addr = address;
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var result = _siemensTcpNet.Write(addr, buffer);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_logger.Debug("写入字符串成功: 地址={Address}", addr);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "写入字符串失败: 地址={Address}, 错误={Message}", addr, result.Message);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "写入字符串时发生错误: 地址={Address}, 错误={Message}", address, ex.Message);
|
||||
throw new PlcException($"写入字符串失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadBytesAsync(string dbBlock, int startAddress, int length)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "批量读取失败: PLC 未连接, 数据块={DbBlock}, 地址={Address}, 长度={Length}",
|
||||
dbBlock, startAddress, length);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//_logger.Debug("批量读取 PLC 数据: 数据块={DbBlock}, 起始地址={Address}, 长度={Length}", dbBlock, startAddress, length);
|
||||
|
||||
var address = $"{dbBlock}.{startAddress}";
|
||||
var result = await Task.Run(() => _siemensTcpNet.Read(address, (ushort)length));
|
||||
PlcHelper.CheckSuccess(result);
|
||||
|
||||
//_logger.Debug("批量读取成功: 数据块={DbBlock}, 读取字节数={Length}", dbBlock, result.Content.Length);
|
||||
return result.Content;
|
||||
}
|
||||
catch (PlcException)
|
||||
{
|
||||
_logger.Error(null, "批量读取失败: 数据块={DbBlock}, 地址={Address}", dbBlock, startAddress);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量读取 PLC 数据时发生错误: 数据块={DbBlock}, 地址={Address}, 错误={Message}",
|
||||
dbBlock, startAddress, ex.Message);
|
||||
throw new PlcException($"批量读取失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量写入字节数组 | Write byte array in bulk
|
||||
/// </summary>
|
||||
/// <param name="dbBlock">数据块 | Data block</param>
|
||||
/// <param name="startAddress">起始地址 | Start address</param>
|
||||
/// <param name="data">要写入的数据 | Data to write</param>
|
||||
/// <returns>写入是否成功 | Whether write succeeded</returns>
|
||||
public async Task<bool> WriteBytesAsync(string dbBlock, int startAddress, byte[] data)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Error(null, "批量写入失败: PLC 未连接, 数据块={DbBlock}, 地址={Address}, 长度={Length}",
|
||||
dbBlock, startAddress, data?.Length ?? 0);
|
||||
throw new PlcException("PLC未连接");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("批量写入 PLC 数据: 数据块={DbBlock}, 起始地址={Address}, 长度={Length}",
|
||||
dbBlock, startAddress, data.Length);
|
||||
|
||||
var address = $"{dbBlock}.{startAddress}";
|
||||
var result = await Task.Run(() => _siemensTcpNet.Write(address, data));
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_logger.Debug("批量写入成功: 数据块={DbBlock}, 写入字节数={Length}", dbBlock, data.Length);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "批量写入失败: 数据块={DbBlock}, 地址={Address}, 错误={Message}",
|
||||
dbBlock, startAddress, result.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "批量写入 PLC 数据时发生错误: 数据块={DbBlock}, 地址={Address}, 错误={Message}",
|
||||
dbBlock, startAddress, ex.Message);
|
||||
throw new PlcException($"批量写入失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false;
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否释放托管资源 | Whether to dispose managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_logger.Info("正在释放 S7PlcClient 资源");
|
||||
Disconnect();
|
||||
}
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user