996 lines
35 KiB
C#
996 lines
35 KiB
C#
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
|
|
}
|
|
}
|