2026-01-31 17:12:58 +08:00

389 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using S7.Net; // 确保引用 S7.Net 库
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RIZO.Service.PLC
{
public class PlcConntectHepler : IDisposable
{
public Plc _plc;
private readonly string _ipAddress;
private readonly CpuType _cpuType;
private readonly short _rack;
private readonly short _slot;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private bool _disposed;
public PlcConntectHepler(string ipAddress, CpuType cpuType = CpuType.S71200, short rack = 0, short slot = 1)
{
_ipAddress = ipAddress;
_cpuType = cpuType;
_rack = rack;
_slot = slot;
}
private async Task ConnectAsync(CancellationToken cancellationToken = default)
{
int maxRetries = 3;
int retryDelayMs = 2000;
int attempt = 0;
while (attempt < maxRetries)
{
attempt++;
try
{
_plc = new Plc(_cpuType, _ipAddress, _rack, _slot);
await Task.Run(() => _plc.Open(), cancellationToken); // 在线程池中执行同步Open
Console.WriteLine($"成功连接到PLC: {_ipAddress} (尝试{attempt}/{maxRetries})");
return;
}
catch (Exception ex)
{
Console.WriteLine($"连接尝试 {attempt}/{maxRetries} 失败: {ex.Message}");
if (attempt < maxRetries)
{
Console.WriteLine($"{retryDelayMs / 1000}秒后重试...");
await Task.Delay(retryDelayMs, cancellationToken);
}
else
{
Console.WriteLine("无法建立PLC连接请检查网络和设备状态");
_plc?.Close();
_plc = null;
}
}
}
}
/// <summary>
/// 读取指定类型数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="address"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> ReadAsync<T>(string address, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_plc == null || !_plc.IsConnected)
{
await ConnectAsync(cancellationToken);
if (_plc == null || !_plc.IsConnected)
throw new InvalidOperationException("PLC未连接");
}
var result = await Task.Run(() => _plc.Read(address), cancellationToken);
// 根据泛型类型转换结果
return ConvertResult<T>(result);
}
catch (Exception ex)
{
Console.WriteLine($"Read error: {ex.Message}");
return default(T);
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 写入指定类型数据
/// </summary>
/// <param name="address"></param>
/// <param name="value"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task WriteAsync(string address, object value, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_plc == null || !_plc.IsConnected)
{
await ConnectAsync(cancellationToken);
if (_plc == null || !_plc.IsConnected)
throw new InvalidOperationException("PLC未连接");
}
await Task.Run(() => _plc.Write(address, value), cancellationToken);
}
catch (Exception ex)
{
Console.WriteLine($"Write error: {ex.Message}");
}
finally
{
_semaphore.Release();
}
}
public async Task WriteAsync2(string address, object value, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
// 1. 确保PLC连接正常
if (_plc == null || !_plc.IsConnected)
{
await ConnectAsync(cancellationToken);
if (_plc == null || !_plc.IsConnected)
throw new InvalidOperationException("PLC未连接");
}
// 2. 核心修复DBW地址强制转short适配16位字类型
object writeValue = value;
if (address.Contains("DBW", StringComparison.OrdinalIgnoreCase) && value is int intVal)
{
writeValue = (short)intVal; // 写入1时自动转为short匹配DBW类型
}
// 3. 移除Task.Run直接同步写入避免异步调度导致失效
_plc.Write(address, writeValue);
}
catch (Exception ex)
{
Console.WriteLine($"Write error: {ex.Message}");
throw; // 抛出异常,让上层感知写入失败(关键:不吞异常)
}
finally
{
_semaphore.Release();
}
}
private T ConvertResult<T>(object result)
{
if (result == null) return default(T);
if (typeof(T).IsAssignableFrom(result.GetType()))
{
return (T)result;
}
return typeof(T) switch
{
Type t when t == typeof(bool) => (T)(object)Convert.ToBoolean(result),
Type t when t == typeof(short) => (T)(object)Convert.ToInt16(result),
Type t when t == typeof(int) => (T)(object)Convert.ToInt32(result),
Type t when t == typeof(float) => (T)(object)Convert.ToSingle(result),
Type t when t == typeof(double) => (T)(object)Convert.ToDouble(result),
Type t when t == typeof(string) => (T)(object)result.ToString(),
_ => throw new InvalidCastException($"无法将类型 {result.GetType()} 转换为 {typeof(T)}")
};
}
/// <summary>
/// 从PLC读取字符串主要方法
/// </summary>
/// <param name="address">地址字符串(如:"DB1001.DBB1000"</param>
/// <returns>读取到的字符串</returns>
public async Task<string> ReadStringAsync(string address)
{
return await ReadStringAsync(address, CancellationToken.None);
}
/// <summary>
/// 从PLC读取字符串带取消令牌
/// </summary>
/// <param name="address">地址字符串(如:"DB1001.DBB1000"</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>读取到的字符串</returns>
public async Task<string> ReadStringAsync(string address, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_plc == null || !_plc.IsConnected)
{
await ConnectAsync(cancellationToken);
if (_plc == null || !_plc.IsConnected)
throw new InvalidOperationException("PLC未连接");
}
// 解析地址
if (!ParseAddress(address, out int dbNumber, out int startByte))
{
throw new ArgumentException($"无效的地址格式: {address}");
}
// 读取第一个字节(字符串长度)
byte stringLength = await Task.Run(() =>
(byte)_plc.Read(DataType.DataBlock, dbNumber, startByte, VarType.Byte, 1), cancellationToken);
// 如果长度为0直接返回空字符串
if (stringLength == 0)
return string.Empty;
// 读取字符串内容从startByte+1开始读取stringLength个字节
byte[] stringBytes = await Task.Run(() =>
(byte[])_plc.Read(DataType.DataBlock, dbNumber, startByte + 1, VarType.Byte, (int)stringLength), cancellationToken);
// 将字节数组转换为ASCII字符串
return Encoding.ASCII.GetString(stringBytes);
}
catch (Exception ex)
{
Console.WriteLine($"ReadString error: {ex.Message}");
return string.Empty;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 向PLC写入字符串
/// </summary>
/// <param name="address">地址字符串(如:"DB1001.DBB1000"</param>
/// <param name="value">要写入的字符串</param>
/// <returns>是否写入成功</returns>
public async Task<bool> WriteStringAsync(string address, string value)
{
return await WriteStringAsync(address, value, CancellationToken.None);
}
/// <summary>
/// 向PLC写入字符串带取消令牌
/// </summary>
/// <param name="address">地址字符串(如:"DB1001.DBB1000"</param>
/// <param name="value">要写入的字符串</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>是否写入成功</returns>
public async Task<bool> WriteStringAsync(string address, string value, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_plc == null || !_plc.IsConnected)
{
await ConnectAsync(cancellationToken);
if (_plc == null || !_plc.IsConnected)
throw new InvalidOperationException("PLC未连接");
}
if (value == null)
value = string.Empty;
// 限制字符串长度S7字符串最大254字符
if (value.Length > 254)
{
value = value.Substring(0, 254);
}
// 准备写入的数据
List<byte> writeData = new List<byte>();
// 第一个字节:字符串长度
writeData.Add((byte)value.Length);
// 后续字节字符串内容ASCII编码
byte[] contentBytes = Encoding.ASCII.GetBytes(value);
writeData.AddRange(contentBytes);
// 解析地址
if (!ParseAddress(address, out int dbNumber, out int startByte))
{
throw new ArgumentException($"无效的地址格式: {address}");
}
// 写入数据
await Task.Run(() =>
_plc.WriteBytes(DataType.DataBlock, dbNumber, startByte, writeData.ToArray()), cancellationToken);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"WriteString error: {ex.Message}");
return false;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 解析PLC地址字符串提取DB号和起始字节
/// 支持格式DB1001.DBB1000, DB1001.DBW1000, DB1001.DBD1000 等
/// </summary>
private bool ParseAddress(string address, out int dbNumber, out int startByte)
{
dbNumber = 0;
startByte = 0;
try
{
// 移除空格并转为大写
address = address.Replace(" ", "").ToUpper();
// 分割DB部分和数据部分
string[] parts = address.Split('.');
if (parts.Length < 2) return false;
// 解析DB号
string dbPart = parts[0];
if (!dbPart.StartsWith("DB") || !int.TryParse(dbPart.Substring(2), out dbNumber))
return false;
// 解析起始字节
string dataPart = parts[1];
// 提取偏移量数字去掉DBB/DBW/DBD前缀
string offsetStr = "";
if (dataPart.StartsWith("DBB"))
offsetStr = dataPart.Substring(3);
else if (dataPart.StartsWith("DBW"))
offsetStr = dataPart.Substring(3);
else if (dataPart.StartsWith("DBD"))
offsetStr = dataPart.Substring(3);
else
return false;
if (!int.TryParse(offsetStr, out startByte))
return false;
return true;
}
catch
{
return false;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_semaphore.Dispose();
_plc?.Close();
(_plc as IDisposable)?.Dispose();
}
_disposed = true;
}
}
}