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;
}
}
}
}