// 统一引入所有必要命名空间
using MDM.Services.Plant;
using Microsoft.Extensions.Options;
using RIZO.Admin.WebApi.PLC.Model;
using RIZO.Common;
using S7.Net;
using SqlSugar;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RIZO.Admin.WebApi.PLC.Service
{
///
/// PLC通信服务(封装连接、读写、生产数据采集等功能)
///
public class PlcService : IDisposable
{
#region 字段与构造函数
// 标记是否已释放资源,避免重复释放
private bool _disposed = false;
// PLC配置参数(从配置文件注入)
private readonly List _plcConfigs;
private readonly GlobalPlcConfig _globalConfig;
private PlcProductionDataService _plcProductionDataService = new PlcProductionDataService();
private PlantWorkstationService _plantWorkstationService = new PlantWorkstationService();
// 先在类中添加2个核心优化字段(支撑高频访问)
private readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(15, 50); // 限制20并发(适配50台PLC)
private readonly ConcurrentDictionary _plcConnPool = new(); // 连接池
// PLC地址映射(严格匹配业务地址清单)
private readonly Dictionary _plcStringMap = new()
{
{ "LineCode", ("DB1010.DBB50", 14) },
{ "IpStation", ("DB1010.DBB64", 16) },
{ "ProductCode", ("DB1010.DBB80", 16) },
{ "ProductName", ("DB1010.DBB94", 50) },
{ "PartCode", ("DB1010.DBB144", 16) },
{ "PartName", ("DB1010.DBB158", 28) },
{ "ProcessName", ("DB1010.DBB186", 12) },
{ "ParamName", ("DB1010.DBB198", 12) },
{ "ParamValue", ("DB1010.DBB210", 14) },
{ "Static16", ("DB1010.DBB238", 68) }
};
private readonly Dictionary _plcIntMap = new()
{
{ "QualificationFlag", "DB1010.DBW224" },
{ "ReworkFlag", "DB1010.DBW226" },
{ "ProductionCycle", "DB1010.DBW228" },
{ "AutoManual", "DB1010.DBW230" },
{ "RunStatus", "DB1010.DBW232" },
{ "QueryRequest", "DB1001.DBW2001" }
};
#region PLC地址块儿映射
// OP070-1 专属地址映射
private readonly Dictionary _op070_1IntMap = new Dictionary
{
{ "运行状态", "DB1001.DBW0" }, // 运行状态
{ "设备模式", "DB1001.DBW2" }, // 设备模式
{ "设备在线状态", "DB1001.DBW4" },// 设备在线状态
{ "ByPass", "DB1001.DBW6" }, // ByPass
{ "生产模式", "DB1001.DBW8" }, // 生产模式
{ "查询请求", "DB1001.DBW2000" }, // 查询请求
{ "保存请求", "DB1001.DBW2002" }, // 保存请求
{ "托盘号", "DB1001.DBW2004" }, // 托盘号
{ "相机结果", "DB1001.DBW2164" }, // 相机结果
{ "站位结果", "DB1001.DBW2166" } // 站位结果
};
private readonly Dictionary _op070_1StringMap = new Dictionary
{
{ "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte
{ "产品名称", ("DB1001.DBB1054", 48) }, // String[48]
{ "产品型号", ("DB1001.DBB2006", 28) }, // String[28]
{ "SN_1", ("DB1001.DBB2100", 28) }, // 条码查询
{ "SN_2", ("DB1001.DBB2134", 28) } // 结果上传条码
};
#endregion
///
/// 构造函数(依赖注入获取PLC配置)
///
/// 所有PLC的连接配置
/// PLC全局超时配置
public PlcService(IOptions globalConfig)
{
_globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig), "PLC全局配置不能为空");
//初始化plcConfigs
_plcConfigs = initPlcConfigs(_plcConfigs);
}
#endregion
#region 核心业务方法
/////
///// 读取PLC生产数据(严格匹配业务地址和解析规则)
/////
///// PLC IP地址
///// 机架号
///// 槽位号
///// PLC型号(默认S7-1500)
///// 读取结果(状态+数据+消息)
//public async Task<(bool Success, PlcProductionData Data, string Message)> ReadProductionDataAsync(
// string ip,
// short rack,
// short slot,
// CpuType cpuType = CpuType.S71500)
//{
// // 参数校验
// if (string.IsNullOrWhiteSpace(ip))
// return (false, null, "PLC IP地址不能为空");
// Plc plc = null;
// try
// {
// // 初始化PLC连接
// plc = CreatePlcClient(cpuType, ip, rack, slot);
// await OpenPlcConnectionAsync(plc);
// if (!plc.IsConnected)
// return (false, null, "PLC连接失败");
// // 构建生产数据实体
// var prodData = new PlcProductionData
// {
// PlcIp = ip.Trim(),
// OccurTime = DateTime.Now, // MES自配时间
// // 字符串字段(按规则解析)
// LineCode = await ReadPlcStringAsync(plc, _plcStringMap["LineCode"].Addr, _plcStringMap["LineCode"].Len),
// ProductCode = await ReadPlcStringAsync(plc, _plcStringMap["ProductCode"].Addr, _plcStringMap["ProductCode"].Len),
// ProductName = await ReadPlcStringAsync(plc, _plcStringMap["ProductName"].Addr, _plcStringMap["ProductName"].Len),
// PartCode = await ReadPlcStringAsync(plc, _plcStringMap["PartCode"].Addr, _plcStringMap["PartCode"].Len),
// PartName = await ReadPlcStringAsync(plc, _plcStringMap["PartName"].Addr, _plcStringMap["PartName"].Len),
// ProcessName = await ReadPlcStringAsync(plc, _plcStringMap["ProcessName"].Addr, _plcStringMap["ProcessName"].Len),
// ParamName = await ReadPlcStringAsync(plc, _plcStringMap["ParamName"].Addr, _plcStringMap["ParamName"].Len),
// ParamValue = await ReadPlcStringAsync(plc, _plcStringMap["ParamValue"].Addr, _plcStringMap["ParamValue"].Len),
// // 修复:改为异步读取整数,确保类型兼容
// //合格标志(0默认,1合格,2不合格)
// QualificationFlag = (await ReadPlcIntAsync(plc, _plcIntMap["QualificationFlag"])).ToString(),
// //返工标志(0正常,1返工)
// ReworkFlag = (await ReadPlcIntAsync(plc, _plcIntMap["ReworkFlag"])).ToString(),
// //设备自动手动:0-自动,1-手动
// AutoManual = (await ReadPlcIntAsync(plc, _plcIntMap["AutoManual"])),
// //运行状态:1正常,2异常
// RunStatus = (await ReadPlcIntAsync(plc, _plcIntMap["RunStatus"])),
// //生产节拍秒
// ProductionCycle = await ReadPlcIntAsync(plc, _plcIntMap["ProductionCycle"])
// };
// // 空值兜底(避免入库报错)
// prodData.QualificationFlag ??= "0";
// prodData.ReworkFlag ??= "0";
// prodData.ProductionCycle ??= 0;
// // 保存生产数据到MES数据库
// _plcProductionDataService.AddPlcProductionData(prodData);
// return (true, prodData, "生产数据读取成功");
// }
// catch (Exception ex)
// {
// return (false, null, $"生产数据读取失败:{ex.Message}");
// }
// finally
// {
// ReleasePlcConnection(plc);
// }
//}
///
/// 读取PLC生产数据(优化版:连接池+并发控制+批量读取+异常防护)
///
/// PLC IP地址
/// 机架号
/// 槽位号
/// PLC型号(默认S7-1500)
/// 读取结果(状态+数据+消息)
public async Task<(bool Success, PlcProductionData Data, string Message)> ReadProductionDataAsync(
string ip,
string plcName,
short rack,
short slot,
CpuType cpuType = CpuType.S71500)
{
// 1. 强化参数校验(源头避免无效请求)
if (string.IsNullOrWhiteSpace(ip))
return (false, null, "PLC IP地址不能为空");
if (rack < 0 || rack > 10) // 工业场景机架号常规范围
return (false, null, $"PLC机架号{rack}无效(有效值0-10)");
if (slot < 0 || slot > 4) // 工业场景槽位号常规范围
return (false, null, $"PLC槽位号{slot}无效(有效值0-4)");
Plc plc = null;
bool isConnReused = false; // 标记是否复用连接
var poolKey = $"{ip}_{rack}_{slot}_{cpuType}"; // 连接池唯一标识
// 2. 并发控制(防止50台PLC同时访问导致端口/线程耗尽)
await _concurrencySemaphore.WaitAsync();
try
{
// 3. 连接池复用(核心优化:避免高频创建/销毁连接)
if (_plcConnPool.TryGetValue(poolKey, out var poolItem) && poolItem.Client.IsConnected)
{
plc = poolItem.Client;
_plcConnPool.TryUpdate(poolKey, (plc, DateTime.Now), poolItem); // 更新最后使用时间
isConnReused = true;
}
else
{
// 初始化新连接(保留你的原始逻辑,增加重试)
plc = CreatePlcClient(cpuType, ip, rack, slot);
// 连接重试(应对临时网络波动)
int retryCount = 2;
bool isConnected = false;
while (retryCount-- > 0 && !isConnected)
{
try
{
await OpenPlcConnectionAsync(plc);
isConnected = plc.IsConnected;
}
catch
{
if (retryCount > 0) await Task.Delay(100); // 重试间隔
}
}
if (!isConnected)
return (false, null, "PLC连接失败(含2次重试)");
// 新连接加入池
_plcConnPool.TryAdd(poolKey, (plc, DateTime.Now));
}
//先读取查询请求标志,判断是否需要读取生产数据
int iQueryRequest = await ReadPlcIntAsync(plc, _plcIntMap["QueryRequest"]);
if (iQueryRequest != 1)
{
return (false, null, "PLC查询请求标志未设置,跳过生产数据读取");
}
if (plcName == "OP070-1")
{
}
// 4. 批量并行读取(核心优化:替代串行await,提升效率30%+)
// 4.1 字符串字段批量读取
var stringTasks = new Dictionary>
{
{ "LineCode", ReadPlcStringAsync(plc, _plcStringMap["LineCode"].Addr, _plcStringMap["LineCode"].Len) },
{ "ProductCode", ReadPlcStringAsync(plc, _plcStringMap["ProductCode"].Addr, _plcStringMap["ProductCode"].Len) },
{ "ProductName", ReadPlcStringAsync(plc, _plcStringMap["ProductName"].Addr, _plcStringMap["ProductName"].Len) },
{ "PartCode", ReadPlcStringAsync(plc, _plcStringMap["PartCode"].Addr, _plcStringMap["PartCode"].Len) },
{ "PartName", ReadPlcStringAsync(plc, _plcStringMap["PartName"].Addr, _plcStringMap["PartName"].Len) },
{ "ProcessName", ReadPlcStringAsync(plc, _plcStringMap["ProcessName"].Addr, _plcStringMap["ProcessName"].Len) },
{ "ParamName", ReadPlcStringAsync(plc, _plcStringMap["ParamName"].Addr, _plcStringMap["ParamName"].Len) },
{ "ParamValue", ReadPlcStringAsync(plc, _plcStringMap["ParamValue"].Addr, _plcStringMap["ParamValue"].Len) }
};
// 4.2 整数字段批量读取
var intTasks = new Dictionary>
{
{ "QualificationFlag", ReadPlcIntAsync(plc, _plcIntMap["QualificationFlag"]) },
{ "ReworkFlag", ReadPlcIntAsync(plc, _plcIntMap["ReworkFlag"]) },
{ "AutoManual", ReadPlcIntAsync(plc, _plcIntMap["AutoManual"]) },
{ "RunStatus", ReadPlcIntAsync(plc, _plcIntMap["RunStatus"]) },
{ "ProductionCycle", ReadPlcIntAsync(plc, _plcIntMap["ProductionCycle"]) }
};
// 等待所有读取完成(并行执行,减少等待时间)
IEnumerable allTasks = stringTasks.Values.Cast()
.Concat(intTasks.Values.Cast());
await Task.WhenAll(allTasks);
// 5. 构建数据实体(空值安全处理,单个字段失败不影响整体)
var prodData = new PlcProductionData
{
PlcIp = ip.Trim(),
OccurTime = DateTime.Now,
// 字符串字段(空值兜底)
LineCode = await stringTasks["LineCode"] ?? string.Empty,
ProductCode = await stringTasks["ProductCode"] ?? string.Empty,
ProductName = await stringTasks["ProductName"] ?? string.Empty,
PartCode = await stringTasks["PartCode"] ?? string.Empty,
PartName = await stringTasks["PartName"] ?? string.Empty,
ProcessName = await stringTasks["ProcessName"] ?? string.Empty,
ParamName = await stringTasks["ParamName"] ?? string.Empty,
ParamValue = await stringTasks["ParamValue"] ?? string.Empty,
// 整数字段(读取失败返回0,不中断流程)
QualificationFlag = (await intTasks["QualificationFlag"]).ToString(),
ReworkFlag = (await intTasks["ReworkFlag"]).ToString(),
AutoManual = await intTasks["AutoManual"],
RunStatus = await intTasks["RunStatus"],
ProductionCycle = await intTasks["ProductionCycle"]
};
// 空值兜底(保留你的原始逻辑)
prodData.QualificationFlag ??= "0";
prodData.ReworkFlag ??= "0";
prodData.ProductionCycle ??= 0;
// 6. 异步保存数据(非阻塞,提升响应速度)
_ = Task.Run(() => _plcProductionDataService.AddPlcProductionData(prodData))
.ContinueWith(t =>
{
if (t.IsFaulted)
{
// 记录保存失败日志(建议替换为ILogger)
Console.WriteLine($"PLC({ip})数据保存失败:{t.Exception?.InnerException?.Message}");
}
});
var successMsg = isConnReused ? "生产数据读取成功(复用连接)" : "生产数据读取成功(新建连接)";
return (true, prodData, successMsg);
}
catch (Exception ex)
{
// 精细化异常日志(便于定位具体PLC故障)
Console.WriteLine($"PLC({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
return (false, null, $"生产数据读取失败:{ex.Message}");
}
finally
{
// 7. 资源安全释放(核心:复用连接不释放,仅新建连接释放;必须释放并发信号量)
if (!isConnReused)
{
ReleasePlcConnection(plc);
}
_concurrencySemaphore.Release(); // 防止死锁的关键
}
}
// 可选:添加连接池清理方法(防止长期运行导致连接泄露)
private void CleanupExpiredConnections()
{
var expiredTime = DateTime.Now.AddMinutes(-5); // 5分钟未使用的连接清理
// 先复制所有Key到列表,避免遍历中修改原集合
var allKeys = _plcConnPool.Keys.ToList();
foreach (var key in allKeys)
{
if (!_plcConnPool.TryGetValue(key, out var poolItem))
continue;
if (poolItem.LastUsedTime < expiredTime)
{
if (_plcConnPool.TryRemove(key, out var removedItem))
{
try
{
if (removedItem.Client != null && removedItem.Client.IsConnected)
removedItem.Client.Close();
if (removedItem.Client is IDisposable disposable)
disposable.Dispose();
// 可选:置空引用,帮助GC回收
removedItem.Client = null;
}
catch
{
// 静默释放,避免清理失败影响主流程
}
}
}
}
}
// 在类的构造函数中启动定时清理(可选)
// public PlcService(IOptions globalConfig)
// {
// _globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig));
// _plcConfigs = initPlcConfigs(_plcConfigs);
// // 每30秒清理一次过期连接
// new Timer(_ => CleanupExpiredConnections(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
// }
///
/// 测试单个PLC的连接、读、写功能
///
/// 单个PLC的配置参数
/// 取消令牌
/// 测试结果
public async Task TestSinglePlcAsync(PlcConfig config, CancellationToken cancellationToken = default)
{
// 参数校验
if (config == null)
throw new ArgumentNullException(nameof(config), "PLC测试配置不能为空");
if (string.IsNullOrWhiteSpace(config.Ip))
return new PlcTestResult { PlcName = config.PlcName, Ip = config.Ip, ConnectSuccess = false, ConnectMessage = "IP地址为空" };
var result = new PlcTestResult
{
PlcName = config.PlcName,
Ip = config.Ip
};
Plc plc = null;
try
{
// 初始化PLC客户端
plc = CreatePlcClient(CpuType.S71500, config.Ip, config.Rack, config.Slot);
// 带超时的连接测试
var connectTask = OpenPlcConnectionAsync(plc);
if (await Task.WhenAny(connectTask, Task.Delay(_globalConfig.ReadWriteTimeout, cancellationToken)) != connectTask)
{
result.ConnectSuccess = false;
result.ConnectMessage = $"连接超时({_globalConfig.ReadWriteTimeout}ms)";
return result;
}
await connectTask;
// 检查连接状态
if (plc.IsConnected)
{
result.ConnectSuccess = true;
result.ConnectMessage = "连接成功";
}
else
{
result.ConnectSuccess = false;
result.ConnectMessage = "连接失败(PLC未返回连接状态)";
return result;
}
string TestAddress = "DB1010.DBB238"; // 测试地址
// 读取测试
if (!string.IsNullOrWhiteSpace(TestAddress))
{
try
{
var readValue = await Task.Run(() => plc.Read(TestAddress), cancellationToken);
result.ReadSuccess = true;
result.ReadValue = FormatPlcValue(readValue);
result.ReadMessage = "读取成功";
}
catch (Exception ex)
{
result.ReadSuccess = false;
result.ReadMessage = $"读取失败:{ex.Message}";
}
}
// 写入测试
if (!string.IsNullOrWhiteSpace(TestAddress) && !string.IsNullOrWhiteSpace(TestAddress))
{
try
{
bool writeOk = await Task.Run(() => WritePlcValue(plc, TestAddress, "1"), cancellationToken);
result.WriteSuccess = writeOk;
result.WriteMessage = writeOk ? "写入成功" : "写入失败(值类型与地址不匹配)";
}
catch (Exception ex)
{
result.WriteSuccess = false;
result.WriteMessage = $"写入失败:{ex.Message}";
}
}
}
catch (OperationCanceledException)
{
result.ConnectSuccess = false;
result.ConnectMessage = "测试被取消";
}
catch (Exception ex)
{
result.ConnectSuccess = false;
result.ConnectMessage = $"连接异常:{ex.Message}";
}
finally
{
ReleasePlcConnection(plc);
}
return result;
}
///
/// 批量测试配置文件中所有PLC的读写功能
///
/// 取消令牌
/// 所有PLC的测试结果列表
public async Task> BatchTestAllPlcAsync(CancellationToken cancellationToken = default)
{
if (_plcConfigs == null || _plcConfigs.Count == 0)
throw new InvalidOperationException("未从配置文件加载到任何PLC参数,请检查PlcConfigs配置");
// 并行测试所有PLC(带最大并行度限制,避免资源耗尽)
var testTasks = _plcConfigs
.Select(config => TestSinglePlcAsync(config, cancellationToken))
.ToList();
var results = await Task.WhenAll(testTasks);
return results.ToList();
}
///
/// 单独读取指定PLC的某个地址数据
///
/// PLC的IP地址
/// 机架号
/// 槽位号
/// 读取地址(如DB1.DBD0)
/// PLC型号
/// 读取结果(成功状态、值、消息)
public async Task<(bool Success, string Value, string Message)> ReadPlcDataAsync(
string ip,
short rack,
short slot,
string address,
CpuType cpuType = CpuType.S71500)
{
// 参数校验
if (string.IsNullOrWhiteSpace(ip))
return (false, "", "PLC IP地址不能为空");
if (string.IsNullOrWhiteSpace(address))
return (false, "", "读取地址不能为空");
Plc plc = null;
try
{
plc = CreatePlcClient(cpuType, ip, rack, slot);
await OpenPlcConnectionAsync(plc);
if (!plc.IsConnected)
return (false, "", "PLC连接失败,请检查IP/机架号/槽位号是否正确");
var value = await Task.Run(() => plc.Read(address));
return (true, FormatPlcValue(value), "读取成功");
}
catch (Exception ex)
{
return (false, "", $"读取失败:{ex.Message}");
}
finally
{
ReleasePlcConnection(plc);
}
}
///
/// 单独写入数据到指定PLC的某个地址
///
/// PLC的IP地址
/// 机架号
/// 槽位号
/// 写入地址(如DB1.DBD0)
/// 写入的值(字符串格式)
/// PLC型号
/// 写入结果(成功状态、消息)
public async Task<(bool Success, string Message)> WritePlcDataAsync(
string ip,
short rack,
short slot,
string address,
string valueStr,
CpuType cpuType = CpuType.S71500)
{
// 参数校验
if (string.IsNullOrWhiteSpace(ip))
return (false, "PLC IP地址不能为空");
if (string.IsNullOrWhiteSpace(address))
return (false, "写入地址不能为空");
if (valueStr == null) // 允许空字符串(如清空值)
return (false, "写入值不能为null");
Plc plc = null;
try
{
plc = CreatePlcClient(cpuType, ip, rack, slot);
await OpenPlcConnectionAsync(plc);
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
{
ReleasePlcConnection(plc);
}
}
#endregion
#region 私有辅助方法
///
/// 创建PLC客户端实例(统一配置超时)
///
private Plc CreatePlcClient(CpuType cpuType, string ip, short rack, short slot)
{
var plc = new Plc(cpuType, ip, rack, slot)
{
ReadTimeout = _globalConfig.ReadWriteTimeout,
WriteTimeout = _globalConfig.ReadWriteTimeout
};
return plc;
}
///
/// 异步打开PLC连接(封装同步方法为异步)
///
private async Task OpenPlcConnectionAsync(Plc plc)
{
if (plc == null) return;
await Task.Run(() => plc.Open());
}
///
/// 释放PLC连接(安全关闭+资源释放)
///
private void ReleasePlcConnection(Plc plc)
{
if (plc == null) return;
try
{
if (plc.IsConnected)
plc.Close();
// 正确释放PLC实例(适配S7.Net显式实现IDisposable)
if (plc is IDisposable disposable)
disposable.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"PLC连接关闭失败:{ex.Message}");
}
// 移除finally中的Dispose(),避免误释放服务
}
///
/// 按业务规则解析PLC字符串(支持中文GBK编码)
///
private async Task ReadPlcStringAsync(Plc plc, string addr, int maxLen)
{
try
{
// 解析地址:DB1010.DBB50 → DB编号1010 + 起始字节50
var parts = addr.Split('.', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return string.Empty;
if (!int.TryParse(parts[0].Replace("DB", ""), out int dbNum) || dbNum <= 0)
return string.Empty;
if (!int.TryParse(parts[1].Replace("DBB", ""), out int startByte) || startByte < 0)
return string.Empty;
// 读取字节数组
var bytes = await Task.Run(() => plc.ReadBytes(DataType.DataBlock, dbNum, startByte, maxLen));
if (bytes == null || bytes.Length < 3)
return string.Empty;
// 严格按规则解析:bytes[0]=总长度(备用)、bytes[1]=有效长度、bytes[2+]=内容
int validLen = Math.Clamp(bytes[1], 0, bytes.Length - 2);
if (validLen <= 0)
return string.Empty;
// 提取有效内容并转字符串(GBK编码支持中文)
var contentBytes = new byte[validLen];
Array.Copy(bytes, 2, contentBytes, 0, validLen);
return Encoding.GetEncoding("GBK").GetString(contentBytes).Trim('\0', ' ', '\t');
}
catch
{
return string.Empty;
}
}
///
/// 修复版:异步读取PLC整数(DBW地址,16位短整型)
/// 增强类型兼容、异常日志、地址校验
///
/// PLC客户端实例
/// 读取地址(如DB1010.DBW226)
/// 解析后的整数值,读取失败返回0
private async Task ReadPlcIntAsync(Plc plc, string addr)
{
// 1. 地址和PLC实例有效性校验
if (plc == null)
{
Console.WriteLine($"PLC整数读取失败:PLC实例为空(地址:{addr})");
return 0;
}
if (string.IsNullOrWhiteSpace(addr))
{
Console.WriteLine("PLC整数读取失败:读取地址为空");
return 0;
}
try
{
// 2. 异步读取(避免同步阻塞,适配整体异步上下文)
var val = await Task.Run(() => plc.Read(addr));
// 调试日志:输出原始值和类型,便于排查问题
Console.WriteLine($"PLC地址[{addr}]原始读取值:{val ?? "null"},类型:{val?.GetType().Name ?? "未知"}");
// 3. 增强类型兼容(覆盖PLC可能返回的所有整数类型)
return val switch
{
short s => s, // 16位短整型(DBW默认类型)
int i => i, // 32位整型(兼容返回)
uint ui => (int)ui, // 无符号32位整型
byte b => b, // 8位字节型
ushort us => us, // 无符号16位整型
long l => (int)l, // 64位整型(截断为32位)
ulong ul => (int)ul, // 无符号64位整型(截断为32位)
float f => (int)f, // 浮点数转整型(兼容PLC数值存储)
double d => (int)d, // 双精度浮点数转整型
_ => 0 // 未知类型返回0
};
}
catch (Exception ex)
{
// 4. 输出详细异常日志,便于定位问题
Console.WriteLine($"PLC整数读取失败(地址:{addr}):{ex.Message},堆栈:{ex.StackTrace}");
return 0;
}
}
///
/// 格式化PLC读取的值,转为友好的字符串
///
private string FormatPlcValue(object value)
{
if (value == null) return "空值";
return value switch
{
float f => f.ToString("0.000"), // 浮点数保留3位小数
double d => d.ToString("0.000"), // 补充双精度浮点数支持
short s => s.ToString(), // 16位整数
byte b => b.ToString(), // 8位整数
bool b => b.ToString(), // 布尔值
int i => i.ToString(), // 32位整数
uint ui => ui.ToString(), // 无符号32位整数
long l => l.ToString(), // 64位整数
ulong ul => ul.ToString(), // 无符号64位整数
_ => value.ToString() // 其他类型直接转字符串
};
}
///
/// 根据地址类型转换值并写入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;
}
#endregion
#region 资源释放
///
/// 实现IDisposable接口,释放资源
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 通知GC无需调用终结器
}
///
/// 实际释放资源的逻辑(区分托管/非托管资源)
///
/// 是否释放托管资源
protected virtual void Dispose(bool disposing)
{
if (_disposed) return; // 避免重复释放
// 释放托管资源
if (disposing)
{
// 此处无长期持有的PLC连接,若有可在此统一释放
}
// 标记为已释放
_disposed = true;
}
///
/// 终结器(防忘记手动Dispose)
///
~PlcService()
{
Dispose(false);
}
#endregion
private List initPlcConfigs(List result)
{
var defaultResult = result ?? new List();
try
{
List query = _plantWorkstationService.Queryable()
.Where(it => it.Status == 1)
.Select(it => new PlcConfig
{
PlcName = it.WorkstationCode,
Ip = it.PlcIP,
Rack = (short)it.Rack, // 直接强制转换(it.Rack是int,非空)
Slot = (short)it.Slot // 同理,it.Slot是int,非空
})
.ToList();
return query.Count > 0 ? query : defaultResult;
}
catch (Exception ex)
{
Console.WriteLine($"初始化PLC配置异常:{ex.Message}");
return defaultResult;
}
}
}
}