using System; using System.Threading.Tasks; using XP.Common.Logging.Interfaces; using XP.Hardware.Plc.Abstractions; using XP.Hardware.Plc.Exceptions; using XP.Hardware.Plc.Services; using XP.Hardware.PLC.Configs; using XP.Hardware.PLC.Helpers; using XP.Hardware.PLC.Models; using XP.Hardware.PLC.Services; namespace XP.Hardware.Plc.Services { /// /// PLC 信号数据交互服务实现 | PLC signal data interaction service implementation /// 提供基于信号逻辑名称的缓存读取、队列写入和直接写入+回读校验操作 /// Provides cache read, queue write, and direct write with read-back verification based on signal logical names /// public class SignalDataService : ISignalDataService { private readonly PlcService _plcService; private readonly PlcWriteQueue _writeQueue; private readonly IPlcClient _directWriteChannel; private readonly ILoggerService _logger; /// /// 直接写入通道引用,供 PlcService 管理连接生命周期 /// Direct write channel reference, for PlcService to manage connection lifecycle /// public IPlcClient DirectWriteChannel => _directWriteChannel; /// /// 构造函数 | Constructor /// /// PLC 服务实例,提供缓存快照和信号查找 | PLC service instance for cache snapshot and signal lookup /// PLC 写入队列,用于队列写入操作 | PLC write queue for enqueue write operations /// 直接写入通道,独立的 IPlcClient 实例用于高优先级写入 | Direct write channel, independent IPlcClient instance for high-priority writes /// 日志服务 | Logger service public SignalDataService( PlcService plcService, PlcWriteQueue writeQueue, IPlcClient directWriteChannel, ILoggerService logger) { _plcService = plcService ?? throw new ArgumentNullException(nameof(plcService)); _writeQueue = writeQueue ?? throw new ArgumentNullException(nameof(writeQueue)); _directWriteChannel = directWriteChannel ?? throw new ArgumentNullException(nameof(directWriteChannel)); _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); } /// /// 从 ReadDbBlock 配置字符串中提取 DB 块号 | Extract DB block number from ReadDbBlock config string /// 例如 "DB1" → 1,"DB31" → 31 | e.g. "DB1" → 1, "DB31" → 31 /// /// ReadDbBlock 配置值 | ReadDbBlock config value /// DB 块号 | DB block number private int ExtractDbNumber(string readDbBlock) { if (string.IsNullOrEmpty(readDbBlock)) return -1; // 移除 "DB" 前缀,解析数字部分 | Remove "DB" prefix, parse numeric part var numStr = readDbBlock.StartsWith("DB", StringComparison.OrdinalIgnoreCase) ? readDbBlock.Substring(2) : readDbBlock; return int.TryParse(numStr, out var num) ? num : -1; } /// /// 格式化单点读取地址 | Format single-point read address /// bool 类型追加位索引 | Bool type appends bit index /// /// 信号定义条目 | Signal definition entry /// 格式化后的 PLC 地址字符串 | Formatted PLC address string private string FormatReadAddress(SignalEntry signal) { return signal.Type.ToLowerInvariant() switch { "bool" => $"DB{signal.DBNumber}.{signal.StartAddr}.{signal.IndexOrLength}", _ => $"DB{signal.DBNumber}.{signal.StartAddr}" }; } /// /// 通过单点读取获取信号值 | Get signal value via single-point read /// 根据信号类型调用对应的 ReadAsync 方法 | Call corresponding ReadAsync method based on signal type /// /// 信号定义条目 | Signal definition entry /// PLC 地址 | PLC address /// 读取的信号值 | Read signal value private object ReadSinglePoint(SignalEntry signal, string address) { // 使用 Task.Run 在线程池执行异步读取,避免 UI 线程 SynchronizationContext 死锁 // Use Task.Run to execute async read on thread pool, avoiding UI thread SynchronizationContext deadlock return signal.Type.ToLowerInvariant() switch { "bool" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "byte" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "short" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "int" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "single" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "double" => Task.Run(() => _plcService.ReadValueAsync(address)).GetAwaiter().GetResult(), "string" => Task.Run(() => _plcService.ReadStringAsync(address, int.TryParse(signal.IndexOrLength, out var len) ? (ushort)len : (ushort)1)).GetAwaiter().GetResult(), _ => throw new PlcException( $"不支持的信号类型: {signal.Type}, 信号: {signal.Name} | " + $"Unsupported signal type: {signal.Type}, signal: {signal.Name}") }; } /// public object GetValueByName(string signalName) { // 查找信号定义 | Look up signal definition var signal = _plcService.FindSignal(signalName); // 提取批量读取参数 | Extract bulk read parameters var bulkDbNumber = ExtractDbNumber(_plcService.Config.ReadDbBlock); var bulkStart = _plcService.Config.ReadStartAddress; var bulkEnd = bulkStart + _plcService.Config.ReadLength - 1; // 判断是否走批量缓存:DB 号匹配 且 信号地址在缓存范围内 // Route to bulk cache: DB number matches AND signal address within cache range var useBulkCache = signal.DBNumber == bulkDbNumber && signal.StartAddr >= bulkStart && signal.StartAddr <= bulkEnd; object result; if (useBulkCache) { // 匹配批量读取范围,走缓存解析逻辑 | Within bulk read range, use cache parsing logic var dataBlock = _plcService.GetCacheSnapshot(); if (dataBlock == null) { throw new PlcException("数据缓存未就绪 | Data cache not ready"); } try { result = ParseSignalValue(signal, dataBlock); } catch (PlcException) { throw; } catch (Exception ex) { throw new PlcException( $"信号解析失败: {signal.Name}, 原因: {ex.Message} | Signal parsing failed: {signal.Name}, reason: {ex.Message}", ex); } } else { // 不在批量读取范围内,走单点读取逻辑 | Outside bulk read range, use single-point read var address = FormatReadAddress(signal); try { result = ReadSinglePoint(signal, address); } catch (PlcException) { throw; } catch (Exception ex) { throw new PlcException( $"单点读取失败: 信号={signal.Name}, DB块={signal.DBNumber}, 原因={ex.Message} | " + $"Single-point read failed: signal={signal.Name}, DB={signal.DBNumber}, reason={ex.Message}", ex); } } // 记录调试日志 | Log debug info _logger.Debug("读取信号: {SignalName}, DB块={DBNumber}, 结果: {Result} | Read signal: {SignalName}, DB={DBNumber}, result: {Result}", signalName, signal.DBNumber, result); return result; } /// public T GetValueByName(string signalName) { // 调用非泛型方法获取解析结果 | Call non-generic method to get parsed result var result = GetValueByName(signalName); // 将结果转换为目标类型 T | Convert result to target type T try { return (T)Convert.ChangeType(result, typeof(T)); } catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is OverflowException) { throw new PlcException( $"类型转换失败: 信号 {signalName}, 期望类型: {typeof(T).Name}, 实际类型: {result?.GetType().Name ?? "null"} | " + $"Type conversion failed: signal {signalName}, expected: {typeof(T).Name}, actual: {result?.GetType().Name ?? "null"}", ex); } } /// /// 根据信号定义从数据块中解析信号值 | Parse signal value from data block based on signal definition /// /// 信号定义条目 | Signal definition entry /// PLC 数据块快照 | PLC data block snapshot /// 解析后的信号值 | Parsed signal value private object ParseSignalValue(SignalEntry signal, PlcDataBlock dataBlock) { return signal.Type.ToLowerInvariant() switch { "bool" => dataBlock.GetBool(signal.StartAddr, int.TryParse(signal.IndexOrLength, out var bitIdx) ? bitIdx : 0), "byte" => dataBlock.GetByte(signal.StartAddr), "short" => dataBlock.GetInt16(signal.StartAddr), "int" => dataBlock.GetInt32(signal.StartAddr), "single" => dataBlock.GetFloat(signal.StartAddr), "double" => dataBlock.GetDouble(signal.StartAddr), "string" => dataBlock.GetString(signal.StartAddr, int.TryParse(signal.IndexOrLength, out var len) ? len : 1), _ => throw new PlcException( $"不支持的信号类型: {signal.Type}, 信号: {signal.Name} | " + $"Unsupported signal type: {signal.Type}, signal: {signal.Name}") }; } /// /// 根据信号类型获取对应的 CLR 类型 | Get CLR type based on signal type /// /// 信号类型字符串 | Signal type string /// 对应的 CLR 类型 | Corresponding CLR type private Type GetClrType(string signalType) { return signalType.ToLowerInvariant() switch { "bool" => typeof(bool), "byte" => typeof(byte), "short" => typeof(short), "int" => typeof(int), "single" => typeof(float), "double" => typeof(double), "string" => typeof(string), _ => throw new PlcException($"不支持的信号类型: {signalType} | Unsupported signal type: {signalType}") }; } /// /// 根据信号类型转换写入值 | Convert write value based on signal type /// 使用 System.Convert 执行安全类型转换 | Uses System.Convert for safe type conversion /// /// 信号名称,用于异常消息 | Signal name for exception messages /// 信号定义条目 | Signal definition entry /// 待转换的值 | Value to convert /// 转换后的值 | Converted value private object ConvertValue(string signalName, SignalEntry signal, object value) { try { var targetType = GetClrType(signal.Type); return Convert.ChangeType(value, targetType); } catch (Exception ex) when (ex is InvalidCastException || ex is OverflowException || ex is FormatException) { throw new PlcException( $"类型转换失败: 信号 {signalName}, 期望类型: {signal.Type}, 实际类型: {value?.GetType().Name ?? "null"} | " + $"Type conversion failed: signal {signalName}, expected: {signal.Type}, actual: {value?.GetType().Name ?? "null"}", ex); } } /// /// 根据信号定义格式化 PLC 写入地址 | Format PLC write address based on signal definition /// 使用信号所属 DBNumber 拼接地址 | Uses signal's DBNumber to construct address /// bool 类型包含位索引,其他类型仅包含起始地址 | Bool type includes bit index, others only include start address /// /// 信号定义条目 | Signal definition entry /// 格式化后的 PLC 地址字符串 | Formatted PLC address string private string FormatWriteAddress(SignalEntry signal) { return signal.Type.ToLowerInvariant() switch { "bool" => $"DB{signal.DBNumber}.{signal.StartAddr}.{signal.IndexOrLength}", _ => $"DB{signal.DBNumber}.{signal.StartAddr}" }; } /// public bool EnqueueWrite(string signalName, object value) { // 查找信号定义 | Look up signal definition var signal = _plcService.FindSignal(signalName); // 类型转换校验 | Type conversion validation var convertedValue = ConvertValue(signalName, signal, value); // 格式化写入地址 | Format write address var address = FormatWriteAddress(signal); // 检查队列状态 | Check queue status if (!_writeQueue.IsRunning) { _logger.Warn("写入队列未运行,无法入队: 信号={SignalName} | Write queue not running, cannot enqueue: signal={SignalName}", signalName); return false; } // 根据信号类型调用对应的入队方法 | Call corresponding enqueue method based on signal type var signalType = signal.Type.ToLowerInvariant(); if (signalType == "string") { var strValue = convertedValue as string ?? convertedValue.ToString(); var length = int.TryParse(signal.IndexOrLength, out var len) ? (ushort)len : (ushort)1; _writeQueue.EnqueueString(address, strValue, length); } else { // 根据具体类型调用泛型入队方法 | Call generic enqueue method based on specific type switch (signalType) { case "bool": _writeQueue.Enqueue(address, (bool)convertedValue); break; case "byte": _writeQueue.Enqueue(address, (byte)convertedValue); break; case "short": _writeQueue.Enqueue(address, (short)convertedValue); break; case "int": _writeQueue.Enqueue(address, (int)convertedValue); break; case "single": _writeQueue.Enqueue(address, (float)convertedValue); break; case "double": _writeQueue.Enqueue(address, (double)convertedValue); break; default: throw new PlcException($"不支持的信号类型: {signal.Type} | Unsupported signal type: {signal.Type}"); } } // 记录调试日志 | Log debug info _logger.Debug("队列写入: 信号={SignalName}, 值={Value}, 地址={Address} | Queue write: signal={SignalName}, value={Value}, address={Address}", signalName, convertedValue, address); return true; } /// public async Task WriteDirectWithVerify(string signalName, object value) { // 查找信号定义 | Look up signal definition var signal = _plcService.FindSignal(signalName); // 类型转换校验 | Type conversion validation var convertedValue = ConvertValue(signalName, signal, value); // 格式化写入地址 | Format write address var address = FormatWriteAddress(signal); // 检查 Direct_Write_Channel 连接状态 | Check Direct_Write_Channel connection status if (!_directWriteChannel.IsConnected) { _logger.Error(null, "直接写入通道未连接: 信号={SignalName} | Direct write channel not connected: signal={SignalName}", signalName); return false; } try { var signalType = signal.Type.ToLowerInvariant(); bool writeSuccess; object readBackValue; // 根据信号类型执行写入和回读操作 | Execute write and read-back based on signal type if (signalType == "string") { var strValue = convertedValue as string ?? convertedValue.ToString(); var length = int.TryParse(signal.IndexOrLength, out var len) ? (ushort)len : (ushort)1; // 写入字符串 | Write string writeSuccess = await _directWriteChannel.WriteStringAsync(address, strValue, length); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } // 回读字符串 | Read back string readBackValue = await _directWriteChannel.ReadStringAsync(address, length); } else { // 根据具体类型执行泛型写入和回读 | Execute generic write and read-back based on specific type switch (signalType) { case "bool": writeSuccess = await _directWriteChannel.WriteAsync(address, (bool)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; case "byte": writeSuccess = await _directWriteChannel.WriteAsync(address, (byte)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; case "short": writeSuccess = await _directWriteChannel.WriteAsync(address, (short)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; case "int": writeSuccess = await _directWriteChannel.WriteAsync(address, (int)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; case "single": writeSuccess = await _directWriteChannel.WriteAsync(address, (float)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; case "double": writeSuccess = await _directWriteChannel.WriteAsync(address, (double)convertedValue); if (!writeSuccess) { _logger.Error(null, "直接写入失败: 信号={SignalName}, 地址={Address} | Direct write failed: signal={SignalName}, address={Address}", signalName, address); return false; } readBackValue = await _directWriteChannel.ReadAsync(address); break; default: throw new PlcException( $"不支持的信号类型: {signal.Type}, 信号: {signal.Name} | " + $"Unsupported signal type: {signal.Type}, signal: {signal.Name}"); } } // 比较回读值与写入值 | Compare read-back value with written value var isMatch = convertedValue.Equals(readBackValue); // Info 级别记录信号名称、写入值、回读值和校验结果 | Log at Info level _logger.Info("直接写入校验: 信号={SignalName}, 写入值={WriteValue}, 回读值={ReadValue}, 结果={Result} | " + "Direct write verify: signal={SignalName}, write={WriteValue}, readback={ReadValue}, result={Result}", signalName, convertedValue, readBackValue, isMatch ? "通过|Pass" : "失败|Fail"); if (!isMatch) { _logger.Error(null, "回读校验失败: 信号={SignalName}, 写入值={WriteValue}, 回读值={ReadValue} | " + "Read-back verification failed: signal={SignalName}, write={WriteValue}, readback={ReadValue}", signalName, convertedValue, readBackValue); } return isMatch; } catch (PlcException) { throw; } catch (Exception ex) { _logger.Error(ex, "直接写入异常: 信号={SignalName}, 错误={Message} | Direct write exception: signal={SignalName}, error={Message}", signalName, ex.Message); return false; } } } }