using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Aging.Infrastructure.Contracts;
using log4net;
using S7Driver;
namespace Aging.Infrastructure.Drivers
{
///
/// 西门子 S7 PLC 驱动适配器。
/// 封装 S7Driver.PLCS7Driver,实现统一的 IDeviceDriver 接口。
/// 命令格式约定:
/// 写入: "WRITE:DBx.DBXy.z:value" 如 "WRITE:DB1.DBX0.0:TRUE"
/// 读取: "READ:DBx.DBWy" 如 "READ:DB1.DBD0"
/// 支持数据类型: BOOL, INT, REAL
///
public sealed class S7DriverAdapter : IDeviceDriver
{
private static readonly ILog Log = LogManager.GetLogger(typeof(S7DriverAdapter));
private readonly string _ip;
private readonly int _port;
private PLCS7Driver? _driver;
private bool _disposed;
private readonly Stopwatch _latencyStopwatch = new Stopwatch();
private double _lastLatencyMs;
private string _lastError = string.Empty;
private DateTime? _lastErrorTime;
private int _reconnectCount;
///
public string DeviceId { get; }
///
public bool IsConnected => _driver != null;
///
public event EventHandler? ConnectionChanged;
///
/// 初始化 S7 PLC 驱动适配器
///
/// 设备唯一标识符
/// PLC IP 地址
/// PLC 端口号(通常为 102)
public S7DriverAdapter(string deviceId, string ip, int port)
{
DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
_ip = ip ?? throw new ArgumentNullException(nameof(ip));
_port = port;
}
///
public Task ConnectAsync(CancellationToken ct)
{
ThrowIfDisposed();
Log.Info($"[S7] 设备 {DeviceId} 正在连接 {_ip}:{_port}...");
try
{
_driver = new PLCS7Driver(_ip, _port);
bool success = _driver.Connect(out string errMsg);
if (!success)
{
_lastError = errMsg;
_lastErrorTime = DateTime.Now;
throw new InvalidOperationException($"[S7] 设备 {DeviceId} 连接失败: {errMsg}");
}
_lastError = string.Empty;
Log.Info($"[S7] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}");
OnConnectionChanged(true, "连接成功");
}
catch (Exception ex)
{
_lastError = ex.Message;
_lastErrorTime = DateTime.Now;
Log.Error($"[S7] 设备 {DeviceId} 连接异常: {ex.Message}", ex);
_driver?.Dispose();
_driver = null;
throw;
}
return Task.CompletedTask;
}
///
public Task DisconnectAsync()
{
ThrowIfDisposed();
Log.Info($"[S7] 设备 {DeviceId} 正在断开连接...");
try
{
if (_driver != null)
{
_driver.DisConnect(out string errMsg);
_driver.Dispose();
_driver = null;
}
Log.Info($"[S7] 设备 {DeviceId} 已断开连接");
OnConnectionChanged(false, "主动断开");
}
catch (Exception ex)
{
_lastError = ex.Message;
_lastErrorTime = DateTime.Now;
Log.Error($"[S7] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex);
_driver?.Dispose();
_driver = null;
}
return Task.CompletedTask;
}
///
public Task SendCommandAsync(string command, CancellationToken ct)
{
ThrowIfDisposed();
if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接");
_latencyStopwatch.Restart();
try
{
Log.Debug($"[S7] 设备 {DeviceId} 发送命令: {command}");
// 解析命令格式: "WRITE:DBx.DBXy.z:value" 或 "WRITE:DBx.DBWy:value"
string[] parts = command.Split(':');
if (parts.Length < 3)
{
throw new ArgumentException($"[S7] 命令格式错误,应为 WRITE:address:value,实际: {command}");
}
string operation = parts[0].ToUpper();
string address = parts[1];
string value = parts[2];
if (operation != "WRITE")
{
throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 WRITE");
}
// 解析地址和值,执行写入
bool success = ParseAndWrite(address, value, out string msg);
_latencyStopwatch.Stop();
_lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
if (!success)
{
_lastError = msg;
_lastErrorTime = DateTime.Now;
throw new InvalidOperationException($"[S7] 写入失败: {msg}");
}
string response = "OK";
Log.Debug($"[S7] 设备 {DeviceId} 写入成功 (延迟 {_lastLatencyMs:F2}ms)");
return Task.FromResult(response);
}
catch (Exception ex)
{
_latencyStopwatch.Stop();
_lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
_lastError = ex.Message;
_lastErrorTime = DateTime.Now;
Log.Error($"[S7] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex);
throw;
}
}
///
public Task QueryAsync(string query, CancellationToken ct)
{
ThrowIfDisposed();
if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接");
_latencyStopwatch.Restart();
try
{
Log.Debug($"[S7] 设备 {DeviceId} 发送查询: {query}");
// 解析查询格式: "READ:DBx.DBWy" 或 "READ:DBx.DBXy.z"
string[] parts = query.Split(':');
if (parts.Length < 2)
{
throw new ArgumentException($"[S7] 查询格式错误,应为 READ:address,实际: {query}");
}
string operation = parts[0].ToUpper();
string address = parts[1];
if (operation != "READ")
{
throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 READ");
}
// 解析地址并执行读取
string response = ParseAndRead(address);
_latencyStopwatch.Stop();
_lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
Log.Debug($"[S7] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)");
return Task.FromResult(response);
}
catch (Exception ex)
{
_latencyStopwatch.Stop();
_lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
_lastError = ex.Message;
_lastErrorTime = DateTime.Now;
Log.Error($"[S7] 设备 {DeviceId} 查询失败: {ex.Message}", ex);
throw;
}
}
///
public DeviceDriverStatus GetStatus()
{
return new DeviceDriverStatus
{
IsConnected = IsConnected,
IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError),
LastError = _lastError,
LastErrorTime = _lastErrorTime,
LatencyMs = _lastLatencyMs,
ReconnectCount = _reconnectCount
};
}
///
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
_driver?.Dispose();
}
catch (Exception ex)
{
Log.Error($"[S7] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex);
}
}
///
/// 解析地址并执行写入操作
///
/// S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10"
/// 要写入的值
/// 错误信息
/// 是否成功
private bool ParseAndWrite(string address, string value, out string msg)
{
msg = null;
try
{
// 解析地址格式: DBx.DBXy.z (BOOL) 或 DBx.DBWy (INT/REAL)
if (address.Contains(".DBX"))
{
// BOOL 类型: DB1.DBX0.0
if (bool.TryParse(value, out bool boolVal))
{
var addrParts = ParseBoolAddress(address);
return _driver.Simens_S7_WriteBool(addrParts.db, addrParts.startAddr, addrParts.pos, boolVal, out msg);
}
throw new ArgumentException($"[S7] BOOL 值解析失败: {value}");
}
else if (address.Contains(".DBW"))
{
// 尝试解析为不同类型
if (float.TryParse(value, out float realVal))
{
var addrParts = ParseWordAddress(address);
return _driver.Simens_S7_WriteReal(addrParts.db, addrParts.startAddr, realVal, out msg);
}
else if (short.TryParse(value, out short shortVal))
{
var addrParts = ParseWordAddress(address);
return _driver.Simens_S7_WriteInt(addrParts.db, addrParts.startAddr, shortVal, out msg);
}
throw new ArgumentException($"[S7] 值解析失败: {value}");
}
else
{
msg = $"[S7] 不支持的地址格式: {address}";
return false;
}
}
catch (Exception ex)
{
msg = ex.Message;
return false;
}
}
///
/// 解析地址并执行读取操作
///
/// S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10"
/// 读取的值字符串
private string ParseAndRead(string address)
{
if (address.Contains(".DBX"))
{
// BOOL 类型读取
var addrParts = ParseBoolAddress(address);
if (_driver.Simens_S7_ReadBool(addrParts.db, addrParts.startAddr, addrParts.pos, out bool value, out string msg))
{
return value.ToString();
}
throw new InvalidOperationException($"[S7] 读取失败: {msg}");
}
else if (address.Contains(".DBD"))
{
// REAL 类型读取 (DBD = Double Word)
var addrParts = ParseWordAddress(address);
if (_driver.Simens_S7_ReadReal(addrParts.db, addrParts.startAddr, out float realValue, out string msg))
{
return realValue.ToString();
}
throw new InvalidOperationException($"[S7] 读取失败: {msg}");
}
else if (address.Contains(".DBW"))
{
// INT 类型读取
var addrParts = ParseWordAddress(address);
if (_driver.Simens_S7_ReadInt(addrParts.db, addrParts.startAddr, out int value, out string msg))
{
return value.ToString();
}
throw new InvalidOperationException($"[S7] 读取失败: {msg}");
}
else
{
throw new ArgumentException($"[S7] 不支持的地址格式: {address}");
}
}
///
/// 解析 BOOL 地址格式: DBx.DBXy.z
///
private (int db, int startAddr, int pos) ParseBoolAddress(string address)
{
// 格式: DB1.DBX0.0
string upper = address.ToUpper();
int dbStart = upper.IndexOf("DB") + 2;
int dbxStart = upper.IndexOf(".DBX");
int db = int.Parse(upper.Substring(dbStart, dbxStart - dbStart));
int posStart = dbxStart + 5; // .DBX 后
int dotPos = address.IndexOf('.', posStart);
int startAddr = int.Parse(address.Substring(posStart, dotPos - posStart));
int pos = int.Parse(address.Substring(dotPos + 1));
return (db, startAddr, pos);
}
///
/// 解析 Word 地址格式: DBx.DBWy 或 DBx.DBDy
///
private (int db, int startAddr) ParseWordAddress(string address)
{
// 格式: DB1.DBW10 或 DB1.DBD0
string upper = address.ToUpper();
int dbStart = upper.IndexOf("DB") + 2;
int dbwStart = upper.Contains(".DBW") ? upper.IndexOf(".DBW") : upper.IndexOf(".DBD");
int db = int.Parse(upper.Substring(dbStart, dbwStart - dbStart));
int addrStart = dbwStart + 5; // .DBW 或 .DBD 后
int startAddr = int.Parse(address.Substring(addrStart));
return (db, startAddr);
}
///
/// 触发连接状态变化事件
///
private void OnConnectionChanged(bool isConnected, string reason)
{
ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs
{
DeviceId = DeviceId,
IsConnected = isConnected,
Reason = reason
});
if (!isConnected)
{
_reconnectCount++;
}
}
///
/// 检查是否已释放
///
private void ThrowIfDisposed()
{
if (_disposed) throw new ObjectDisposedException(nameof(S7DriverAdapter));
}
}
}