// 统一引入所有必要命名空间 using Microsoft.Extensions.Options; using Quartz; using RIZO.Common; using S7.Net; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace RIZO.Admin.WebApi.PLC { // 移除嵌套类,直接定义顶级PlcService类并实现IDisposable public class PlcService : IDisposable { // 标记是否已释放资源,避免重复释放 private bool _disposed = false; // PLC配置参数(从配置文件注入) private readonly List _plcConfigs; private readonly GlobalPlcConfig _globalConfig; /// /// 构造函数(依赖注入获取PLC配置) /// /// 所有PLC的连接配置 /// PLC全局超时配置 public PlcService(IOptions> plcConfigs, IOptions globalConfig) { _plcConfigs = plcConfigs.Value; _globalConfig = globalConfig.Value; } /// /// 测试单个PLC的连接、读、写功能 /// /// 单个PLC的配置参数 /// 测试结果 public async Task TestSinglePlcAsync(PlcConfig config) { var result = new PlcTestResult { PlcName = config.PlcName, Ip = config.Ip }; Plc plc = null; try { // 1. 初始化PLC连接对象 plc = new Plc(CpuType.S71500, config.Ip, config.Rack, config.Slot); plc.ReadTimeout = _globalConfig.ReadWriteTimeout; plc.WriteTimeout = _globalConfig.ReadWriteTimeout; // 异步打开连接(S7.Net无原生异步,用Task.Run包装) await Task.Run(() => plc.Open()); // 检查连接状态 if (plc.IsConnected) { result.ConnectSuccess = true; result.ConnectMessage = "连接成功"; } else { result.ConnectSuccess = false; result.ConnectMessage = "连接失败(PLC未返回连接状态)"; return result; } // 2. 读取PLC数据 try { var readValue = await Task.Run(() => plc.Read(config.TestReadAddress)); result.ReadSuccess = true; result.ReadValue = FormatValue(readValue); result.ReadMessage = "读取成功"; } catch (Exception ex) { result.ReadSuccess = false; result.ReadMessage = $"读取失败:{ex.Message}"; } // 3. 写入PLC数据 try { bool writeOk = await Task.Run(() => WritePlcValue(plc, config.TestWriteAddress, config.TestWriteValue)); result.WriteSuccess = writeOk; result.WriteMessage = writeOk ? "写入成功" : "写入失败(值类型与地址不匹配)"; } catch (Exception ex) { result.WriteSuccess = false; result.WriteMessage = $"写入失败:{ex.Message}"; } } catch (Exception ex) { result.ConnectSuccess = false; result.ConnectMessage = $"连接异常:{ex.Message}"; } finally { // 确保PLC连接最终被释放 if (plc != null) { if (plc.IsConnected) { plc.Close(); } Dispose(); // 释放Plc对象资源 } } return result; } /// /// 批量测试配置文件中所有PLC的读写功能 /// /// 所有PLC的测试结果列表 /// 未配置PLC参数时抛出 public async Task> BatchTestAllPlcAsync() { if (_plcConfigs == null || _plcConfigs.Count == 0) { throw new Exception("未从配置文件加载到任何PLC参数,请检查PlcConfigs配置"); } // 并行测试所有PLC,提高效率 var testTasks = _plcConfigs.Select(config => TestSinglePlcAsync(config)).ToList(); var results = await Task.WhenAll(testTasks); return results.ToList(); } /// /// 单独读取指定PLC的某个地址数据 /// /// PLC的IP地址 /// 机架号 /// 槽位号 /// 读取地址(如DB1.DBD0) /// 读取结果(成功状态、值、消息) public async Task<(bool Success, string Value, string Message)> ReadPlcDataAsync(string ip, short rack, short slot, string address) { Plc plc = null; try { plc = new Plc(CpuType.S71500, ip, rack, slot); plc.ReadTimeout = _globalConfig.ReadWriteTimeout; await Task.Run(() => plc.Open()); if (!plc.IsConnected) { return (false, "", "PLC连接失败,请检查IP/机架号/槽位号是否正确"); } var value = await Task.Run(() => plc.Read(address)); // 示例:读取DB1.DBX0.0和DB1.DBX0.1 //var address1 = "DB1010.DBX0.0"; // 第一个位地址 //var address2 = "DB1010.DBX0.1"; // 第二个位地址 var address1 = "DB1010.DBW2"; // 第二个位地址 var address2 = "DB1010.DBW4"; // 第二个位地址 // 读取DBX0.0 var value1 = await Task.Run(() => plc.Read(address1)); bool writeOk = await Task.Run(() => WritePlcValue(plc, address1, "56")); // 读取DBX0.1 var value2 = await Task.Run(() => plc.Read(address2)); return (true, FormatValue(value), "读取成功"); } catch (Exception ex) { return (false, "", $"读取失败:{ex.Message}"); } finally { // 释放PLC连接 if (plc != null) { if (plc.IsConnected) plc.Close(); Dispose(); } } } /// /// 单独写入数据到指定PLC的某个地址 /// /// PLC的IP地址 /// 机架号 /// 槽位号 /// 写入地址(如DB1.DBD0) /// 写入的值(字符串格式) /// 写入结果(成功状态、消息) public async Task<(bool Success, string Message)> WritePlcDataAsync(string ip, short rack, short slot, string address, string valueStr) { Plc plc = null; try { plc = new Plc(CpuType.S71500, ip, rack, slot); plc.WriteTimeout = _globalConfig.ReadWriteTimeout; await Task.Run(() => plc.Open()); if (!plc.IsConnected) { return (false, "PLC连接失败,请检查IP/机架号/槽位号是否正确"); } bool writeOk = await Task.Run(() => WritePlcValue(plc, address, valueStr)); return (writeOk, writeOk ? "写入成功" : "写入失败(值类型与地址不匹配)"); } catch (Exception ex) { return (false, $"写入失败:{ex.Message}"); } finally { // 释放PLC连接 if (plc != null) { if (plc.IsConnected) plc.Close(); Dispose(); } } } /// /// 格式化PLC读取的值,转为友好的字符串 /// /// PLC读取的原始值 /// 格式化后的字符串 private string FormatValue(object value) { if (value == null) return "空值"; return value switch { float f => f.ToString("0.000"), // 浮点数保留3位小数 short s => s.ToString(), // 16位整数 byte b => b.ToString(), // 8位整数 bool b => b.ToString(), // 布尔值 int i => i.ToString(), // 32位整数 uint ui => ui.ToString(), // 无符号32位整数 _ => value.ToString() // 其他类型直接转字符串 }; } /// /// 根据地址类型转换值并写入PLC /// /// PLC连接对象 /// 写入地址 /// 写入的值(字符串) /// 是否写入成功 private bool WritePlcValue(Plc plc, string address, string valueStr) { // 双字(DBD/DD):浮点数或32位整数 if (address.Contains("DBD") || address.Contains("DD")) { if (float.TryParse(valueStr, out float f)) { plc.Write(address, f); return true; } else if (int.TryParse(valueStr, out int i)) { plc.Write(address, i); return true; } } // 字(DBW/DW):16位整数 else if (address.Contains("DBW") || address.Contains("DW")) { if (short.TryParse(valueStr, out short s)) { plc.Write(address, s); return true; } } // 字节(DBB/DB):8位整数 else if (address.Contains("DBB") || address.Contains("DB")) { if (byte.TryParse(valueStr, out byte b)) { plc.Write(address, b); return true; } } // 位(DBX):布尔值(兼容0/1输入) else if (address.Contains("DBX")) { if (bool.TryParse(valueStr, out bool bl)) { plc.Write(address, bl); return true; } else if (valueStr == "0") { plc.Write(address, false); return true; } else if (valueStr == "1") { plc.Write(address, true); return true; } } // 不匹配的类型 return false; } /// /// 带超时的PLC连接测试 /// public async Task TestSinglePlcAsync(PlcConfig config, CancellationToken cancellationToken) { // 替换为实际的PLC连接逻辑(如S7NetPlus) // 示例: // using var plc = new S7Client(); // var result = plc.ConnectTo(config.Ip, config.Rack, config.Slot); // return new PlcTestResult { ConnectSuccess = result == 0, ConnectMessage = result == 0 ? "成功" : "失败" }; await Task.Delay(100, cancellationToken); return new PlcTestResult { ConnectSuccess = true, ConnectMessage = "连接正常" }; } /// /// 读取PLC数据 /// public async Task<(bool success, object value, string message)> ReadPlcDataAsync(string ip, int rack, int slot, string address) { // 替换为实际的PLC读取逻辑 await Task.Delay(50); return (true, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), "读取成功"); } /// /// 实现IDisposable接口,释放资源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // 通知GC无需调用终结器 } /// /// 实际释放资源的逻辑(区分托管/非托管资源) /// /// 是否释放托管资源 protected virtual void Dispose(bool disposing) { if (_disposed) return; // 避免重复释放 // 释放托管资源(此处无长期持有的PLC连接,若有可在此处理) if (disposing) { // 示例:若有全局PLC连接对象,可在此释放 // if (_globalPlc != null) _globalPlc.Dispose(); } // 标记为已释放 _disposed = true; } /// /// 终结器(防忘记手动Dispose) /// ~PlcService() { Dispose(false); } } }