将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,995 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using XP.Hardware.RaySource.Comet;
|
||||
using XP.Hardware.RaySource.Comet.Host.Pipe;
|
||||
using XP.Hardware.RaySource.Comet.Host.Properties;
|
||||
using XP.Hardware.RaySource.Comet.Messages;
|
||||
using XP.Hardware.RaySource.Comet.Messages.Responses;
|
||||
|
||||
namespace XP.Hardware.RaySource.Comet.Host
|
||||
{
|
||||
/// <summary>
|
||||
/// XplorePlane Comet Host 主窗体
|
||||
/// 集成托盘图标、管道通信、PVI 客户端生命周期和功能测试按钮
|
||||
/// </summary>
|
||||
public partial class XplorePlaneCometHost : Form
|
||||
{
|
||||
#region 管道通信常量和字段
|
||||
|
||||
/// <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 static readonly object WriteLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 管道是否已连接,用于控制日志是否推送到管道
|
||||
/// </summary>
|
||||
private volatile bool _isPipeConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 管道轮询定时器
|
||||
/// </summary>
|
||||
private System.Windows.Forms.Timer _pipeTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 命令管道服务端
|
||||
/// </summary>
|
||||
private NamedPipeServerStream _cmdPipe;
|
||||
|
||||
/// <summary>
|
||||
/// 响应管道服务端
|
||||
/// </summary>
|
||||
private NamedPipeServerStream _rspPipe;
|
||||
|
||||
/// <summary>
|
||||
/// 管道读取器
|
||||
/// </summary>
|
||||
private StreamReader _pipeReader;
|
||||
|
||||
/// <summary>
|
||||
/// 管道写入器
|
||||
/// </summary>
|
||||
private StreamWriter _pipeWriter;
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在等待客户端连接
|
||||
/// </summary>
|
||||
private bool _isWaitingConnection;
|
||||
|
||||
/// <summary>
|
||||
/// 异步连接结果(命令管道)
|
||||
/// </summary>
|
||||
private IAsyncResult _cmdConnectResult;
|
||||
|
||||
/// <summary>
|
||||
/// 异步连接结果(响应管道)
|
||||
/// </summary>
|
||||
private IAsyncResult _rspConnectResult;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 PeekNamedPipe API,用于非阻塞检测管道中是否有可读数据
|
||||
/// StreamReader.Peek() 在异步模式 NamedPipe 上不可靠:
|
||||
/// 当内部缓冲区为空时,Peek 调用底层同步 Read,异步管道会返回 0 字节,
|
||||
/// 导致 StreamReader 误判为 EOF,后续所有读取永远返回 -1/null
|
||||
/// </summary>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool PeekNamedPipe(
|
||||
SafePipeHandle hNamedPipe,
|
||||
IntPtr lpBuffer,
|
||||
uint nBufferSize,
|
||||
IntPtr lpBytesRead,
|
||||
out uint lpTotalBytesAvail,
|
||||
IntPtr lpBytesLeftThisMessage);
|
||||
|
||||
/// <summary>
|
||||
/// 检查命令管道中是否有可读数据(非阻塞)
|
||||
/// </summary>
|
||||
private bool HasPipeData()
|
||||
{
|
||||
if (_cmdPipe == null || !_cmdPipe.IsConnected)
|
||||
return false;
|
||||
|
||||
bool result = PeekNamedPipe(
|
||||
_cmdPipe.SafePipeHandle,
|
||||
IntPtr.Zero, 0, IntPtr.Zero,
|
||||
out uint bytesAvailable,
|
||||
IntPtr.Zero);
|
||||
|
||||
return result && bytesAvailable > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Comet Timer常量
|
||||
/// <summary>
|
||||
/// 连接状态定时器
|
||||
/// </summary>
|
||||
private System.Windows.Forms.Timer _pviConnectTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 看门狗定时器
|
||||
/// </summary>
|
||||
private System.Windows.Forms.Timer _watchDogTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳闪烁状态(true=绿色,false=灰色)
|
||||
/// </summary>
|
||||
private bool _heartbeatToggle;
|
||||
|
||||
#endregion
|
||||
|
||||
public XplorePlaneCometHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeTrayIcon();
|
||||
SetupLogger();
|
||||
SubscribeLocalEvents();
|
||||
StartPipeLoop();
|
||||
StartCometPviTimer();
|
||||
}
|
||||
|
||||
#region 托盘图标
|
||||
|
||||
/// <summary>
|
||||
/// 初始化系统托盘图标和右键菜单
|
||||
/// </summary>
|
||||
private void InitializeTrayIcon()
|
||||
{
|
||||
var contextMenu = new ContextMenuStrip();
|
||||
contextMenu.Items.Add("XplorePlane Comet Host", null, null).Enabled = false;
|
||||
contextMenu.Items.Add(new ToolStripSeparator());
|
||||
contextMenu.Items.Add("测试窗口|Test(&S)", null, (s, e) =>
|
||||
{
|
||||
this.ShowInTaskbar = true;
|
||||
this.Show();
|
||||
this.WindowState = FormWindowState.Normal;
|
||||
this.Activate();
|
||||
});
|
||||
contextMenu.Items.Add("退出|Exit(&X)", null, OnExitClick);
|
||||
|
||||
_notifyIcon = new NotifyIcon
|
||||
{
|
||||
Icon = Resource.Comet_Host_Icon,
|
||||
Text = "XP.Hardware.RaySource.Comet.Host",
|
||||
Visible = true,
|
||||
ContextMenuStrip = contextMenu
|
||||
};
|
||||
_notifyIcon.DoubleClick += (s, e) =>
|
||||
{
|
||||
this.ShowInTaskbar = true;
|
||||
this.Show();
|
||||
this.WindowState = FormWindowState.Normal;
|
||||
this.Activate();
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 统一日志
|
||||
|
||||
/// <summary>
|
||||
/// 设置统一日志回调
|
||||
/// 界面日志始终输出,管道推送仅在管道连接后生效
|
||||
/// </summary>
|
||||
private void SetupLogger()
|
||||
{
|
||||
CometPviClient.SetLogger((level, message, logArgs) =>
|
||||
{
|
||||
// 始终输出到界面
|
||||
string formatted = logArgs != null && logArgs.Length > 0
|
||||
? FormatLogMessage(message, logArgs)
|
||||
: message;
|
||||
AppendLog($"[{level}] {formatted}");
|
||||
|
||||
// 管道已连接时,同步推送到主进程
|
||||
if (_isPipeConnected)
|
||||
{
|
||||
var logResponse = new LogResponse
|
||||
{
|
||||
Success = true,
|
||||
PushType = "Log",
|
||||
Level = level.ToString(),
|
||||
Message = message,
|
||||
Args = logArgs != null ? Array.ConvertAll(logArgs, a => a?.ToString() ?? "") : null
|
||||
};
|
||||
PushNotifier.SendPush(logResponse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 管道通信
|
||||
|
||||
/// <summary>
|
||||
/// 启动管道通信:创建管道并开始异步等待连接,启动轮询定时器
|
||||
/// </summary>
|
||||
private void StartPipeLoop()
|
||||
{
|
||||
// 创建管道
|
||||
_cmdPipe = new NamedPipeServerStream(CmdPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
_rspPipe = new NamedPipeServerStream(RspPipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
|
||||
// 异步等待客户端连接
|
||||
_cmdConnectResult = _cmdPipe.BeginWaitForConnection(null, null);
|
||||
_rspConnectResult = _rspPipe.BeginWaitForConnection(null, null);
|
||||
_isWaitingConnection = true;
|
||||
|
||||
AppendLog("[管道] 等待客户端连接...");
|
||||
|
||||
// 启动定时器轮询(50ms 间隔)
|
||||
_pipeTimer = new System.Windows.Forms.Timer();
|
||||
_pipeTimer.Interval = 50;
|
||||
_pipeTimer.Tick += PipeTimer_Tick;
|
||||
_pipeTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在处理管道命令,防止 DoEvents 期间 Timer 重入
|
||||
/// </summary>
|
||||
private bool _isProcessingCommand;
|
||||
|
||||
/// <summary>
|
||||
/// 定时器回调:检查管道连接状态和读取命令
|
||||
/// </summary>
|
||||
private void PipeTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// 防止 HandleInitialize 中 DoEvents 导致的 Timer 重入
|
||||
if (_isProcessingCommand)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 阶段1:等待客户端连接
|
||||
if (_isWaitingConnection)
|
||||
{
|
||||
if (_cmdConnectResult.IsCompleted && _rspConnectResult.IsCompleted)
|
||||
{
|
||||
_cmdPipe.EndWaitForConnection(_cmdConnectResult);
|
||||
_rspPipe.EndWaitForConnection(_rspConnectResult);
|
||||
|
||||
_pipeReader = new StreamReader(_cmdPipe);
|
||||
_pipeWriter = new StreamWriter(_rspPipe) { AutoFlush = true };
|
||||
|
||||
// 初始化推送管理器
|
||||
PushNotifier.Initialize(_pipeWriter, WriteLock);
|
||||
|
||||
// 订阅 PVI 推送事件
|
||||
SubscribePviPushEvents();
|
||||
|
||||
_isPipeConnected = true;
|
||||
_isWaitingConnection = false;
|
||||
|
||||
AppendLog("[管道] 客户端已连接");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 阶段2:轮询读取命令
|
||||
if (!_cmdPipe.IsConnected)
|
||||
{
|
||||
AppendLog("[管道] 客户端已断开");
|
||||
CleanupPipe();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查管道中是否有可读数据(非阻塞)
|
||||
// 使用 Win32 PeekNamedPipe 替代 StreamReader.Peek()
|
||||
// StreamReader.Peek() 在异步模式 NamedPipe 上会误判 EOF,导致后续命令永远无法读取
|
||||
if (!HasPipeData())
|
||||
return;
|
||||
|
||||
_isProcessingCommand = true;
|
||||
try
|
||||
{
|
||||
string line = _pipeReader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
AppendLog("[管道] 客户端已关闭连接");
|
||||
CleanupPipe();
|
||||
return;
|
||||
}
|
||||
|
||||
AppendLog($"[管道] 收到命令:{line}", Color.Cyan);
|
||||
|
||||
var command = MessageSerializer.DeserializeCommand(line);
|
||||
if (command == null)
|
||||
{
|
||||
var errorResponse = new OperationResponse
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "命令反序列化失败"
|
||||
};
|
||||
WriteResponse(_pipeWriter, errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
var response = CommandDispatcher.Dispatch(command);
|
||||
WriteResponse(_pipeWriter, response);
|
||||
|
||||
if (CommandDispatcher.ShouldExit)
|
||||
{
|
||||
AppendLog("[管道] 收到断开命令,管道循环退出");
|
||||
CleanupPipe();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessingCommand = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[管道] 通信异常:{ex.Message}", Color.Red);
|
||||
CleanupPipe();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理管道资源
|
||||
/// </summary>
|
||||
private void CleanupPipe()
|
||||
{
|
||||
_pipeTimer?.Stop();
|
||||
_isPipeConnected = false;
|
||||
|
||||
try { _pipeReader?.Dispose(); } catch { }
|
||||
try { _pipeWriter?.Dispose(); } catch { }
|
||||
try { _cmdPipe?.Dispose(); } catch { }
|
||||
try { _rspPipe?.Dispose(); } catch { }
|
||||
|
||||
_pipeReader = null;
|
||||
_pipeWriter = null;
|
||||
_cmdPipe = null;
|
||||
_rspPipe = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全地写入响应到管道
|
||||
/// </summary>
|
||||
private static void WriteResponse(StreamWriter writer, RaySourceResponse response)
|
||||
{
|
||||
var json = MessageSerializer.Serialize(response);
|
||||
lock (WriteLock)
|
||||
{
|
||||
writer.WriteLine(json);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PVI 管道推送事件
|
||||
|
||||
/// <summary>
|
||||
/// 订阅 CometPviClient 事件,通过 PushNotifier 推送给主进程
|
||||
/// </summary>
|
||||
private void SubscribePviPushEvents()
|
||||
{
|
||||
CometPviClient.StatusChanged += (sender, status) =>
|
||||
{
|
||||
PushNotifier.SendPush(new StatusResponse
|
||||
{
|
||||
Success = true,
|
||||
PushType = "StatusChanged",
|
||||
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
|
||||
});
|
||||
};
|
||||
|
||||
CometPviClient.XRayStateChanged += (sender, isXRayOn) =>
|
||||
{
|
||||
PushNotifier.SendPush(new OperationResponse
|
||||
{
|
||||
Success = true,
|
||||
PushType = "XRayStateChanged",
|
||||
Data = isXRayOn
|
||||
});
|
||||
};
|
||||
|
||||
CometPviClient.ErrorOccurred += (sender, errorMessage) =>
|
||||
{
|
||||
PushNotifier.SendPush(new OperationResponse
|
||||
{
|
||||
Success = false,
|
||||
PushType = "ErrorOccurred",
|
||||
ErrorMessage = errorMessage
|
||||
});
|
||||
};
|
||||
|
||||
CometPviClient.ConnectionStateChanged += (sender, state) =>
|
||||
{
|
||||
PushNotifier.SendPush(new OperationResponse
|
||||
{
|
||||
Success = true,
|
||||
PushType = "ConnectionStateChanged",
|
||||
Data = state.ToString()
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Comet Timer事件
|
||||
|
||||
private void StartCometPviTimer()
|
||||
{
|
||||
// 启动连接状态定时器轮询(100ms 间隔)
|
||||
_pviConnectTimer = new System.Windows.Forms.Timer();
|
||||
_pviConnectTimer.Interval = 100;
|
||||
_pviConnectTimer.Tick += PviConnectTimer_Tick;
|
||||
_pviConnectTimer.Start();
|
||||
|
||||
// 启动看门狗定时器轮询(1000ms 间隔)
|
||||
_watchDogTimer = new System.Windows.Forms.Timer();
|
||||
_watchDogTimer.Interval = 1000;
|
||||
_watchDogTimer.Tick += WatchDogTimer_Tick;
|
||||
_watchDogTimer.Start();
|
||||
}
|
||||
|
||||
private void PviConnectTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool bRaySourceConnected = CometPviClient.ReadRaySourceConnectStatus();
|
||||
// 根据当前连接状态更新标签
|
||||
var state = CometPviClient.CurrentConnectionState;
|
||||
switch (state)
|
||||
{
|
||||
case PviConnectionState.RaySourceConnected:
|
||||
_lblPviState.Text = "射线源就绪";
|
||||
_lblPviState.ForeColor = Color.Green;
|
||||
break;
|
||||
case PviConnectionState.VariablesConnected:
|
||||
_lblPviState.Text = "变量已连接";
|
||||
_lblPviState.ForeColor = Color.DarkGoldenrod;
|
||||
break;
|
||||
case PviConnectionState.ServiceConnected:
|
||||
_lblPviState.Text = "Service已连接";
|
||||
_lblPviState.ForeColor = Color.Orange;
|
||||
break;
|
||||
default:
|
||||
_lblPviState.Text = "未连接";
|
||||
_lblPviState.ForeColor = Color.Red;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 连接状态定时器出错失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void WatchDogTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
CometPviClient.SetWatchDog();
|
||||
// 心跳闪烁:绿色/灰色交替
|
||||
_heartbeatToggle = !_heartbeatToggle;
|
||||
_lblHeartbeat.ForeColor = _heartbeatToggle ? Color.LimeGreen : Color.Gray;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_lblHeartbeat.ForeColor = Color.Red;
|
||||
AppendLog($"[错误] 看门狗定时器出错失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 本地界面事件订阅
|
||||
|
||||
/// <summary>
|
||||
/// 订阅 CometPviClient 事件,将状态输出到界面
|
||||
/// </summary>
|
||||
private void SubscribeLocalEvents()
|
||||
{
|
||||
// 订阅连接状态变更
|
||||
CometPviClient.ConnectionStateChanged += (sender, state) =>
|
||||
{
|
||||
AppendLog($"[连接状态] {state}");
|
||||
SafeInvoke(() =>
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PviConnectionState.RaySourceConnected:
|
||||
_lblConnectionState.Text = "状态: 射线源已连接";
|
||||
_lblConnectionState.ForeColor = Color.Green;
|
||||
_btnConnectVariables.Enabled = false;
|
||||
_btnDisconnect.Enabled = true;
|
||||
_btnInitialize.Enabled = false;
|
||||
break;
|
||||
case PviConnectionState.ServiceConnected:
|
||||
_lblConnectionState.Text = "状态: PVI Service 已连接";
|
||||
_lblConnectionState.ForeColor = Color.Orange;
|
||||
_btnConnectVariables.Enabled = true;
|
||||
_btnDisconnect.Enabled = true;
|
||||
_btnInitialize.Enabled = false;
|
||||
break;
|
||||
case PviConnectionState.VariablesConnected:
|
||||
_lblConnectionState.Text = "状态: 变量已连接";
|
||||
_lblConnectionState.ForeColor = Color.DarkGoldenrod;
|
||||
SetOperationButtonsEnabled(true);
|
||||
_btnConnectVariables.Enabled = false;
|
||||
break;
|
||||
case PviConnectionState.Disconnected:
|
||||
_lblConnectionState.Text = "状态: 已断开";
|
||||
_lblConnectionState.ForeColor = Color.Red;
|
||||
SetOperationButtonsEnabled(false);
|
||||
_btnConnectVariables.Enabled = false;
|
||||
_btnDisconnect.Enabled = false;
|
||||
_btnInitialize.Enabled = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 订阅状态更新
|
||||
CometPviClient.StatusChanged += (sender, status) =>
|
||||
{
|
||||
AppendLog($"[状态更新] 电压={status.ActualVoltage}kV, 电流={status.ActualCurrent}μA, XRay={status.IsXRayOn}");
|
||||
};
|
||||
|
||||
// 订阅射线状态变更
|
||||
CometPviClient.XRayStateChanged += (sender, isOn) =>
|
||||
{
|
||||
AppendLog($"[射线状态] XRay {(isOn ? "已开启" : "已关闭")}");
|
||||
};
|
||||
|
||||
// 订阅错误事件
|
||||
CometPviClient.ErrorOccurred += (sender, errorMsg) =>
|
||||
{
|
||||
AppendLog($"[错误] {errorMsg}", Color.Red);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化日志消息,替换占位符
|
||||
/// </summary>
|
||||
private string FormatLogMessage(string message, object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = message;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
result = result.Replace("{" + i + "}", args[i]?.ToString() ?? "null");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 按钮事件处理
|
||||
|
||||
private void OnInitialize(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_btnInitialize.Enabled = false;
|
||||
AppendLog("[操作] 开始初始化 PVI 连接...");
|
||||
CometPviClient.Initialize(
|
||||
_txtIpAddress.Text,
|
||||
(int)_nudPort.Value,
|
||||
_txtCpuName.Text,
|
||||
(int)_nudSourcePort.Value,
|
||||
(int)_nudStationNumber.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 初始化失败:{ex.Message}", Color.Red);
|
||||
_btnInitialize.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectVariables(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[操作] 开始连接 PVI 变量...");
|
||||
CometPviClient.ConnectVariables();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 连接变量失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisconnect(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[操作] 断开连接...");
|
||||
CometPviClient.Disconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 断开连接失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTurnOn(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[操作] 开启高压...");
|
||||
CometPviClient.TurnOn();
|
||||
AppendLog("[操作] TurnOn 命令已发送");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] TurnOn 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTurnOff(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[操作] 关闭高压...");
|
||||
CometPviClient.TurnOff();
|
||||
AppendLog("[操作] TurnOff 命令已发送");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] TurnOff 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetVoltage(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
float voltage = (float)_nudVoltage.Value;
|
||||
AppendLog($"[操作] 设置电压:{voltage} kV");
|
||||
CometPviClient.SetVoltage(voltage);
|
||||
AppendLog($"[操作] SetVoltage 命令已发送,电压={voltage}kV");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] SetVoltage 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetCurrent(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
float current = (float)_nudCurrent.Value;
|
||||
AppendLog($"[操作] 设置电流:{current} μA");
|
||||
CometPviClient.SetCurrent(current);
|
||||
AppendLog($"[操作] SetCurrent 命令已发送,电流={current}μA");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] SetCurrent 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReadVoltage(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
float voltage = CometPviClient.ReadVoltage();
|
||||
AppendLog($"[读取] 实际电压:{voltage} kV");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] ReadVoltage 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReadCurrent(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
float current = CometPviClient.ReadCurrent();
|
||||
AppendLog($"[读取] 实际电流:{current} μA");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] ReadCurrent 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReadSystemStatus(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = CometPviClient.ReadSystemStatus();
|
||||
AppendLog("========== 系统状态 ==========");
|
||||
AppendLog($" 设定电压: {status.SetVoltage} kV | 实际电压: {status.ActualVoltage} kV");
|
||||
AppendLog($" 设定电流: {status.SetCurrent} μA | 实际电流: {status.ActualCurrent} μA");
|
||||
AppendLog($" 射线状态: {(status.IsXRayOn ? "开启" : "关闭")}");
|
||||
AppendLog($" 暖机: {status.WarmUpStatus} | 真空: {status.VacuumStatus}");
|
||||
AppendLog($" 训机: {status.StartUpStatus} | 自动定心: {status.AutoCenterStatus}");
|
||||
AppendLog($" 灯丝调整: {status.FilamentAdjustStatus}");
|
||||
AppendLog($" 连锁: {(status.IsInterlockActive ? "激活" : "未激活")} | 看门狗: {status.WatchdogStatus}");
|
||||
AppendLog($" 功率模式: {status.PowerMode} | TXI: {status.TxiStatus}");
|
||||
AppendLog("==============================");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] ReadSystemStatus 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReadErrors(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errors = CometPviClient.ReadErrors();
|
||||
AppendLog("========== 错误信息 ==========");
|
||||
AppendLog($" 系统错误: {errors.SystemError}");
|
||||
AppendLog($" HSG错误: {errors.HSGError}");
|
||||
AppendLog($" 管错误: {errors.TubeError}");
|
||||
AppendLog($" 管真空错误: {errors.TubeVacError}");
|
||||
AppendLog("==============================");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] ReadErrors 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWarmUp(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行暖机设置...", Color.Yellow);
|
||||
CometPviClient.WarmUp();
|
||||
AppendLog("[维护] 暖机设置指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 暖机设置失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTraining(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行训机设置...", Color.Yellow);
|
||||
CometPviClient.Training();
|
||||
AppendLog("[维护] 训机设置指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 训机设置失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilamentCalibration(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行灯丝校准...", Color.Yellow);
|
||||
CometPviClient.FilamentCalibration();
|
||||
AppendLog("[维护] 灯丝校准指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 灯丝校准失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAutoCenter(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行全部电压自动定心...", Color.Yellow);
|
||||
CometPviClient.AutoCenter();
|
||||
AppendLog("[维护] 自动定心指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] 自动定心失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTxiOn(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行 TXI ON...", Color.Yellow);
|
||||
CometPviClient.TxiOn();
|
||||
AppendLog("[维护] TXI ON 指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] TXI ON 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTxiOff(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendLog("[维护] 执行 TXI OFF...", Color.Yellow);
|
||||
CometPviClient.TxiOff();
|
||||
AppendLog("[维护] TXI OFF 指令已发送", Color.Lime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"[错误] TXI OFF 失败:{ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置操作类按钮的启用状态
|
||||
/// </summary>
|
||||
private void SetOperationButtonsEnabled(bool enabled)
|
||||
{
|
||||
_btnTurnOn.Enabled = enabled;
|
||||
_btnTurnOff.Enabled = enabled;
|
||||
_btnSetVoltage.Enabled = enabled;
|
||||
_btnSetCurrent.Enabled = enabled;
|
||||
_btnReadVoltage.Enabled = enabled;
|
||||
_btnReadCurrent.Enabled = enabled;
|
||||
_btnReadSystemStatus.Enabled = enabled;
|
||||
_btnReadErrors.Enabled = enabled;
|
||||
_btnWarmUp.Enabled = enabled;
|
||||
_btnTraining.Enabled = enabled;
|
||||
_btnFilamentCalibration.Enabled = enabled;
|
||||
_btnAutoCenter.Enabled = enabled;
|
||||
_btnTxiOn.Enabled = enabled;
|
||||
_btnTxiOff.Enabled = enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全地追加日志到 RichTextBox
|
||||
/// </summary>
|
||||
private void AppendLog(string message, Color? color = null)
|
||||
{
|
||||
SafeInvoke(() =>
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
var logLine = $"[{timestamp}] {message}\n";
|
||||
|
||||
_rtbLog.SelectionStart = _rtbLog.TextLength;
|
||||
_rtbLog.SelectionLength = 0;
|
||||
_rtbLog.SelectionColor = color ?? Color.LightGreen;
|
||||
_rtbLog.AppendText(logLine);
|
||||
_rtbLog.ScrollToCaret();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全地在 UI 线程执行操作
|
||||
/// </summary>
|
||||
private void SafeInvoke(Action action)
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
try { this.Invoke(action); }
|
||||
catch (ObjectDisposedException) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗体关闭和退出
|
||||
|
||||
/// <summary>
|
||||
/// 窗体加载后立即隐藏,默认只显示托盘图标
|
||||
/// </summary>
|
||||
protected override void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
this.Visible = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.WindowState = FormWindowState.Minimized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭窗体时最小化到托盘,不退出应用
|
||||
/// </summary>
|
||||
private void OnFormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason == CloseReason.UserClosing)
|
||||
{
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
this.ShowInTaskbar = false;
|
||||
_notifyIcon.ShowBalloonTip(1000, "XplorePlane Comet Host",
|
||||
"程序已最小化到系统托盘", ToolTipIcon.Info);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 托盘菜单退出(已禁用,仅提示)
|
||||
/// </summary>
|
||||
private void OnExitClick(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("XplorePlane Comet Host does not allow manual exit; it will close along with the shutdown of XplorePlane.\r\nXplorePlane Comet Host不允许手动退出,它会跟随XplorePlane的关闭一并退出。", "XplorePlane Comet Host Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制退出按钮点击 — 需要输入密码 Hexagon 才可退出
|
||||
/// Force exit button click — requires password "Hexagon" to exit
|
||||
/// </summary>
|
||||
private void OnForceExitClick(object sender, EventArgs e)
|
||||
{
|
||||
// 使用自定义输入对话框获取密码 | Use custom input dialog to get password
|
||||
using (var dlg = new ForceExitPasswordDialog())
|
||||
{
|
||||
if (dlg.ShowDialog(this) == DialogResult.OK && dlg.EnteredPassword == "Hexagon")
|
||||
{
|
||||
AppendLog("[系统] 强制退出已授权,正在退出... | Force exit authorized, exiting...", Color.Orange);
|
||||
ColseForm(sender, e);
|
||||
}
|
||||
else if (dlg.DialogResult == DialogResult.OK)
|
||||
{
|
||||
// 密码错误 | Wrong password
|
||||
MessageBox.Show("密码错误,无法退出。\r\nIncorrect password, cannot exit.",
|
||||
"XplorePlane Comet Host", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
AppendLog("[警告] 强制退出尝试失败:密码错误 | Force exit attempt failed: wrong password", Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 托盘菜单退出
|
||||
/// </summary>
|
||||
private void ColseForm(object sender, EventArgs e)
|
||||
{
|
||||
CleanupPipe();
|
||||
try { CometPviClient.Dispose(); } catch { }
|
||||
|
||||
if (_notifyIcon != null)
|
||||
{
|
||||
_notifyIcon.Visible = false;
|
||||
_notifyIcon.Dispose();
|
||||
_notifyIcon = null;
|
||||
}
|
||||
|
||||
// 移除 FormClosing 拦截,允许窗体真正关闭
|
||||
this.FormClosing -= OnFormClosing;
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user