新增1.0和2.0两种协议车型车型

This commit is contained in:
ykkokluo
2026-07-17 13:36:01 +08:00
parent a0dc1e6cd0
commit a28bc68e23
62 changed files with 12638 additions and 203 deletions
@@ -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;
}
}
}