using Prism.Mvvm; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using XP.Common.Logging.Interfaces; using XP.Hardware.Plc.Abstractions; using XP.Hardware.Plc.Core; using XP.Hardware.Plc.Exceptions; 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 服务层,适配 Prism MVVM 架构 | PLC service layer, adapted for Prism MVVM architecture /// public class PlcService : BindableBase, IPlcService, IDisposable { private readonly IPlcClient _plcClient; private readonly ILoggerService _logger; private PlcConfig _config; private Timer _connectionMonitorTimer; private int _reconnectAttempts = 0; private bool _isConnected; private string _statusText; private bool _disposed = false; // 信号解析器和统一信号字典 | Signal parser and unified signal dictionary private readonly XmlSignalParser _signalParser; private Dictionary _signalDict = new Dictionary(); // 批量读取缓存和定时任务 | Bulk read cache and timer private byte[] _bulkReadCache; private readonly object _cacheLock = new object(); private Timer _bulkReadTimer; private int _bulkReadFailCount = 0; private volatile bool _disconnectNotified = false; // 写入队列和直接写入通道引用,由 PlcService 统一管理生命周期 // Write queue and direct write channel references, lifecycle managed by PlcService private PlcWriteQueue _writeQueue; private IPlcClient _directWriteChannel; /// /// PLC 配置,供同程序集内部访问 | PLC configuration, accessible within the same assembly /// internal PlcConfig Config => _config; /// /// PLC 连接状态 | PLC connection status /// public bool IsConnected { get => _isConnected; private set => SetProperty(ref _isConnected, value); } /// /// 状态文本,用于 UI 显示 | Status text for UI display /// public string StatusText { get => _statusText; private set => SetProperty(ref _statusText, value); } /// /// 构造函数 | Constructor /// /// PLC 客户端接口 | PLC client interface /// 日志服务 | Logger service /// XML 信号解析器 | XML signal parser public PlcService(IPlcClient plcClient, ILoggerService logger, XmlSignalParser signalParser) { _plcClient = plcClient ?? throw new ArgumentNullException(nameof(plcClient)); // 使用泛型方法自动获取完整类型名:XP.Hardware.Plc.Services.PlcService _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); _signalParser = signalParser ?? throw new ArgumentNullException(nameof(signalParser)); StatusText = "未初始化 | Not initialized"; } /// /// 注册写入队列和直接写入通道,由 PLCModule 在 DI 注册后调用 /// Register write queue and direct write channel, called by PLCModule after DI registration /// /// 写入队列实例 | Write queue instance /// 直接写入通道实例 | Direct write channel instance public void RegisterWriteComponents(PlcWriteQueue writeQueue, IPlcClient directWriteChannel) { _writeQueue = writeQueue; _directWriteChannel = directWriteChannel; } /// /// 初始化 PLC 连接 | Initialize PLC connection /// /// PLC 配置 | PLC configuration /// 连接是否成功 | Whether connection succeeded public async Task InitializeAsync(PlcConfig config) { try { _config = config ?? throw new ArgumentNullException(nameof(config)); _logger.Info("正在初始化 PLC 连接... | Initializing PLC connection..."); var result = await _plcClient.ConnectAsync(config); if (result) { StatusText = "连接成功 | Connection successful"; _logger.Info("PLC 连接成功 | PLC connection successful"); // 启动连接监控 | Start connection monitoring StartConnectionMonitoring(); // 启动批量读取定时任务 | Start bulk read timer _bulkReadTimer = new Timer(BulkReadCallback, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_config.BulkReadIntervalMs)); _logger.Info("批量读取定时任务已启动,周期 {Interval}ms | Bulk read timer started, interval {Interval}ms", _config.BulkReadIntervalMs); // 启动写入队列 | Start write queue if (_writeQueue != null) { // 连接写入队列的 PLC 客户端(独立 Socket)| Connect write queue's PLC client (independent socket) try { var wqResult = await _writeQueue.PlcClient.ConnectAsync(config); if (wqResult) { _writeQueue.Start(); _logger.Info("写入队列 PLC 通道连接成功并已启动 | Write queue PLC channel connected and started"); } else { _logger.Warn("写入队列 PLC 通道连接失败,队列写入将不可用 | Write queue PLC channel connection failed, queue write will be unavailable"); } } catch (Exception wqEx) { _logger.Error(wqEx, "写入队列 PLC 通道连接异常: {Message} | Write queue PLC channel connection exception: {Message}", wqEx.Message); } } // 连接直接写入通道(使用相同配置,独立 Socket)| Connect direct write channel (same config, independent socket) if (_directWriteChannel != null) { try { var dwcResult = await _directWriteChannel.ConnectAsync(config); if (dwcResult) { _logger.Info("直接写入通道连接成功 | Direct write channel connected"); } else { _logger.Warn("直接写入通道连接失败,WriteDirectWithVerify 将不可用 | Direct write channel connection failed, WriteDirectWithVerify will be unavailable"); } } catch (Exception dwcEx) { _logger.Error(dwcEx, "直接写入通道连接异常: {Message} | Direct write channel connection exception: {Message}", dwcEx.Message); } } } else { StatusText = "连接失败 | Connection failed"; _logger.Warn("PLC 连接失败 | PLC connection failed"); } return result; } catch (Exceptions.PlcException ex) { StatusText = $"连接异常: {ex.Message} | Connection exception: {ex.Message}"; _logger.Error(ex, $"PLC 初始化失败: {ex.Message} | PLC initialization failed: {ex.Message}"); MessageBox.Show($"PLC初始化失败 | PLC initialization failed: {ex.Message}", "错误 | Error", MessageBoxButton.OK, MessageBoxImage.Error); return false; } catch (Exception ex) { StatusText = $"连接异常: {ex.Message} | Connection exception: {ex.Message}"; _logger.Error(ex, $"PLC 初始化异常: {ex.Message} | PLC initialization exception: {ex.Message}"); MessageBox.Show($"PLC初始化失败 | PLC initialization failed: {ex.Message}", "错误 | Error", MessageBoxButton.OK, MessageBoxImage.Error); return false; } } /// /// 启动连接状态监控 | Start connection status monitoring /// private void StartConnectionMonitoring() { if (_connectionMonitorTimer != null) { return; } _logger.Info("启动连接状态监控 | Starting connection status monitoring"); // 创建 Timer,首次延迟 1 秒,之后每 3 秒检查一次 // Create Timer, first delay 1 second, then check every 3 seconds _connectionMonitorTimer = new Timer( CheckConnectionStatus, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3)); } /// /// 停止连接状态监控 | Stop connection status monitoring /// private void StopConnectionMonitoring() { if (_connectionMonitorTimer != null) { _logger.Info("停止连接状态监控 | Stopping connection status monitoring"); _connectionMonitorTimer.Dispose(); _connectionMonitorTimer = null; } } /// /// 检查连接状态 | Check connection status /// /// 状态对象 | State object private void CheckConnectionStatus(object state) { try { // 先检查 _plcClient.IsConnected 标志 | First check _plcClient.IsConnected flag var clientReportsConnected = _plcClient.IsConnected; // 如果客户端报告已连接,但批量读取连续失败,则认为实际已断开 // If client reports connected but bulk read has consecutive failures, treat as disconnected var effectiveStatus = clientReportsConnected && _bulkReadFailCount < 3; // 检测到连接状态变化 | Connection status change detected if (effectiveStatus != IsConnected) { IsConnected = effectiveStatus; if (effectiveStatus) { StatusText = "PLC 连接正常 | PLC connection normal"; _logger.Info("PLC 连接状态恢复 | PLC connection status restored"); } else { StatusText = "PLC 连接断开 | PLC connection disconnected"; _logger.Warn("检测到 PLC 连接断开 | PLC connection disconnected detected"); } } // 已断开且启用自动重连时,持续尝试重连 | When disconnected and auto-reconnect enabled, keep retrying if (!IsConnected && _config?.bReConnect == true) { _ = TryReconnectAsync(); } } catch (Exception ex) { _logger.Error(ex, $"检查连接状态时发生异常: {ex.Message} | Exception occurred while checking connection status: {ex.Message}"); } } /// /// 尝试自动重连 | Try auto-reconnection /// private async Task TryReconnectAsync() { _reconnectAttempts++; _logger.Warn($"检测到连接断开,尝试第 {_reconnectAttempts} 次重连... | Connection disconnected detected, attempting reconnection #{_reconnectAttempts}..."); StatusText = $"正在重连 (第 {_reconnectAttempts} 次)... | Reconnecting (attempt #{_reconnectAttempts})..."; try { var result = await _plcClient.ConnectAsync(_config); if (result) { _reconnectAttempts = 0; _bulkReadFailCount = 0; _disconnectNotified = false; IsConnected = true; StatusText = "重连成功 | Reconnection successful"; _logger.Info("PLC 重连成功 | PLC reconnection successful"); } else { StatusText = $"重连失败 (第 {_reconnectAttempts} 次) | Reconnection failed (attempt #{_reconnectAttempts})"; _logger.Warn($"PLC 重连失败 (第 {_reconnectAttempts} 次) | PLC reconnection failed (attempt #{_reconnectAttempts})"); } } catch (Exceptions.PlcException ex) { StatusText = $"重连异常: {ex.Message} | Reconnection exception: {ex.Message}"; _logger.Error(ex, $"PLC 重连失败 (第 {_reconnectAttempts} 次): {ex.Message} | PLC reconnection failed (attempt #{_reconnectAttempts}): {ex.Message}"); } catch (Exception ex) { StatusText = $"重连异常: {ex.Message} | Reconnection exception: {ex.Message}"; _logger.Error(ex, $"PLC 重连异常 (第 {_reconnectAttempts} 次): {ex.Message} | PLC reconnection exception (attempt #{_reconnectAttempts}): {ex.Message}"); } } /// /// 批量读取定时回调,周期性从 PLC 读取数据块并更新缓存 | Bulk read timer callback, periodically reads data block from PLC and updates cache /// /// 状态对象 | State object private async void BulkReadCallback(object state) { // 未连接时跳过读取,等待重连成功 | Skip read when disconnected, wait for reconnection if (!IsConnected) return; try { var data = await _plcClient.ReadBytesAsync(_config.ReadDbBlock, _config.ReadStartAddress, _config.ReadLength); // 批量读取成功,线程安全更新缓存 | Bulk read succeeded, update cache with thread safety lock (_cacheLock) { _bulkReadCache = data; } // 读取成功,重置连续失败计数 | Read succeeded, reset consecutive failure count _bulkReadFailCount = 0; } catch (Exception ex) { _bulkReadFailCount++; // 仅首次和每10次记录日志,避免刷屏 | Log only first and every 10th to avoid spam if (_bulkReadFailCount == 1 || _bulkReadFailCount % 10 == 0) { _logger.Error(ex, "批量读取 PLC 数据失败(第{Count}次): {Message} | Bulk read PLC data failed (#{Count}): {Message}", _bulkReadFailCount, ex.Message); } // 连续失败达到阈值,判定连接已断开 | Consecutive failures reached threshold, mark as disconnected if (_bulkReadFailCount >= 2 && IsConnected && !_disconnectNotified) { _disconnectNotified = true; _logger.Warn("批量读取连续失败 {Count} 次,判定 PLC 连接已断开 | Bulk read failed {Count} consecutive times, marking PLC as disconnected", _bulkReadFailCount); IsConnected = false; StatusText = "PLC 连接断开(批量读取失败)| PLC disconnected (bulk read failed)"; // 清空缓存,避免返回过期数据 | Clear cache to avoid returning stale data lock (_cacheLock) { _bulkReadCache = null; } // 首次断开时弹窗提示(非阻塞,投递到 UI 线程)| Show non-blocking notification on first disconnect var autoReconnect = _config?.bReConnect == true; var message = autoReconnect ? "PLC 连接已断开,系统将自动尝试重连。\nPLC connection lost, auto-reconnecting..." : "PLC 连接已断开,自动重连未启用,请手动检查连接。\nPLC connection lost, auto-reconnect disabled. Please check manually."; Application.Current?.Dispatcher?.BeginInvoke(new Action(() => { MessageBox.Show( Application.Current?.MainWindow, message, "PLC", MessageBoxButton.OK, MessageBoxImage.Warning); })); } } } /// /// 通用读取方法 | Generic read method /// /// 数据类型 | Data type /// PLC 地址 | PLC address /// 读取的值 | Read value public async Task ReadValueAsync(string address) { try { return await _plcClient.ReadAsync(address); } catch (Exceptions.PlcException ex) { StatusText = $"读取失败: {ex.Message} | Read failed: {ex.Message}"; _logger.Error(ex, $"读取 PLC 数据失败: 地址={address}, 错误={ex.Message} | Failed to read PLC data: address={address}, error={ex.Message}"); throw; } } /// /// 按指定长度读取字符串 | Read string with specified length /// /// PLC 地址 | PLC address /// 字符串长度(字节数)| String length in bytes /// 读取的字符串 | Read string public async Task ReadStringAsync(string address, ushort length) { try { return await _plcClient.ReadStringAsync(address, length); } catch (Exceptions.PlcException ex) { StatusText = $"读取字符串失败: {ex.Message} | Read string failed: {ex.Message}"; _logger.Error(ex, $"读取字符串失败: 地址={address}, 长度={length}, 错误={ex.Message} | Read string failed"); throw; } } /// /// 按指定长度写入字符串 | Write string with specified length /// /// PLC 地址 | PLC address /// 字符串值 | String value /// 字符串长度(字节数)| String length in bytes /// 是否成功 | Whether succeeded public async Task WriteStringAsync(string address, string value, ushort length) { try { var result = await _plcClient.WriteStringAsync(address, value, length); if (!result) { StatusText = $"写入字符串失败: 地址={address} | Write string failed"; _logger.Warn($"写入字符串失败: 地址={address}, 值={value} | Write string failed"); } return result; } catch (Exceptions.PlcException ex) { StatusText = $"写入字符串异常: {ex.Message} | Write string exception"; _logger.Error(ex, $"写入字符串异常: 地址={address}, 错误={ex.Message} | Write string exception"); return false; } } /// /// 通用写入方法 | Generic write method /// /// 数据类型 | Data type /// PLC 地址 | PLC address /// 要写入的值 | Value to write /// 写入是否成功 | Whether write succeeded public async Task WriteValueAsync(string address, T value) { try { var result = await _plcClient.WriteAsync(address, value); if (!result) { StatusText = $"写入失败: 地址={address} | Write failed: address={address}"; _logger.Warn($"写入 PLC 数据失败: 地址={address}, 值={value} | Failed to write PLC data: address={address}, value={value}"); } return result; } catch (Exceptions.PlcException ex) { StatusText = $"写入异常: {ex.Message} | Write exception: {ex.Message}"; _logger.Error(ex, $"写入 PLC 数据异常: 地址={address}, 值={value}, 错误={ex.Message} | Exception writing PLC data: address={address}, value={value}, error={ex.Message}"); return false; } } /// /// 加载信号定义文件,建立读取和写入信号字典索引 | Load signal definitions and build dictionary indexes /// /// XML 信号定义文件路径 | XML signal definition file path public void LoadSignalDefinitions(string xmlFilePath) { _logger.Info($"正在加载信号定义文件: {xmlFilePath} | Loading signal definitions: {xmlFilePath}"); // 解析所有 Group | Parse all groups var groups = _signalParser.LoadFromFile(xmlFilePath); // 建立统一信号字典索引,检查信号名称全局唯一性 | Build unified signal dictionary, check global name uniqueness var newDict = new Dictionary(); foreach (var group in groups) { foreach (var signal in group.Signals) { if (newDict.TryGetValue(signal.Name, out var existing)) { // 信号名称重复,抛出异常 | Duplicate signal name, throw exception throw new PlcException( $"信号名称重复: '{signal.Name}',分别存在于 Group '{existing.GroupId}' 和 Group '{group.GroupId}' | " + $"Duplicate signal name: '{signal.Name}', found in Group '{existing.GroupId}' and Group '{group.GroupId}'"); } newDict[signal.Name] = signal; } } _signalDict = newDict; _logger.Info($"信号定义加载完成: 共 {_signalDict.Count} 个信号,来自 {groups.Count} 个 Group | " + $"Signal definitions loaded: {_signalDict.Count} signals from {groups.Count} groups"); } /// /// 按名称查找信号定义,O(1) 时间复杂度 | Find signal definition by name, O(1) lookup /// /// 信号逻辑名称 | Signal logical name /// 信号条目 | Signal entry /// 信号名称未找到时抛出 | Thrown when signal name not found public SignalEntry FindSignal(string signalName) { if (_signalDict.TryGetValue(signalName, out var entry)) { return entry; } throw new PlcException($"信号未找到: {signalName} | Signal not found: {signalName}"); } /// /// 获取当前批量读取缓存的 PlcDataBlock 快照 | Get PlcDataBlock snapshot of current bulk read cache /// 在 lock 内复制 byte[] 并创建新实例,确保快照不可变 | Copy byte[] inside lock and create new instance to ensure snapshot immutability /// /// 缓存快照,缓存为空时返回 null | Cache snapshot, returns null when cache is empty public PlcDataBlock GetCacheSnapshot() { lock (_cacheLock) { if (_bulkReadCache == null) { return null; } // 复制 byte[] 确保快照独立于缓存 | Copy byte[] to ensure snapshot is independent from cache var copy = new byte[_bulkReadCache.Length]; Array.Copy(_bulkReadCache, copy, _bulkReadCache.Length); return new PlcDataBlock(copy, _config.ReadStartAddress); } } /// /// 释放资源 | Dispose resources /// public void Dispose() { if (_disposed) { return; } _logger.Info("正在释放 PlcService 资源... | Disposing PlcService resources..."); // 停止批量读取定时任务并清空缓存 | Stop bulk read timer and clear cache if (_bulkReadTimer != null) { _bulkReadTimer.Dispose(); _bulkReadTimer = null; } lock (_cacheLock) { _bulkReadCache = null; } // 停止写入队列 | Stop write queue if (_writeQueue != null) { _writeQueue.Dispose(); } // 断开并释放直接写入通道 | Disconnect and dispose direct write channel if (_directWriteChannel is IDisposable dwcDisposable) { _directWriteChannel.Disconnect(); dwcDisposable.Dispose(); } StopConnectionMonitoring(); if (_plcClient is IDisposable disposable) { disposable.Dispose(); } _disposed = true; _logger.Info("PlcService 资源已释放 | PlcService resources disposed"); } } }