完善蟹行虚拟阿克曼与SendMotion运动坐标系,并添加轮速诊断日志

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 18:10:22 +08:00
co-authored by Cursor
parent 7e05ff098e
commit c1fe73caec
92 changed files with 1794 additions and 776 deletions
+134 -31
View File
@@ -70,6 +70,9 @@ namespace MedullaAdapter
[AsInitParam(desc = "手动控制夹臂速度系数")] public float ManualArmSpeedFac = 1.0f;
[AsInitParam(desc = "遥控转弯舵角同步限速宽度,单位为度")]
public float ManualSteeringAlignmentSigmaDegrees = 8.0f;
[AsInitParam(desc = "轮速诊断日志相对目录")]
public string WheelSpeedDiagnosticDirectory =
@"logs\wheel-speed";
[AsInitParam(desc = "左夹臂低限位")][AsLowerIO] public int LeftArmLowerPos = -10000;
[AsInitParam(desc = "左夹臂高限位")][AsLowerIO] public int LeftArmUpperPos = 5927610;
[AsInitParam(desc = "右夹臂低限位")][AsLowerIO] public int RightArmLowerPos = -17295;
@@ -91,6 +94,10 @@ namespace MedullaAdapter
[IOObjectMonitor(desc = "右后右轮PID修正后速度")] public float SpeedRRR;
[IOObjectMonitor(desc = "灯光模式")] public int LightMode = 0;
[IOObjectMonitor(desc = "实体遥控器当前速度倍率")] public float TransmitterSpeed = 0.3f;
[IOObjectMonitor(desc = "轮速诊断记录已启用")]
public bool WheelSpeedDiagnosticEnabled;
[IOObjectMonitor(desc = "轮速诊断记录状态")]
public string WheelSpeedDiagnosticStatus = "未启动";
[IOObjectMonitor(desc = "左前左驱动器远程帧701")] public byte LFLRemoteCode = 0;
[IOObjectMonitor(desc = "左前右驱动器远程帧702")] public byte LFRRemoteCode = 0;
[IOObjectMonitor(desc = "右前左驱动器远程帧703")] public byte RFLRemoteCode = 0;
@@ -116,6 +123,22 @@ namespace MedullaAdapter
{
DisableFromM = true;
}
// M层诊断:请求开始保存CAN轮速事件和底盘周期快照。
[IOObjectUtility]
public void StartWheelSpeedDiagnostic()
{
WheelSpeedDiagnosticEnabled = true;
WheelSpeedDiagnosticStatus = "等待创建日志文件";
}
// M层诊断:请求停止轮速记录并刷新CSV文件。
[IOObjectUtility]
public void StopWheelSpeedDiagnostic()
{
WheelSpeedDiagnosticEnabled = false;
WheelSpeedDiagnosticStatus = "等待停止并刷新日志";
}
#endregion
public override void CommunicationInit()
@@ -234,10 +257,8 @@ namespace MedullaAdapter
Math.Sign(x);
var steeringDegrees =
-normalizedSteering * MaxManualTheta;
var omega = CalculateManualOmega(
speed,
steeringDegrees,
adapter.HalfWheelBaseMeters);
var frontTh = steeringDegrees;
var rearTh = -steeringDegrees;
ManualMode = (int)mode;
switch (mode)
@@ -245,27 +266,96 @@ namespace MedullaAdapter
case ManualControlMode.Normal:
// 普通模式统一使用车体速度命令:
// X向前,行驶中连续改变角速度时舵轮边转、车辆边走。
SendBodyCommand(
vx: speed,
vy: 0.0,
omegaRadiansPerSecond: omega,
interval);
// SendBodyCommand(
// vx: speed,
// vy: 0.0,
// omegaRadiansPerSecond: omega,
// interval);
Chassis.SendMotion(
speed,
frontTh,
rearTh,
interval);
break;
case ManualControlMode.Crab:
SendBodyCommand(vx: 0.0, vy: speed, omegaRadiansPerSecond: omega, interval);
// 舵轮机械范围为[-120°,120°]。
// 蟹行后虚拟轴距由原车宽度决定,比正常模式轴距短。
// 按几何比例缩小转角,使相同摇杆输入获得接近一致的曲率。
var normalSteeringRadians =
steeringDegrees *
Math.PI / 180.0;
var geometryRatio =
adapter.HalfTrackWidthMeters /
adapter.HalfWheelBaseMeters;
// +90°运动坐标系已经把虚拟左侧映射为车体后方,
// 此处保持普通模式的转向符号,避免再次取反导致左右颠倒。
var crabSteeringRadians =
Math.Atan(
geometryRatio *
Math.Tan(
normalSteeringRadians));
// 蟹行转角最终限制为±30°,为±120°机械舵角保留余量。
var maximumCrabSteeringRadians =
30.0 * Math.PI / 180.0;
crabSteeringRadians = Math.Max(
-maximumCrabSteeringRadians,
Math.Min(
maximumCrabSteeringRadians,
crabSteeringRadians));
// 将车体左侧作为虚拟阿克曼车头,并在该运动坐标系中
// 复用与普通模式相同的SendMotion前后控制点解算。
if (!adapter.SendVirtualAckermannMotion(
motionDirectionRadians:
Math.PI / 2.0,
speedMetersPerSecond:
speed,
steeringRadians:
crabSteeringRadians,
interval))
{
adapter.StopImmediately();
Console.WriteLine(
"蟹行SendMotion命令分解失败,车辆已经停车:" +
adapter.LastFailureReason);
}
break;
case ManualControlMode.Spin:
// 自转时speed表示最外侧舵轮的目标切向速度。
// 根据v=omega*r换算角速度,不能把m/s与deg/s直接相乘
var spinOmega =
// 摇杆处于中位时只清零驱动速度,保持已经准备好的
// 自转舵角;下次推动摇杆时仍会重新检查实际舵角
if (Math.Abs(speed) < 1e-6f)
{
adapter
.StopXYThDrivePreserveSteeringState();
break;
}
// 自转时speed表示最外侧舵轮中心的目标切向速度,
// 根据v=omega*r换算为SendXYThSpeed需要的角速度。
var spinOmegaRadiansPerSecond =
speed /
adapter.MaximumWheelRadiusMeters;
SendBodyCommand(
vx: 0.0,
vy: 0.0,
omegaRadiansPerSecond: spinOmega,
interval);
// 普通安全版SendXYThSpeed只下发角速度,
// 四轮实际舵角未到位时不会开放驱动速度。
if (!adapter.Send(
new ChassisCommand(
CarNum,
new Twist2D(
0.0,
0.0,
spinOmegaRadiansPerSecond)),
interval))
{
adapter.StopImmediately();
Console.WriteLine(
"SendXYThSpeed原地自转命令分解失败,车辆已经停车:" +
adapter.LastFailureReason);
}
break;
default:
ManualMode = -1;
@@ -294,6 +384,10 @@ namespace MedullaAdapter
if (_pendingManualMode != mode)
{
adapter.StopImmediately();
// 所有模式的准备角度均按真实机械舵角表达;
// 先退出上一模式的虚拟运动坐标系,再执行预对齐。
adapter.ResetToBodyFrame();
_activeManualMode = null;
var preparationAccepted = mode switch
{
@@ -354,24 +448,33 @@ namespace MedullaAdapter
if (!aligned)
return false;
if (mode == ManualControlMode.Spin)
{
// 四轮实际舵角确认到位后只交接一次,保留PrepareSpin
// 选定的机械舵角和轮速方向,避免首条XYTh命令重新选角。
if (!adapter.AdoptPreparedSpinForXYTh(
toleranceRadians))
{
return false;
}
}
// 蟹行轮子在真实车体系中到达机械+90°后,
// 再将车体左侧激活为SendMotion的虚拟X正方向。
else if (mode == ManualControlMode.Crab)
{
adapter.ActivateMotionFrame(
Math.PI / 2.0);
}
else
{
adapter.ResetToBodyFrame();
}
_activeManualMode = mode;
_pendingManualMode = null;
return true;
}
private double CalculateManualOmega(
float speed,
float steeringDegrees,
double halfWheelBaseMeters)
{
var steeringRadians =
steeringDegrees * Math.PI / 180.0;
return speed *
Math.Tan(steeringRadians) /
Math.Max(halfWheelBaseMeters, 0.01);
}
internal void SendBodyCommand(double vx, double vy, double omegaRadiansPerSecond, TimeSpan? interval = null)
{
var adapter = GetChassisAdapter();
+138 -9
View File
@@ -4,6 +4,7 @@ using FundamentalLib;
using MCUSerialBridgeCLR;
using System;
using System.Collections.Generic;
using System.IO;
namespace MedullaAdapter
{
@@ -26,6 +27,9 @@ namespace MedullaAdapter
private bool io_bit4 = false;//黄灯
private const byte BatteryPortIndex = 3;
private static readonly byte[] BatteryRequest = BuildBatteryRequest();
private readonly WheelSpeedDiagnosticLogger
_wheelSpeedLogger =
new WheelSpeedDiagnosticLogger();
// M层单车底盘:将车轮线速度换算为驱动电机转速。
private static float ConvertMps2Rpm(float mps)
@@ -50,6 +54,8 @@ namespace MedullaAdapter
// M层硬件主循环:交换IO、发送轮组指令并更新车辆反馈状态。
public override void Operation(int iteration)
{
UpdateWheelSpeedDiagnosticState();
if (_lastIteration != iteration)
{
_lastIteration = iteration;
@@ -158,6 +164,7 @@ namespace MedullaAdapter
cart.ActualSpeedLeftRear = (cart.ActualSpeedLeftRearLeft + cart.ActualSpeedLeftRearRight) / 2;
cart.ActualSpeedRightFront = (cart.ActualSpeedRightFrontLeft + cart.ActualSpeedRightFrontRight) / 2;
cart.ActualSpeedRightRear = (cart.ActualSpeedRightRearLeft + cart.ActualSpeedRightRearRight) / 2;
_wheelSpeedLogger.RecordSnapshot(cart);
// M层CAN辅助:封装本周期驱动器CAN发送参数。
MCUSerialBridgeError SendCan(byte port, ushort standardId, byte[] payload, bool RTR = false, uint timeout = 2)
{
@@ -264,8 +271,9 @@ namespace MedullaAdapter
{
_operationTime++;
return;
}
}
}
// 没有错误 没有节点保护 就发06 07 0F使能
else if (_operationTime == 4)
{
@@ -428,6 +436,79 @@ namespace MedullaAdapter
}
// M层诊断:根据界面开关创建或关闭本次轮速CSV记录。
private void UpdateWheelSpeedDiagnosticState()
{
if (cart == null)
return;
if (cart.WheelSpeedDiagnosticEnabled)
{
if (_wheelSpeedLogger.IsRunning)
return;
try
{
var configuredDirectory =
string.IsNullOrWhiteSpace(
cart.WheelSpeedDiagnosticDirectory)
? @"logs\wheel-speed"
: cart.WheelSpeedDiagnosticDirectory;
var logDirectory =
Path.IsPathRooted(configuredDirectory)
? configuredDirectory
: Path.Combine(
AppContext.BaseDirectory,
configuredDirectory);
_wheelSpeedLogger.Start(
logDirectory,
cart.CarNum);
cart.WheelSpeedDiagnosticStatus =
"记录中:" +
_wheelSpeedLogger.SnapshotLogPath;
}
catch (Exception ex)
{
cart.WheelSpeedDiagnosticEnabled =
false;
cart.WheelSpeedDiagnosticStatus =
"启动失败:" + ex.Message;
Console.WriteLine(
"轮速诊断启动失败:" +
ex.Message);
}
return;
}
if (!_wheelSpeedLogger.IsRunning)
return;
try
{
var snapshotPath =
_wheelSpeedLogger.SnapshotLogPath;
_wheelSpeedLogger.Stop();
cart.WheelSpeedDiagnosticStatus =
"已保存:" + snapshotPath;
}
catch (Exception ex)
{
cart.WheelSpeedDiagnosticStatus =
"停止失败:" + ex.Message;
Console.WriteLine(
"轮速诊断停止失败:" +
ex.Message);
}
}
// M层CAN安全:判断单个驱动节点是否处于可运行状态。
private static bool IsNodeOperational(byte remoteCode)
{
@@ -601,7 +682,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedLeftFrontLeft = ConvertRpm2Mps(rpm);
var speed = ConvertRpm2Mps(rpm);
cart.ActualSpeedLeftFrontLeft = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x281,
"LFL",
rpm,
speed);
cart.LFLActualPos = ConvertR2MM(BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x282] = (msg) =>
@@ -610,7 +697,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedLeftFrontRight = -ConvertRpm2Mps(rpm);
var speed = -ConvertRpm2Mps(rpm);
cart.ActualSpeedLeftFrontRight = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x282,
"LFR",
rpm,
speed);
cart.LFRActualPos = ConvertR2MM(-BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x283] = (msg) =>
@@ -619,7 +712,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedRightFrontLeft = ConvertRpm2Mps(rpm);
var speed = ConvertRpm2Mps(rpm);
cart.ActualSpeedRightFrontLeft = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x283,
"RFL",
rpm,
speed);
cart.RFLActualPos = ConvertR2MM(BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x284] = (msg) =>
@@ -628,7 +727,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedRightFrontRight = -ConvertRpm2Mps(rpm);
var speed = -ConvertRpm2Mps(rpm);
cart.ActualSpeedRightFrontRight = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x284,
"RFR",
rpm,
speed);
cart.RFRActualPos = ConvertR2MM(-BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x285] = (msg) =>
@@ -637,7 +742,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedLeftRearLeft = ConvertRpm2Mps(rpm);
var speed = ConvertRpm2Mps(rpm);
cart.ActualSpeedLeftRearLeft = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x285,
"LRL",
rpm,
speed);
cart.LRLActualPos = ConvertR2MM(BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x286] = (msg) =>
@@ -646,7 +757,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedLeftRearRight = -ConvertRpm2Mps(rpm);
var speed = -ConvertRpm2Mps(rpm);
cart.ActualSpeedLeftRearRight = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x286,
"LRR",
rpm,
speed);
cart.LRRActualPos = ConvertR2MM(-BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x287] = (msg) =>
@@ -655,7 +772,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedRightRearLeft = ConvertRpm2Mps(rpm);
var speed = ConvertRpm2Mps(rpm);
cart.ActualSpeedRightRearLeft = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x287,
"RRL",
rpm,
speed);
cart.RRLActualPos = ConvertR2MM(BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x288] = (msg) =>
@@ -664,7 +787,13 @@ namespace MedullaAdapter
var payload = msg.Payload;
if (payload == null || payload.Length < 8) return;
var rpm = DecodeRpmFromPayload(payload);
cart.ActualSpeedRightRearRight = -ConvertRpm2Mps(rpm);
var speed = -ConvertRpm2Mps(rpm);
cart.ActualSpeedRightRearRight = speed;
_wheelSpeedLogger.RecordCanFeedback(
0x288,
"RRR",
rpm,
speed);
cart.RRRActualPos = ConvertR2MM(-BitConverter.ToInt32(payload, 0) / 10000f);
},
[0x289] = (msg) =>
@@ -0,0 +1,362 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace MedullaAdapter
{
/// <summary>
/// 在后台保存驱动器CAN速度事件和底盘周期快照,避免文件IO阻塞CAN回调。
/// </summary>
internal sealed class WheelSpeedDiagnosticLogger : IDisposable
{
private readonly struct LogRecord
{
public LogRecord(bool isCanEvent, string line)
{
IsCanEvent = isCanEvent;
Line = line;
}
public bool IsCanEvent { get; }
public string Line { get; }
}
private const int MaximumQueuedRecords = 100000;
private const double SnapshotIntervalMilliseconds = 20.0;
private readonly ConcurrentQueue<LogRecord> _records = new();
private readonly AutoResetEvent _recordsAvailable = new(false);
private readonly object _lifecycleLock = new();
private Stopwatch _stopwatch;
private Thread _writerThread;
private StreamWriter _canWriter;
private StreamWriter _snapshotWriter;
private volatile bool _isRunning;
private int _queuedRecordCount;
private long _receiveSequence;
private long _droppedRecordCount;
private double _lastSnapshotMilliseconds = double.NegativeInfinity;
public bool IsRunning => _isRunning;
public string CanLogPath { get; private set; } = "";
public string SnapshotLogPath { get; private set; } = "";
/// <summary>
/// 创建本次诊断的两个CSV文件并启动后台写入线程。
/// </summary>
public void Start(string directory, int carNumber)
{
lock (_lifecycleLock)
{
if (_isRunning)
return;
if (string.IsNullOrWhiteSpace(directory))
throw new ArgumentException(
"轮速诊断目录不能为空。",
nameof(directory));
Directory.CreateDirectory(directory);
var filePrefix =
$"{DateTime.Now:yyyyMMdd_HHmmss_fff}_Car{carNumber}";
CanLogPath = Path.Combine(
directory,
$"{filePrefix}_can.csv");
SnapshotLogPath = Path.Combine(
directory,
$"{filePrefix}_snapshot.csv");
_canWriter = CreateWriter(CanLogPath);
_snapshotWriter = CreateWriter(SnapshotLogPath);
_canWriter.WriteLine(
"ElapsedMs,ReceiveSequence,CanId,MotorName,RawRpm,SpeedMps");
_snapshotWriter.WriteLine(
"ElapsedMs,CarNum,ManualMode,SendThresSpeed," +
"CmdLFL,CmdLFR,CmdLRL,CmdLRR,CmdRFL,CmdRFR,CmdRRL,CmdRRR," +
"PidLFL,PidLFR,PidLRL,PidLRR,PidRFL,PidRFR,PidRRL,PidRRR," +
"ActualLFL,ActualLFR,ActualLRL,ActualLRR,ActualRFL,ActualRFR,ActualRRL,ActualRRR," +
"ActualLeftFront,ActualLeftRear,ActualRightFront,ActualRightRear," +
"TargetThLeftFront,TargetThLeftRear,TargetThRightFront,TargetThRightRear," +
"ActualThLeftFront,ActualThLeftRear,ActualThRightFront,ActualThRightRear");
while (_records.TryDequeue(out _))
{
}
_queuedRecordCount = 0;
_receiveSequence = 0;
_droppedRecordCount = 0;
_lastSnapshotMilliseconds =
double.NegativeInfinity;
_stopwatch = Stopwatch.StartNew();
_isRunning = true;
_writerThread = new Thread(WriterLoop)
{
IsBackground = true,
Name = "WheelSpeedDiagnosticWriter"
};
_writerThread.Start();
}
}
/// <summary>
/// 停止记录并等待队列中的诊断数据写入磁盘。
/// </summary>
public void Stop()
{
Thread writerThread;
lock (_lifecycleLock)
{
if (!_isRunning &&
_writerThread == null)
return;
_isRunning = false;
writerThread = _writerThread;
_recordsAvailable.Set();
}
writerThread?.Join(3000);
lock (_lifecycleLock)
{
_canWriter?.Flush();
_snapshotWriter?.Flush();
_canWriter?.Dispose();
_snapshotWriter?.Dispose();
_canWriter = null;
_snapshotWriter = null;
_writerThread = null;
_stopwatch?.Stop();
}
}
/// <summary>
/// 将一帧驱动器速度反馈加入内存队列,不在CAN回调中执行文件写入。
/// </summary>
public void RecordCanFeedback(
ushort canId,
string motorName,
float rawRpm,
float speedMetersPerSecond)
{
if (!_isRunning)
return;
var elapsedMilliseconds =
_stopwatch.Elapsed.TotalMilliseconds;
var receiveSequence =
Interlocked.Increment(
ref _receiveSequence);
var line = string.Join(
",",
Format(elapsedMilliseconds),
receiveSequence.ToString(
CultureInfo.InvariantCulture),
$"0x{canId:X3}",
motorName,
Format(rawRpm),
Format(speedMetersPerSecond));
Enqueue(new LogRecord(
isCanEvent: true,
line));
}
/// <summary>
/// 按最多50Hz记录一帧控制命令、PID输出、CAN反馈和舵角快照。
/// </summary>
public void RecordSnapshot(
DiverCartDefinition cart)
{
if (!_isRunning || cart == null)
return;
var elapsedMilliseconds =
_stopwatch.Elapsed.TotalMilliseconds;
if (elapsedMilliseconds -
_lastSnapshotMilliseconds <
SnapshotIntervalMilliseconds)
{
return;
}
_lastSnapshotMilliseconds =
elapsedMilliseconds;
var line = string.Join(
",",
Format(elapsedMilliseconds),
cart.CarNum.ToString(
CultureInfo.InvariantCulture),
Format(cart.ManualMode),
Format(cart.SendThresSpeed),
Format(cart.SpeedLeftFrontLeft),
Format(cart.SpeedLeftFrontRight),
Format(cart.SpeedLeftRearLeft),
Format(cart.SpeedLeftRearRight),
Format(cart.SpeedRightFrontLeft),
Format(cart.SpeedRightFrontRight),
Format(cart.SpeedRightRearLeft),
Format(cart.SpeedRightRearRight),
Format(cart.SpeedLFL),
Format(cart.SpeedLFR),
Format(cart.SpeedLRL),
Format(cart.SpeedLRR),
Format(cart.SpeedRFL),
Format(cart.SpeedRFR),
Format(cart.SpeedRRL),
Format(cart.SpeedRRR),
Format(cart.ActualSpeedLeftFrontLeft),
Format(cart.ActualSpeedLeftFrontRight),
Format(cart.ActualSpeedLeftRearLeft),
Format(cart.ActualSpeedLeftRearRight),
Format(cart.ActualSpeedRightFrontLeft),
Format(cart.ActualSpeedRightFrontRight),
Format(cart.ActualSpeedRightRearLeft),
Format(cart.ActualSpeedRightRearRight),
Format(cart.ActualSpeedLeftFront),
Format(cart.ActualSpeedLeftRear),
Format(cart.ActualSpeedRightFront),
Format(cart.ActualSpeedRightRear),
Format(cart.ThLeftFront),
Format(cart.ThLeftRear),
Format(cart.ThRightFront),
Format(cart.ThRightRear),
Format(cart.ActualThLeftFront),
Format(cart.ActualThLeftRear),
Format(cart.ActualThRightFront),
Format(cart.ActualThRightRear));
Enqueue(new LogRecord(
isCanEvent: false,
line));
}
private static StreamWriter CreateWriter(
string path)
{
return new StreamWriter(
path,
append: false,
new UTF8Encoding(
encoderShouldEmitUTF8Identifier: true),
bufferSize: 64 * 1024);
}
private void Enqueue(LogRecord record)
{
var queuedCount =
Interlocked.Increment(
ref _queuedRecordCount);
if (queuedCount >
MaximumQueuedRecords)
{
Interlocked.Decrement(
ref _queuedRecordCount);
Interlocked.Increment(
ref _droppedRecordCount);
return;
}
_records.Enqueue(record);
_recordsAvailable.Set();
}
private void WriterLoop()
{
var lastFlushTime = DateTime.UtcNow;
try
{
while (_isRunning ||
!_records.IsEmpty)
{
var wroteAnyRecord = false;
while (_records.TryDequeue(
out var record))
{
Interlocked.Decrement(
ref _queuedRecordCount);
if (record.IsCanEvent)
_canWriter.WriteLine(record.Line);
else
_snapshotWriter.WriteLine(record.Line);
wroteAnyRecord = true;
}
var shouldFlush =
wroteAnyRecord &&
(DateTime.UtcNow -
lastFlushTime)
.TotalMilliseconds >= 500.0;
if (shouldFlush)
{
_canWriter.Flush();
_snapshotWriter.Flush();
lastFlushTime = DateTime.UtcNow;
}
if (!wroteAnyRecord)
_recordsAvailable.WaitOne(100);
}
var dropped =
Interlocked.Read(
ref _droppedRecordCount);
if (dropped > 0)
{
_canWriter.WriteLine(
$"# DroppedRecords={dropped}");
_snapshotWriter.WriteLine(
$"# DroppedRecords={dropped}");
}
_canWriter.Flush();
_snapshotWriter.Flush();
}
catch (Exception ex)
{
// 后台日志失败不能终止车辆控制线程。
Console.WriteLine(
"轮速诊断后台写入失败:" +
ex.Message);
}
}
private static string Format(
double value)
{
return value.ToString(
"0.######",
CultureInfo.InvariantCulture);
}
public void Dispose()
{
Stop();
_recordsAvailable.Dispose();
}
}
}
Binary file not shown.
@@ -10,7 +10,7 @@
"projectUniqueName": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\MedullaAdapter.csproj",
"projectName": "MedullaAdapter",
"projectPath": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\MedullaAdapter.csproj",
"packagesPath": "C:\\Users\\admin\\.nuget\\packages\\",
"packagesPath": "C:\\Users\\CodexSandboxOffline\\.nuget\\packages\\",
"outputPath": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
@@ -4,13 +4,13 @@
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\admin\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">C:\Users\CodexSandboxOffline\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\CodexSandboxOffline\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.3</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\admin\.nuget\packages\" />
<SourceRoot Include="C:\Users\CodexSandboxOffline\.nuget\packages\" />
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -8,7 +8,7 @@
"net8.0": []
},
"packageFolders": {
"C:\\Users\\admin\\.nuget\\packages\\": {},
"C:\\Users\\CodexSandboxOffline\\.nuget\\packages\\": {},
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
},
"project": {
@@ -17,7 +17,7 @@
"projectUniqueName": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\MedullaAdapter.csproj",
"projectName": "MedullaAdapter",
"projectPath": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\MedullaAdapter.csproj",
"packagesPath": "C:\\Users\\admin\\.nuget\\packages\\",
"packagesPath": "C:\\Users\\CodexSandboxOffline\\.nuget\\packages\\",
"outputPath": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"version": 2,
"dgSpecHash": "4fABnQtycfA=",
"dgSpecHash": "b9v8vkN2ac8=",
"success": true,
"projectFilePath": "D:\\Users\\Desktop\\入职培训\\停车机器人\\MyParking\\MedullaAdapter\\MedullaAdapter.csproj",
"expectedPackageFiles": [],