将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Comet 225kV 射线源适配器
|
||||
/// 通过 CometIpcClient 和 CometHostManager 实现进程外隔离的 PVI 通信
|
||||
/// 所有操作委托给 CometIpcClient,Host 进程生命周期由 CometHostManager 管理
|
||||
/// </summary>
|
||||
public class Comet225RaySource : XRaySourceBase
|
||||
{
|
||||
#region 依赖注入字段
|
||||
|
||||
private readonly CometHostManager _hostManager;
|
||||
private readonly CometIpcClient _ipcClient;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,注入依赖
|
||||
/// </summary>
|
||||
/// <param name="hostManager">Host 进程生命周期管理器</param>
|
||||
/// <param name="ipcClient">IPC 客户端,负责与 Host 进程通信</param>
|
||||
/// <param name="loggerService">日志服务</param>
|
||||
public Comet225RaySource(
|
||||
CometHostManager hostManager,
|
||||
CometIpcClient ipcClient,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_hostManager = hostManager ?? throw new ArgumentNullException(nameof(hostManager));
|
||||
_ipcClient = ipcClient ?? throw new ArgumentNullException(nameof(ipcClient));
|
||||
_logger = loggerService?.ForModule<Comet225RaySource>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
public override string SourceName => "Comet 225kV";
|
||||
|
||||
#endregion
|
||||
|
||||
#region IXRaySource 方法实现 - 委托给 CometIpcClient
|
||||
|
||||
/// <summary>
|
||||
/// 初始化射线源
|
||||
/// 先确保 Host 进程运行,再通过 IPC 客户端初始化 PVI 连接
|
||||
/// </summary>
|
||||
public override XRayResult Initialize()
|
||||
{
|
||||
_logger.Info("初始化 Comet225 射线源(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 先确保 Host 进程运行
|
||||
_hostManager.EnsureRunning();
|
||||
_logger.Info("Host 进程已就绪,开始 IPC 初始化");
|
||||
|
||||
// 再通过 IPC 客户端初始化
|
||||
var result = _ipcClient.Initialize();
|
||||
if (result.Success)
|
||||
{
|
||||
_isInitialized = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "初始化 Comet225 射线源异常:{Message}", ex.Message);
|
||||
return XRayResult.Error($"初始化异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建和连接 PVI 变量
|
||||
/// </summary>
|
||||
public override XRayResult ConnectVariables()
|
||||
{
|
||||
var result = _ipcClient.ConnectVariables();
|
||||
if (result.Success)
|
||||
{
|
||||
_isConnected = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线源高压
|
||||
/// </summary>
|
||||
public override XRayResult TurnOn()
|
||||
{
|
||||
return _ipcClient.TurnOn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源高压
|
||||
/// </summary>
|
||||
public override XRayResult TurnOff()
|
||||
{
|
||||
return _ipcClient.TurnOff();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电压值
|
||||
/// </summary>
|
||||
public override XRayResult SetVoltage(float voltage)
|
||||
{
|
||||
return _ipcClient.SetVoltage(voltage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电流值
|
||||
/// </summary>
|
||||
public override XRayResult SetCurrent(float current)
|
||||
{
|
||||
return _ipcClient.SetCurrent(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置焦点 - Comet 225kV 不支持
|
||||
/// </summary>
|
||||
public override XRayResult SetFocus(float focus)
|
||||
{
|
||||
return _ipcClient.SetFocus(focus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前电压反馈值
|
||||
/// </summary>
|
||||
public override XRayResult ReadVoltage()
|
||||
{
|
||||
return _ipcClient.ReadVoltage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前电流反馈值
|
||||
/// </summary>
|
||||
public override XRayResult ReadCurrent()
|
||||
{
|
||||
return _ipcClient.ReadCurrent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统状态
|
||||
/// </summary>
|
||||
public override XRayResult ReadSystemStatus()
|
||||
{
|
||||
return _ipcClient.ReadSystemStatus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查错误状态
|
||||
/// </summary>
|
||||
public override XRayResult CheckErrors()
|
||||
{
|
||||
return _ipcClient.CheckErrors();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启
|
||||
/// </summary>
|
||||
public override XRayResult TxiOn()
|
||||
{
|
||||
return _ipcClient.TxiOn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭
|
||||
/// </summary>
|
||||
public override XRayResult TxiOff()
|
||||
{
|
||||
return _ipcClient.TxiOff();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置
|
||||
/// </summary>
|
||||
public override XRayResult WarmUp()
|
||||
{
|
||||
return _ipcClient.WarmUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置
|
||||
/// </summary>
|
||||
public override XRayResult Training()
|
||||
{
|
||||
return _ipcClient.Training();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准
|
||||
/// </summary>
|
||||
public override XRayResult FilamentCalibration()
|
||||
{
|
||||
return _ipcClient.FilamentCalibration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全部电压自动定心
|
||||
/// </summary>
|
||||
public override XRayResult AutoCenter()
|
||||
{
|
||||
return _ipcClient.AutoCenter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置功率模式
|
||||
/// </summary>
|
||||
public override XRayResult SetPowerMode(int mode)
|
||||
{
|
||||
return _ipcClient.SetPowerMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源,释放 IPC 连接并关闭 Host 进程
|
||||
/// </summary>
|
||||
public override XRayResult CloseOff()
|
||||
{
|
||||
_logger.Info("执行 CloseOff 操作(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 先通过 IPC 客户端发送断开命令
|
||||
var result = _ipcClient.CloseOff();
|
||||
|
||||
// 再关闭 Host 进程
|
||||
_hostManager.Shutdown();
|
||||
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
_logger.Info("CloseOff 操作完成");
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("CloseOff 过程中发生异常:{Message}", ex.Message);
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return XRayResult.Error($"CloseOff 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 资源释放
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源,清理 IPC 客户端和 Host 进程管理器
|
||||
/// </summary>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed && disposing)
|
||||
{
|
||||
_ipcClient?.Dispose();
|
||||
_hostManager?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Host 进程生命周期管理器
|
||||
/// 负责启动、监控和关闭 .NET Framework 4.8 Host 子进程
|
||||
/// </summary>
|
||||
public class CometHostManager : IDisposable
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly RaySourceConfig _config;
|
||||
private Process _hostProcess;
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Host 可执行文件名
|
||||
/// </summary>
|
||||
private const string HostExeName = "XP.Hardware.RaySource.Comet.Host.exe";
|
||||
|
||||
/// <summary>
|
||||
/// 命令管道名称(客户端写 → Host 读)
|
||||
/// </summary>
|
||||
private const string CmdPipeName = "XP.Hardware.RaySource.Comet.Cmd";
|
||||
|
||||
/// <summary>
|
||||
/// 响应管道名称(Host 写 → 客户端读)
|
||||
/// </summary>
|
||||
private const string RspPipeName = "XP.Hardware.RaySource.Comet.Rsp";
|
||||
|
||||
/// <summary>
|
||||
/// 等待管道就绪的超时时间(毫秒)
|
||||
/// </summary>
|
||||
private const int PipeReadyTimeoutMs = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// 等待 Host 进程退出的超时时间(毫秒)
|
||||
/// </summary>
|
||||
private const int ShutdownTimeoutMs = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="loggerService">日志服务</param>
|
||||
/// <param name="config">射线源配置</param>
|
||||
public CometHostManager(ILoggerService loggerService, RaySourceConfig config)
|
||||
{
|
||||
_logger = loggerService?.ForModule<CometHostManager>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程是否正在运行
|
||||
/// </summary>
|
||||
public bool IsRunning => _hostProcess != null && !_hostProcess.HasExited;
|
||||
|
||||
/// <summary>
|
||||
/// 确保 Host 进程正在运行
|
||||
/// 优先检查是否已有同路径的 Host 进程在运行,有则直接关联;否则启动新进程
|
||||
/// </summary>
|
||||
public void EnsureRunning()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
_logger.Debug("Host 进程已在运行,PID={Pid}", _hostProcess.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var hostExePath = GetHostExePath();
|
||||
|
||||
// 检查是否已有 Host 进程在运行(例如上次异常退出后残留的进程),杀掉后重新启动
|
||||
var hostProcessName = Path.GetFileNameWithoutExtension(HostExeName);
|
||||
var existingProcesses = Process.GetProcessesByName(hostProcessName);
|
||||
foreach (var proc in existingProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("发现残留的 Host 进程,正在终止,PID={Pid}", proc.Id);
|
||||
proc.Kill();
|
||||
proc.WaitForExit(1500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("终止残留 Host 进程失败,PID={Pid},{Message}", proc.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info("启动 Host 进程:{Path}", hostExePath);
|
||||
|
||||
if (!File.Exists(hostExePath))
|
||||
{
|
||||
throw new FileNotFoundException($"Host 可执行文件未找到:{hostExePath}", hostExePath);
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = hostExePath,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
_hostProcess = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
|
||||
_hostProcess.Exited += OnHostProcessExited;
|
||||
_hostProcess.ErrorDataReceived += OnHostErrorDataReceived;
|
||||
_hostProcess.Start();
|
||||
_hostProcess.BeginErrorReadLine();
|
||||
|
||||
_logger.Info("Host 进程已启动,PID={Pid}", _hostProcess.Id);
|
||||
|
||||
// 等待 NamedPipe 就绪
|
||||
WaitForPipeReady();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全关闭 Host 进程
|
||||
/// 等待进程退出,超时则强制终止(DisconnectCommand 已由 CometIpcClient 发送)
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
if (!IsRunning)
|
||||
{
|
||||
_logger.Debug("Host 进程未运行,无需关闭");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("正在关闭 Host 进程,PID={Pid}", _hostProcess.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// 等待进程退出(DisconnectCommand 已由 CometIpcClient 发送)
|
||||
if (!_hostProcess.WaitForExit(ShutdownTimeoutMs))
|
||||
{
|
||||
_logger.Warn("Host 进程未在超时时间内退出,强制终止");
|
||||
_hostProcess.Kill();
|
||||
_hostProcess.WaitForExit(1000);
|
||||
}
|
||||
|
||||
_logger.Info("Host 进程已关闭");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("关闭 Host 进程时发生异常:{Message}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CleanupProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Host 可执行文件路径
|
||||
/// 优先使用配置的路径,为空时默认在主程序输出目录下查找
|
||||
/// </summary>
|
||||
private string GetHostExePath()
|
||||
{
|
||||
// 优先使用配置的路径
|
||||
if (!string.IsNullOrWhiteSpace(_config.HostExePath))
|
||||
{
|
||||
return _config.HostExePath;
|
||||
}
|
||||
|
||||
// 默认在主程序输出目录的 Host 子目录下查找
|
||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
return Path.Combine(baseDir, "Host", HostExeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待 NamedPipe 就绪
|
||||
/// 通过短暂延迟和进程存活检查确认 Host 进程已启动并创建了管道
|
||||
/// 使用临时 NamedPipeClientStream 尝试连接来检测管道是否就绪
|
||||
/// </summary>
|
||||
private void WaitForPipeReady()
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
while ((DateTime.UtcNow - startTime).TotalMilliseconds < PipeReadyTimeoutMs)
|
||||
{
|
||||
if (_hostProcess.HasExited)
|
||||
{
|
||||
throw new InvalidOperationException($"Host 进程启动后立即退出,退出码={_hostProcess.ExitCode}");
|
||||
}
|
||||
|
||||
// 短暂等待,让 Host 进程有时间创建管道
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 进程仍在运行,认为管道即将就绪或已就绪
|
||||
if (!_hostProcess.HasExited)
|
||||
{
|
||||
_logger.Info("Host 进程正在运行,管道应已就绪");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 超时但进程仍在运行,假设管道已就绪
|
||||
if (!_hostProcess.HasExited)
|
||||
{
|
||||
_logger.Warn("等待管道就绪超时,但 Host 进程仍在运行,继续");
|
||||
return;
|
||||
}
|
||||
|
||||
throw new TimeoutException($"等待 Host 进程 NamedPipe 就绪超时({PipeReadyTimeoutMs}ms)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程退出事件处理
|
||||
/// </summary>
|
||||
private void OnHostProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
if (_hostProcess != null)
|
||||
{
|
||||
_logger.Warn("Host 进程已退出,退出码={ExitCode}", _hostProcess.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程 stderr 输出事件处理,转发到主进程日志
|
||||
/// </summary>
|
||||
private void OnHostErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
_logger.Debug("[Host stderr] {Message}", e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理进程资源
|
||||
/// </summary>
|
||||
private void CleanupProcess()
|
||||
{
|
||||
if (_hostProcess != null)
|
||||
{
|
||||
_hostProcess.Exited -= OnHostProcessExited;
|
||||
_hostProcess.ErrorDataReceived -= OnHostErrorDataReceived;
|
||||
_hostProcess.Dispose();
|
||||
_hostProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
Shutdown();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Telerik.Windows.Documents.Selection;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Comet.Messages;
|
||||
using XP.Hardware.RaySource.Comet.Messages.Commands;
|
||||
using XP.Hardware.RaySource.Comet.Messages.Responses;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// IPC 客户端
|
||||
/// 通过 NamedPipe 与 Host 进程通信,实现 IXRaySource 接口
|
||||
/// 上层代码无需感知进程隔离的存在
|
||||
/// </summary>
|
||||
public class CometIpcClient : XRaySourceBase
|
||||
{
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
private NamedPipeClientStream _cmdPipe;
|
||||
private NamedPipeClientStream _rspPipe;
|
||||
private StreamReader _reader;
|
||||
private Thread _receiveThread;
|
||||
private volatile bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 管道连接状态标志位
|
||||
/// 用于在 Host 进程崩溃或管道断开后,阻止后续 SendCommand 尝试写入已断开的管道
|
||||
/// </summary>
|
||||
private volatile bool _isPipeConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 用于等待命令响应的同步机制
|
||||
/// </summary>
|
||||
private TaskCompletionSource<RaySourceResponse> _pendingResponse;
|
||||
private readonly object _sendLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 命令管道名称(客户端写 → Host 读)
|
||||
/// </summary>
|
||||
private const string CmdPipeName = "XP.Hardware.RaySource.Comet.Cmd";
|
||||
|
||||
/// <summary>
|
||||
/// 响应管道名称(Host 写 → 客户端读)
|
||||
/// </summary>
|
||||
private const string RspPipeName = "XP.Hardware.RaySource.Comet.Rsp";
|
||||
|
||||
/// <summary>
|
||||
/// 命令超时时间(毫秒)
|
||||
/// 需要大于 Host 端 PVI 连接超时时间(30s),因为 Initialize 命令会在 Host 端阻塞等待 PVI 回调链完成
|
||||
/// </summary>
|
||||
private const int CommandTimeoutMs = 35000;
|
||||
|
||||
public override string SourceName => "Comet 225kV (IPC)";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public CometIpcClient(
|
||||
RaySourceConfig config,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = loggerService?.ForModule<CometIpcClient>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
#region 管道连接管理
|
||||
|
||||
/// <summary>
|
||||
/// 连接到 Host 进程的 NamedPipe
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 命令管道:客户端写 → Host 读
|
||||
_cmdPipe = new NamedPipeClientStream(".", CmdPipeName, PipeDirection.Out);
|
||||
_cmdPipe.Connect(_config.ConnectionTimeout);
|
||||
|
||||
// 响应管道:Host 写 → 客户端读
|
||||
_rspPipe = new NamedPipeClientStream(".", RspPipeName, PipeDirection.In);
|
||||
_rspPipe.Connect(_config.ConnectionTimeout);
|
||||
|
||||
_reader = new StreamReader(_rspPipe);
|
||||
|
||||
// 启动后台消息接收线程
|
||||
_isRunning = true;
|
||||
_receiveThread = new Thread(ReceiveLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "CometIpcClient-Receiver"
|
||||
};
|
||||
_receiveThread.Start();
|
||||
|
||||
_isPipeConnected = true;
|
||||
_logger.Info("已连接到 Host 进程 NamedPipe(双管道模式)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "连接 Host 进程 NamedPipe 失败:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接写入一行文本到命令管道
|
||||
/// </summary>
|
||||
private void WriteLineToHost(string text)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(text + "\n");
|
||||
_cmdPipe.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心通信方法
|
||||
|
||||
/// <summary>
|
||||
/// 发送命令并等待响应
|
||||
/// </summary>
|
||||
private RaySourceResponse SendCommand(RaySourceCommand command)
|
||||
{
|
||||
// 检查管道连接状态,如果已断开则直接返回 null
|
||||
if (!_isPipeConnected)
|
||||
{
|
||||
_logger.Error(null, "管道未连接,无法发送命令:{CommandType}", command.CommandType);
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_sendLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pendingResponse = new TaskCompletionSource<RaySourceResponse>();
|
||||
|
||||
var json = MessageSerializer.Serialize(command);
|
||||
_logger.Debug("发送命令:{CommandType},JSON 长度={Length}", command.CommandType, json.Length);
|
||||
WriteLineToHost(json);
|
||||
_logger.Debug("命令已写入管道,开始等待响应:{CommandType}", command.CommandType);
|
||||
|
||||
// 等待响应,带超时
|
||||
using (var cts = new CancellationTokenSource(CommandTimeoutMs))
|
||||
{
|
||||
cts.Token.Register(() => _pendingResponse?.TrySetCanceled());
|
||||
|
||||
try
|
||||
{
|
||||
_pendingResponse.Task.Wait();
|
||||
var result = _pendingResponse.Task.Result;
|
||||
_logger.Debug("收到命令响应:{CommandType},Success={Success}", command.CommandType, result?.Success);
|
||||
return result;
|
||||
}
|
||||
catch (AggregateException ae) when (ae.InnerException is TaskCanceledException)
|
||||
{
|
||||
_logger.Error(null, "命令超时({TimeoutMs}ms):{CommandType}", CommandTimeoutMs, command.CommandType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.Error(ex, "管道通信异常(Host 进程可能已崩溃):{Message}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "发送命令异常:{Message}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 后台消息接收线程
|
||||
|
||||
/// <summary>
|
||||
/// 后台消息接收循环
|
||||
/// 持续读取管道消息,区分命令响应和主动推送
|
||||
/// </summary>
|
||||
private void ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Debug("接收线程已启动");
|
||||
while (_isRunning)
|
||||
{
|
||||
var line = _reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
// 管道断开,设置标志位阻止后续 SendCommand 写入
|
||||
_isPipeConnected = false;
|
||||
_logger.Warn("Host 进程管道已断开");
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.Debug("收到管道消息,长度={Length}", line.Length);
|
||||
|
||||
var response = MessageSerializer.DeserializeResponse(line);
|
||||
if (response == null)
|
||||
{
|
||||
_logger.Warn("收到无法反序列化的消息,原始内容:{Raw}", line.Length > 200 ? line.Substring(0, 200) + "..." : line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.IsPush)
|
||||
{
|
||||
_logger.Debug("收到推送消息:{PushType}", response.PushType);
|
||||
// 主动推送消息,路由到对应的 Prism 事件
|
||||
HandlePushMessage(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("收到命令响应,Success={Success}", response.Success);
|
||||
// 命令响应,通知等待的 SendCommand 调用
|
||||
_pendingResponse?.TrySetResult(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_isPipeConnected = false;
|
||||
_logger.Warn("接收线程 IO 异常(管道可能已断开):{Message}", ex.Message);
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 管道已释放,正常退出
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "接收线程异常:{Message}", ex.Message);
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 推送消息路由
|
||||
|
||||
/// <summary>
|
||||
/// 处理推送消息,根据 PushType 路由到对应的 Prism 事件
|
||||
/// </summary>
|
||||
private void HandlePushMessage(RaySourceResponse response)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (response.PushType)
|
||||
{
|
||||
case "StatusChanged":
|
||||
HandleStatusPush(response as StatusResponse);
|
||||
break;
|
||||
case "XRayStateChanged":
|
||||
HandleXRayStatePush(response as OperationResponse);
|
||||
break;
|
||||
case "ErrorOccurred":
|
||||
HandleErrorPush(response);
|
||||
break;
|
||||
case "ConnectionStateChanged":
|
||||
HandleConnectionStatePush(response as OperationResponse);
|
||||
break;
|
||||
case "Log":
|
||||
HandleLogPush(response as LogResponse);
|
||||
break;
|
||||
default:
|
||||
_logger.Warn("收到未知推送类型:{PushType}", response.PushType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "处理推送消息异常:{Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStatusPush(StatusResponse status)
|
||||
{
|
||||
if (status == null) return;
|
||||
|
||||
var statusData = new SystemStatusData
|
||||
{
|
||||
SetVoltage = status.SetVoltage,
|
||||
ActualVoltage = status.ActualVoltage,
|
||||
SetCurrent = status.SetCurrent,
|
||||
ActualCurrent = status.ActualCurrent,
|
||||
IsXRayOn = status.IsXRayOn,
|
||||
WarmUpStatus = status.WarmUpStatus,
|
||||
VacuumStatus = status.VacuumStatus,
|
||||
StartUpStatus = status.StartUpStatus,
|
||||
AutoCenterStatus = status.AutoCenterStatus,
|
||||
FilamentAdjustStatus = status.FilamentAdjustStatus,
|
||||
IsInterlockActive = status.IsInterlockActive,
|
||||
WatchdogStatus = status.WatchdogStatus,
|
||||
PowerMode = status.PowerMode,
|
||||
TxiStatus = status.TxiStatus
|
||||
};
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Publish(statusData);
|
||||
}
|
||||
|
||||
private void HandleXRayStatePush(OperationResponse response)
|
||||
{
|
||||
if (response?.Data == null) return;
|
||||
|
||||
var isXRayOn = Convert.ToBoolean(response.Data);
|
||||
var rayStatus = isXRayOn ? RaySourceStatus.Opened : RaySourceStatus.Closed;
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(rayStatus);
|
||||
}
|
||||
|
||||
private void HandleErrorPush(RaySourceResponse response)
|
||||
{
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(response.ErrorMessage);
|
||||
}
|
||||
|
||||
private void HandleConnectionStatePush(OperationResponse response)
|
||||
{
|
||||
if (response?.Data == null) return;
|
||||
|
||||
var stateStr = response.Data.ToString();
|
||||
if (stateStr == "RaySourceConnected")
|
||||
{
|
||||
_isConnected = true;
|
||||
_logger.Info("收到 RaySourceConnected 推送,射线源已完成全部连接流程,准备就绪");
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Publish(true);
|
||||
_logger.Info("射线源连接成功: {SourceName},状态更新为 Closed | X-ray source connectzed successfully: {SourceName}, status updated to Closed", SourceName);
|
||||
// 通知状态变更 | Notify status changed
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(RaySourceStatus.Closed);
|
||||
}
|
||||
else if (stateStr == "VariablesConnected")
|
||||
{
|
||||
_isInitialized = true;
|
||||
_logger.Info("收到 VariablesConnected 推送,PVI 变量已创建、激活并绑定");
|
||||
}
|
||||
else if (stateStr == "ServiceConnected")
|
||||
{
|
||||
_serviceConnectedEvent.Set();
|
||||
_logger.Info("收到 ServiceConnected 推送,PVI Service 和 CPU 已连接");
|
||||
}
|
||||
else if (stateStr == "Disconnected")
|
||||
{
|
||||
_isConnected = false;
|
||||
_isInitialized = false;
|
||||
_logger.Info("收到 Disconnected 推送,PVI 未连接或已断开");
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Publish(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 日志传递
|
||||
|
||||
/// <summary>
|
||||
/// 处理日志推送消息
|
||||
/// 根据 Level 字段映射到 ILoggerService 对应方法
|
||||
/// </summary>
|
||||
private void HandleLogPush(LogResponse logResponse)
|
||||
{
|
||||
if (logResponse == null) return;
|
||||
|
||||
var message = logResponse.Message ?? "";
|
||||
// 将 string[] 转换为 object[] 以匹配 ILoggerService 的 params object[] 签名
|
||||
var args = logResponse.Args != null ? (object[])logResponse.Args : Array.Empty<object>();
|
||||
|
||||
switch (logResponse.Level)
|
||||
{
|
||||
case "Debug":
|
||||
_logger.Debug(message, args);
|
||||
break;
|
||||
case "Info":
|
||||
_logger.Info(message, args);
|
||||
break;
|
||||
case "Warn":
|
||||
_logger.Warn(message, args);
|
||||
break;
|
||||
case "Error":
|
||||
_logger.Error(null, message, args);
|
||||
break;
|
||||
case "Fatal":
|
||||
_logger.Fatal(null, message, args);
|
||||
break;
|
||||
default:
|
||||
_logger.Debug(message, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IXRaySource 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 等待 ServiceConnected 推送的信号量
|
||||
/// Host 端 HandleInitialize 会等待 PVI 回调链完成后才返回响应
|
||||
/// 但作为双重保险,客户端也等待 ServiceConnected 推送到达
|
||||
/// </summary>
|
||||
private readonly ManualResetEventSlim _serviceConnectedEvent = new ManualResetEventSlim(false);
|
||||
|
||||
/// <summary>
|
||||
/// 等待 ServiceConnected 推送的超时时间(毫秒)
|
||||
/// 应略大于 Host 端的 PVI 连接超时时间,因为还有管道通信延迟
|
||||
/// </summary>
|
||||
private const int ServiceConnectedTimeoutMs = 35000;
|
||||
|
||||
public override XRayResult Initialize()
|
||||
{
|
||||
_logger.Info("初始化 Comet225 射线源(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 重置等待信号
|
||||
_serviceConnectedEvent.Reset();
|
||||
|
||||
// 连接管道
|
||||
Connect();
|
||||
|
||||
var command = new InitializeCommand
|
||||
{
|
||||
IpAddress = _config.PlcIpAddress,
|
||||
Port = _config.PlcPort,
|
||||
CpuName = _config.CpuName,
|
||||
SourcePort = _config.PortNumber,
|
||||
StationNumber = _config.StationNumber
|
||||
};
|
||||
|
||||
// Host 端 HandleInitialize 会等待 PVI 回调链完成后才返回响应
|
||||
var response = SendCommand(command);
|
||||
var result = ProcessResponse(response, "Initialize");
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 双重保险:等待 ServiceConnected 推送到达
|
||||
// Host 返回成功意味着 PVI 回调链已完成,推送应该很快到达
|
||||
if (!_serviceConnectedEvent.Wait(ServiceConnectedTimeoutMs))
|
||||
{
|
||||
_logger.Warn("Initialize 命令成功但未收到 ServiceConnected 推送,可能推送丢失");
|
||||
// Host 已确认成功,即使推送未到达也设置初始化标志
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Initialize 异常:{Message}", ex.Message);
|
||||
return XRayResult.Error($"Initialize 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override XRayResult ConnectVariables()
|
||||
{
|
||||
var response = SendCommand(new ConnectVariablesCommand());
|
||||
return ProcessResponse(response, "ConnectVariables");
|
||||
}
|
||||
|
||||
public override XRayResult TurnOn()
|
||||
{
|
||||
var response = SendCommand(new TurnOnCommand());
|
||||
return ProcessResponse(response, "TurnOn");
|
||||
}
|
||||
|
||||
public override XRayResult TurnOff()
|
||||
{
|
||||
var response = SendCommand(new TurnOffCommand());
|
||||
return ProcessResponse(response, "TurnOff");
|
||||
}
|
||||
|
||||
public override XRayResult SetVoltage(float voltage)
|
||||
{
|
||||
var response = SendCommand(new SetVoltageCommand { Voltage = voltage });
|
||||
return ProcessResponse(response, "SetVoltage");
|
||||
}
|
||||
|
||||
public override XRayResult SetCurrent(float current)
|
||||
{
|
||||
var response = SendCommand(new SetCurrentCommand { Current = current });
|
||||
return ProcessResponse(response, "SetCurrent");
|
||||
}
|
||||
|
||||
public override XRayResult SetFocus(float focus)
|
||||
{
|
||||
_logger.Info("SetFocus 被调用,Comet 225kV 射线源不支持焦点设置");
|
||||
return XRayResult.Ok("Comet 225kV 射线源不支持焦点设置");
|
||||
}
|
||||
|
||||
public override XRayResult ReadVoltage()
|
||||
{
|
||||
var response = SendCommand(new ReadVoltageCommand());
|
||||
if (response is OperationResponse opResp && opResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(opResp.Data);
|
||||
}
|
||||
return ProcessResponse(response, "ReadVoltage");
|
||||
}
|
||||
|
||||
public override XRayResult ReadCurrent()
|
||||
{
|
||||
var response = SendCommand(new ReadCurrentCommand());
|
||||
if (response is OperationResponse opResp && opResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(opResp.Data);
|
||||
}
|
||||
return ProcessResponse(response, "ReadCurrent");
|
||||
}
|
||||
|
||||
public override XRayResult ReadSystemStatus()
|
||||
{
|
||||
var response = SendCommand(new ReadSystemStatusCommand());
|
||||
if (response is StatusResponse statusResp && statusResp.Success)
|
||||
{
|
||||
var statusData = new SystemStatusData
|
||||
{
|
||||
SetVoltage = statusResp.SetVoltage,
|
||||
ActualVoltage = statusResp.ActualVoltage,
|
||||
SetCurrent = statusResp.SetCurrent,
|
||||
ActualCurrent = statusResp.ActualCurrent,
|
||||
IsXRayOn = statusResp.IsXRayOn,
|
||||
WarmUpStatus = statusResp.WarmUpStatus,
|
||||
VacuumStatus = statusResp.VacuumStatus,
|
||||
StartUpStatus = statusResp.StartUpStatus,
|
||||
AutoCenterStatus = statusResp.AutoCenterStatus,
|
||||
FilamentAdjustStatus = statusResp.FilamentAdjustStatus,
|
||||
IsInterlockActive = statusResp.IsInterlockActive,
|
||||
WatchdogStatus = statusResp.WatchdogStatus,
|
||||
PowerMode = statusResp.PowerMode,
|
||||
TxiStatus = statusResp.TxiStatus
|
||||
};
|
||||
return XRayResult.Ok(statusData);
|
||||
}
|
||||
return ProcessResponse(response, "ReadSystemStatus");
|
||||
}
|
||||
|
||||
public override XRayResult CheckErrors()
|
||||
{
|
||||
var response = SendCommand(new ReadErrorsCommand());
|
||||
if (response is ErrorDataResponse errorResp && errorResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(errorResp);
|
||||
}
|
||||
return ProcessResponse(response, "CheckErrors");
|
||||
}
|
||||
|
||||
public override XRayResult TxiOn()
|
||||
{
|
||||
var response = SendCommand(new TxiOnCommand());
|
||||
return ProcessResponse(response, "TxiOn");
|
||||
}
|
||||
|
||||
public override XRayResult TxiOff()
|
||||
{
|
||||
var response = SendCommand(new TxiOffCommand());
|
||||
return ProcessResponse(response, "TxiOff");
|
||||
}
|
||||
|
||||
public override XRayResult WarmUp()
|
||||
{
|
||||
var response = SendCommand(new WarmUpCommand());
|
||||
return ProcessResponse(response, "WarmUp");
|
||||
}
|
||||
|
||||
public override XRayResult Training()
|
||||
{
|
||||
var response = SendCommand(new TrainingCommand());
|
||||
return ProcessResponse(response, "Training");
|
||||
}
|
||||
|
||||
public override XRayResult FilamentCalibration()
|
||||
{
|
||||
var response = SendCommand(new FilamentCalibrationCommand());
|
||||
return ProcessResponse(response, "FilamentCalibration");
|
||||
}
|
||||
|
||||
public override XRayResult AutoCenter()
|
||||
{
|
||||
var response = SendCommand(new AutoCenterCommand());
|
||||
return ProcessResponse(response, "AutoCenter");
|
||||
}
|
||||
|
||||
public override XRayResult SetPowerMode(int mode)
|
||||
{
|
||||
var response = SendCommand(new SetPowerModeCommand { Mode = mode });
|
||||
return ProcessResponse(response, "SetPowerMode");
|
||||
}
|
||||
|
||||
public override XRayResult CloseOff()
|
||||
{
|
||||
_logger.Info("执行 CloseOff 操作(IPC 模式)");
|
||||
try
|
||||
{
|
||||
var response = SendCommand(new DisconnectCommand());
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return ProcessResponse(response, "CloseOff");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("CloseOff 过程中发生异常:{Message}", ex.Message);
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return XRayResult.Error($"CloseOff 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 处理响应,将 Host 返回的响应转换为 XRayResult
|
||||
/// </summary>
|
||||
private XRayResult ProcessResponse(RaySourceResponse response, string operationName)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
var errorMsg = $"{operationName} 操作失败:未收到 Host 响应(管道可能已断开或超时)";
|
||||
_logger.Error(null, errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
_logger.Error(null, "{Operation} 操作失败:{ErrorMessage}", operationName, response.ErrorMessage);
|
||||
return XRayResult.Error(response.ErrorMessage);
|
||||
}
|
||||
|
||||
return XRayResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 资源释放
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed && disposing)
|
||||
{
|
||||
_isRunning = false;
|
||||
_isPipeConnected = false;
|
||||
|
||||
_serviceConnectedEvent?.Dispose();
|
||||
_reader?.Dispose();
|
||||
_cmdPipe?.Dispose();
|
||||
_rspPipe?.Dispose();
|
||||
|
||||
_reader = null;
|
||||
_cmdPipe = null;
|
||||
_rspPipe = null;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user