2026-01-17 10:42:02 +08:00

377 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 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<PlcConfig> _plcConfigs;
private readonly GlobalPlcConfig _globalConfig;
/// <summary>
/// 构造函数依赖注入获取PLC配置
/// </summary>
/// <param name="plcConfigs">所有PLC的连接配置</param>
/// <param name="globalConfig">PLC全局超时配置</param>
public PlcService(IOptions<List<PlcConfig>> plcConfigs, IOptions<GlobalPlcConfig> globalConfig)
{
_plcConfigs = plcConfigs.Value;
_globalConfig = globalConfig.Value;
}
/// <summary>
/// 测试单个PLC的连接、读、写功能
/// </summary>
/// <param name="config">单个PLC的配置参数</param>
/// <returns>测试结果</returns>
public async Task<PlcTestResult> 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;
}
/// <summary>
/// 批量测试配置文件中所有PLC的读写功能
/// </summary>
/// <returns>所有PLC的测试结果列表</returns>
/// <exception cref="Exception">未配置PLC参数时抛出</exception>
public async Task<List<PlcTestResult>> 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();
}
/// <summary>
/// 单独读取指定PLC的某个地址数据
/// </summary>
/// <param name="ip">PLC的IP地址</param>
/// <param name="rack">机架号</param>
/// <param name="slot">槽位号</param>
/// <param name="address">读取地址如DB1.DBD0</param>
/// <returns>读取结果(成功状态、值、消息)</returns>
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();
}
}
}
/// <summary>
/// 单独写入数据到指定PLC的某个地址
/// </summary>
/// <param name="ip">PLC的IP地址</param>
/// <param name="rack">机架号</param>
/// <param name="slot">槽位号</param>
/// <param name="address">写入地址如DB1.DBD0</param>
/// <param name="valueStr">写入的值(字符串格式)</param>
/// <returns>写入结果(成功状态、消息)</returns>
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();
}
}
}
/// <summary>
/// 格式化PLC读取的值转为友好的字符串
/// </summary>
/// <param name="value">PLC读取的原始值</param>
/// <returns>格式化后的字符串</returns>
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() // 其他类型直接转字符串
};
}
/// <summary>
/// 根据地址类型转换值并写入PLC
/// </summary>
/// <param name="plc">PLC连接对象</param>
/// <param name="address">写入地址</param>
/// <param name="valueStr">写入的值(字符串)</param>
/// <returns>是否写入成功</returns>
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/DW16位整数
else if (address.Contains("DBW") || address.Contains("DW"))
{
if (short.TryParse(valueStr, out short s))
{
plc.Write(address, s);
return true;
}
}
// 字节DBB/DB8位整数
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;
}
/// <summary>
/// 带超时的PLC连接测试
/// </summary>
public async Task<PlcTestResult> 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 = "连接正常" };
}
/// <summary>
/// 读取PLC数据
/// </summary>
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"), "读取成功");
}
/// <summary>
/// 实现IDisposable接口释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 通知GC无需调用终结器
}
/// <summary>
/// 实际释放资源的逻辑(区分托管/非托管资源)
/// </summary>
/// <param name="disposing">是否释放托管资源</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed) return; // 避免重复释放
// 释放托管资源此处无长期持有的PLC连接若有可在此处理
if (disposing)
{
// 示例若有全局PLC连接对象可在此释放
// if (_globalPlc != null) _globalPlc.Dispose();
}
// 标记为已释放
_disposed = true;
}
/// <summary>
/// 终结器防忘记手动Dispose
/// </summary>
~PlcService()
{
Dispose(false);
}
}
}