将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。

This commit is contained in:
QI Mingxuan
2026-04-16 17:31:13 +08:00
parent 6ec4c3ddaa
commit 2bd6e566c3
581 changed files with 74600 additions and 222 deletions
+498
View File
@@ -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
}
}