新增1.0和2.0两种协议车型车型
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace StandardScene.Fass2Simulator
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private const int MaxLogLines = 800;
|
||||
private readonly StringBuilder _logBuffer = new StringBuilder();
|
||||
private int _logLineCount;
|
||||
private Fass2SimConfig _config;
|
||||
private Fass2SimRuntime _runtime;
|
||||
private readonly DispatcherTimer _uiTimer;
|
||||
private readonly Queue<string> _pendingLogLines = new Queue<string>();
|
||||
private readonly object _logQueueLock = new object();
|
||||
private bool _logFlushScheduled;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_config = Fass2SimBootstrap.LoadConfig();
|
||||
Fass2SimFileLogger.Configure(_config);
|
||||
LoadConfigToUi(_config);
|
||||
SetRunningUi(false);
|
||||
UpdateLogFilePathText();
|
||||
|
||||
Fass2SimLog.MessageWritten += OnLogMessage;
|
||||
_uiTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
|
||||
_uiTimer.Tick += (_, __) => RefreshStatus();
|
||||
_uiTimer.Start();
|
||||
RefreshStatus();
|
||||
|
||||
Closed += (_, __) =>
|
||||
{
|
||||
Fass2SimLog.MessageWritten -= OnLogMessage;
|
||||
_runtime?.Dispose();
|
||||
Fass2SimFileLogger.Shutdown();
|
||||
};
|
||||
|
||||
AppendLog("就绪。配置参数后点击「启动模拟」开始联调。完整日志将写入 logs 目录。");
|
||||
}
|
||||
|
||||
private void StartButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_runtime != null && _runtime.IsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_config = ReadConfigFromUi();
|
||||
Fass2SimFileLogger.Configure(_config);
|
||||
Fass2SimFileLogger.BeginSession(_config);
|
||||
UpdateLogFilePathText();
|
||||
_runtime?.Dispose();
|
||||
_runtime = Fass2SimBootstrap.CreateRuntime(_config);
|
||||
_runtime.Start();
|
||||
SetRunningUi(true);
|
||||
RefreshStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, ex.Message, "启动失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_runtime == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtime.Stop();
|
||||
SetRunningUi(false);
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void ClearLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_logBuffer.Clear();
|
||||
_logLineCount = 0;
|
||||
LogTextBox.Clear();
|
||||
}
|
||||
|
||||
private void OpenLogFolderButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var directory = Fass2SimFileLogger.GetLogDirectory();
|
||||
Directory.CreateDirectory(directory);
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = directory,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateLogFilePathText()
|
||||
{
|
||||
if (!_config.EnableFileLog)
|
||||
{
|
||||
LogFilePathText.Text = "文件日志已关闭(appsettings.json: EnableFileLog=false)";
|
||||
return;
|
||||
}
|
||||
|
||||
var path = Fass2SimFileLogger.CurrentFilePath;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
LogFilePathText.Text = $"日志目录: {Fass2SimFileLogger.GetLogDirectory()}(启动模拟后生成会话日志)";
|
||||
return;
|
||||
}
|
||||
|
||||
LogFilePathText.Text = $"日志文件: {path}";
|
||||
}
|
||||
|
||||
private void OnLogMessage(string message)
|
||||
{
|
||||
lock (_logQueueLock)
|
||||
{
|
||||
_pendingLogLines.Enqueue(message);
|
||||
}
|
||||
|
||||
if (_logFlushScheduled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logFlushScheduled = true;
|
||||
Dispatcher.BeginInvoke(new Action(FlushPendingLogs), DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void FlushPendingLogs()
|
||||
{
|
||||
_logFlushScheduled = false;
|
||||
List<string> batch;
|
||||
lock (_logQueueLock)
|
||||
{
|
||||
if (_pendingLogLines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
batch = new List<string>(_pendingLogLines.Count);
|
||||
while (_pendingLogLines.Count > 0)
|
||||
{
|
||||
batch.Add(_pendingLogLines.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var line in batch)
|
||||
{
|
||||
AppendLog(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logBuffer.AppendLine(message);
|
||||
_logLineCount++;
|
||||
while (_logLineCount > MaxLogLines)
|
||||
{
|
||||
var bufferText = _logBuffer.ToString();
|
||||
var firstBreak = bufferText.IndexOf('\n');
|
||||
if (firstBreak < 0)
|
||||
{
|
||||
_logBuffer.Clear();
|
||||
_logLineCount = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
_logBuffer.Remove(0, firstBreak + 1);
|
||||
_logLineCount--;
|
||||
}
|
||||
|
||||
LogTextBox.Text = _logBuffer.ToString();
|
||||
LogTextBox.CaretIndex = LogTextBox.Text.Length;
|
||||
LogTextBox.ScrollToEnd();
|
||||
}
|
||||
|
||||
private void RefreshStatus()
|
||||
{
|
||||
if (_runtime == null || !_runtime.IsRunning)
|
||||
{
|
||||
if (_runtime == null)
|
||||
{
|
||||
ApplyIdleStatus(
|
||||
_config.InitialNode.ToString(),
|
||||
"-",
|
||||
"0",
|
||||
Fass2SimProtocol.StateText(_config.InitialState),
|
||||
"0",
|
||||
"-",
|
||||
"0",
|
||||
$"{_config.BatteryCharge}%",
|
||||
"-",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"无");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var snap = _runtime.Vehicle.Snapshot();
|
||||
var expected = _runtime.Actions.ExpectedStation;
|
||||
var pending = expected == null
|
||||
? "-"
|
||||
: Fass2SimActionResolver.DescribePending(expected, snap);
|
||||
if (string.IsNullOrEmpty(pending))
|
||||
{
|
||||
pending = "-";
|
||||
}
|
||||
|
||||
var distanceText = snap.Distance.ToString();
|
||||
var targetNodeText = "-";
|
||||
if (_runtime.Motion.TryGetSegmentStatus(out var targetNode, out var segmentLengthMm, out var progressMm))
|
||||
{
|
||||
targetNodeText = targetNode.ToString();
|
||||
distanceText = $"{progressMm} / {segmentLengthMm}";
|
||||
}
|
||||
|
||||
ApplyIdleStatus(
|
||||
snap.Node.ToString(),
|
||||
targetNodeText,
|
||||
distanceText,
|
||||
Fass2SimProtocol.StateText(snap.State),
|
||||
snap.Task.ToString(),
|
||||
snap.StartStop.ToString(),
|
||||
snap.Lift.ToString(),
|
||||
$"{snap.BatteryCharge}%",
|
||||
pending,
|
||||
snap.StateReportsSent.ToString(),
|
||||
snap.CommandsReceived.ToString(),
|
||||
snap.AckReceived.ToString(),
|
||||
snap.LastAckUtc == DateTime.MinValue
|
||||
? "无"
|
||||
: $"{(DateTime.UtcNow - snap.LastAckUtc).TotalSeconds:F1}s 前");
|
||||
}
|
||||
|
||||
private void ApplyIdleStatus(
|
||||
string node,
|
||||
string targetNode,
|
||||
string distance,
|
||||
string state,
|
||||
string task,
|
||||
string startStop,
|
||||
string lift,
|
||||
string battery,
|
||||
string pending,
|
||||
string reports,
|
||||
string commands,
|
||||
string ack,
|
||||
string lastAck)
|
||||
{
|
||||
NodeText.Text = node;
|
||||
TargetNodeText.Text = targetNode;
|
||||
DistanceText.Text = distance;
|
||||
StateText.Text = state;
|
||||
TaskText.Text = task;
|
||||
StartStopText.Text = startStop;
|
||||
LiftText.Text = lift;
|
||||
BatteryText.Text = battery;
|
||||
PendingText.Text = pending;
|
||||
ReportsText.Text = reports;
|
||||
CommandsText.Text = commands;
|
||||
AckText.Text = ack;
|
||||
LastAckText.Text = lastAck;
|
||||
}
|
||||
|
||||
private void LoadConfigToUi(Fass2SimConfig config)
|
||||
{
|
||||
VehicleCodeBox.Text = config.VehicleCode.ToString();
|
||||
ListenAddressBox.Text = config.VehicleListenAddress;
|
||||
ListenPortBox.Text = config.VehicleListenPort.ToString();
|
||||
SchedulerHostBox.Text = config.SchedulerHost;
|
||||
SchedulerPortBox.Text = config.SchedulerListenPort.ToString();
|
||||
ReportIntervalBox.Text = config.ReportIntervalMs.ToString();
|
||||
InitialNodeBox.Text = config.InitialNode.ToString();
|
||||
AutoContinueBox.IsChecked = config.AutoContinueOnPass;
|
||||
AutoCompleteBox.IsChecked = config.AutoCompleteStationActions;
|
||||
LogRawFramesBox.IsChecked = config.LogRawFrames;
|
||||
}
|
||||
|
||||
private Fass2SimConfig ReadConfigFromUi()
|
||||
{
|
||||
return new Fass2SimConfig
|
||||
{
|
||||
VehicleCode = ParseUShort(VehicleCodeBox.Text, "车号"),
|
||||
VehicleListenAddress = ListenAddressBox.Text?.Trim() ?? "0.0.0.0",
|
||||
VehicleListenPort = ParseInt(ListenPortBox.Text, "车体监听端口"),
|
||||
SchedulerHost = SchedulerHostBox.Text?.Trim() ?? "127.0.0.1",
|
||||
SchedulerListenPort = ParseInt(SchedulerPortBox.Text, "调度监听端口"),
|
||||
ReportIntervalMs = ParseInt(ReportIntervalBox.Text, "上报周期"),
|
||||
InitialNode = ParseUShort(InitialNodeBox.Text, "初始节点"),
|
||||
InitialState = _config?.InitialState ?? 0,
|
||||
BatteryCharge = _config?.BatteryCharge ?? 100,
|
||||
CarLength = _config?.CarLength ?? 1200,
|
||||
CarWidth = _config?.CarWidth ?? 800,
|
||||
DefaultSpeed = _config?.DefaultSpeed ?? 500,
|
||||
DefaultSegmentDistance = _config?.DefaultSegmentDistance ?? 1000,
|
||||
SecondsPerSegment = _config?.SecondsPerSegment ?? 2,
|
||||
ActionDelayMs = _config?.ActionDelayMs ?? 1000,
|
||||
NodeProfilesPath = _config?.NodeProfilesPath ?? "sim-nodes.json",
|
||||
AutoContinueOnPass = AutoContinueBox.IsChecked == true,
|
||||
AutoCompleteStationActions = AutoCompleteBox.IsChecked == true,
|
||||
LogRawFrames = LogRawFramesBox.IsChecked == true,
|
||||
EnableFileLog = _config?.EnableFileLog ?? true,
|
||||
LogDirectory = _config?.LogDirectory ?? "logs"
|
||||
};
|
||||
}
|
||||
|
||||
private void SetRunningUi(bool running)
|
||||
{
|
||||
StartButton.IsEnabled = !running;
|
||||
StopButton.IsEnabled = running;
|
||||
VehicleCodeBox.IsEnabled = !running;
|
||||
ListenAddressBox.IsEnabled = !running;
|
||||
ListenPortBox.IsEnabled = !running;
|
||||
SchedulerHostBox.IsEnabled = !running;
|
||||
SchedulerPortBox.IsEnabled = !running;
|
||||
ReportIntervalBox.IsEnabled = !running;
|
||||
InitialNodeBox.IsEnabled = !running;
|
||||
AutoContinueBox.IsEnabled = !running;
|
||||
AutoCompleteBox.IsEnabled = !running;
|
||||
LogRawFramesBox.IsEnabled = !running;
|
||||
|
||||
RunStateText.Text = running ? "运行中" : "已停止";
|
||||
RunStateText.Foreground = running
|
||||
? new SolidColorBrush(Color.FromRgb(22, 163, 74))
|
||||
: new SolidColorBrush(Color.FromRgb(107, 114, 128));
|
||||
}
|
||||
|
||||
private static ushort ParseUShort(string text, string fieldName)
|
||||
{
|
||||
if (!ushort.TryParse(text?.Trim(), out var value))
|
||||
{
|
||||
throw new InvalidOperationException($"{fieldName} 无效:{text}");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static int ParseInt(string text, string fieldName)
|
||||
{
|
||||
if (!int.TryParse(text?.Trim(), out var value))
|
||||
{
|
||||
throw new InvalidOperationException($"{fieldName} 无效:{text}");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user