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 { private 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; } } } } /// /// 读取指定类型数据 /// /// /// /// /// public async Task ReadAsync(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(result); } catch (Exception ex) { Console.WriteLine($"Read error: {ex.Message}"); return default(T); } finally { _semaphore.Release(); } } /// /// 写入指定类型数据 /// /// /// /// /// 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(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)}") }; } /// /// 从PLC读取字符串(主要方法) /// /// 地址字符串(如:"DB1001.DBB1000") /// 读取到的字符串 public async Task ReadStringAsync(string address) { return await ReadStringAsync(address, CancellationToken.None); } /// /// 从PLC读取字符串(带取消令牌) /// /// 地址字符串(如:"DB1001.DBB1000") /// 取消令牌 /// 读取到的字符串 public async Task 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(); } } /// /// 向PLC写入字符串 /// /// 地址字符串(如:"DB1001.DBB1000") /// 要写入的字符串 /// 是否写入成功 public async Task WriteStringAsync(string address, string value) { return await WriteStringAsync(address, value, CancellationToken.None); } /// /// 向PLC写入字符串(带取消令牌) /// /// 地址字符串(如:"DB1001.DBB1000") /// 要写入的字符串 /// 取消令牌 /// 是否写入成功 public async Task 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 writeData = new List(); // 第一个字节:字符串长度 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(); } } /// /// 解析PLC地址字符串,提取DB号和起始字节 /// 支持格式:DB1001.DBB1000, DB1001.DBW1000, DB1001.DBD1000 等 /// 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; } } }