7.0 KiB
Olei LiDAR Sensor - Implementation Notes
Tổng Quan
Project này cung cấp một high-performance server để nhận và phân giải dữ liệu từ Olei LiDAR Sensor (LR-1F / LR-1BS) qua giao thức UDP/IP.
Kiến Trúc
1. Data Structures
LidarHeader (40 bytes)
- Struct với
StructLayout(LayoutKind.Sequential, Pack = 1)để đảm bảo layout memory chính xác - Chứa thông tin frame ID, protocol version, distance scale, rotation rate, error status, v.v.
- Các property helper để dễ dàng truy cập thông tin (RotationRate, IsCounterClockwise, HasError, etc.)
LidarDataBlock (8 bytes)
- Struct cho mỗi measurement point
- Chứa angle (0-35999 = 0°-359.99°), distance, signal strength
- Methods để chuyển đổi sang degrees/radians và Cartesian coordinates (X, Y)
LidarDataPacket (1240 bytes total)
- Class chứa header + 150 data blocks
- Methods để lọc và iterate qua valid data points
- Hỗ trợ conversion sang Cartesian coordinates
2. Parser
LidarPacketParser
- Zero-allocation parsing sử dụng
Span<byte>vàReadOnlySpan<byte> - Sử dụng
MemoryMarshal.Read<T>()cho fast struct deserialization - Validation methods để kiểm tra packet nhanh mà không cần parse toàn bộ
- Static class - không cần instantiation
3. Server
OleiLidarServer
- Dedicated Thread với
ThreadPriority.AboveNormalcho receiving - ArrayPool để reuse buffers, giảm GC pressure
- ConcurrentBag để pool LidarDataPacket objects cho reuse
- Event-based callbacks cho data và error handling
- Thread-safe operations với
Interlockedcho counters - Proper
IDisposableimplementation cho resource cleanup
Tối Ưu Hiệu Suất
1. Memory Optimization
Buffer Pooling
private readonly ArrayPool<byte> _bufferPool = ArrayPool<byte>.Shared;
byte[] buffer = _bufferPool.Rent(size);
// ... use buffer ...
_bufferPool.Return(buffer);
Packet Pooling (Automatic)
private readonly ConcurrentBag<LidarDataPacket> _packetPool;
// Get from pool or create new
if (!_packetPool.TryTake(out var packet))
packet = new LidarDataPacket();
// ... use packet ...
// Automatically returned to pool after event handlers complete
// User doesn't need to call ReturnPacketToPool!
2. Zero-Allocation Parsing
Using Span
public static bool TryParse(ReadOnlySpan<byte> data, LidarDataPacket packet)
{
// No allocation - direct memory access
header = MemoryMarshal.Read<LidarHeader>(data);
}
3. Thread Optimization
Dedicated Receive Thread
- Thread riêng biệt với priority cao chỉ cho receiving
- Tránh context switching và latency
- Non-blocking với small sleep để prevent busy-waiting
_receiveThread = new Thread(ReceiveLoop)
{
Name = "OleiLidar-Receive",
IsBackground = true,
Priority = ThreadPriority.AboveNormal
};
4. Struct vs Class
- Struct cho
LidarHeadervàLidarDataBlock: Stack allocation, no GC - Class cho
LidarDataPacket: Chứa array lớn, pooling cho reuse
Usage Pattern
Basic Usage
using var server = new OleiLidarServer(2368);
server.DataReceived += (sender, e) =>
{
var packet = e.Packet;
// Process data
foreach (var block in packet.GetValidDataBlocks())
{
double angle = block.GetAngleDegrees();
double distance = block.GetDistance(packet.Header.DistanceScale);
// ... process ...
}
// Packet is automatically returned to pool after handler completes
// No manual pooling needed!
};
server.Start();
Performance Characteristics
Expected Performance
- Throughput: > 1000 packets/second
- Latency: < 1ms per packet processing
- Memory: Minimal allocation sau warm-up (thanks to pooling)
- CPU: Low overhead do zero-allocation parsing
Profiling Tips
- Monitor
TotalPacketsReceivedvsTotalPacketsParsedđể detect packet loss - Track
TotalParseErrorsđể detect data corruption - Monitor memory usage - should be flat after initial warm-up
- GC collections should be minimal
Thread Safety
- Server có thể start/stop từ bất kỳ thread nào (protected by lock)
- Event callbacks được raised từ receive thread
- Statistics counters sử dụng
Interlockedcho atomic operations - Packet pool sử dụng
ConcurrentBag(thread-safe)
Error Handling
Hardware Errors
Server tự động detect và report lỗi từ LiDAR:
- Motor fault (BIT0)
- Abnormal voltage (BIT1)
- Temperature fault (BIT2)
Access qua:
if (packet.Header.HasError)
{
bool motorFault = packet.Header.HasMotorFault;
bool voltageFault = packet.Header.HasAbnormalVoltage;
bool tempFault = packet.Header.HasTemperatureFault;
}
Network Errors
Tất cả network errors được catch và raised qua ErrorOccurred event.
Best Practices
-
Don't store packet references - Packets are automatically pooled and reused:
// ❌ WRONG - packet will be reused! LidarDataPacket? stored = null; server.DataReceived += (s, e) => { stored = e.Packet; }; // ✅ CORRECT - copy data if needed server.DataReceived += (s, e) => { var points = e.Packet.GetValidPoints().ToList(); }; -
Process quickly - Handler runs on receive thread, avoid long operations
-
Subscribe to ErrorOccurred event để monitor issues
-
Use statistics để track health (
GetStatistics()) -
Dispose properly để cleanup resources
Configuration
UDP Buffer Size
Default: 1MB receive buffer
_udpClient.Client.ReceiveBufferSize = 1024 * 1024;
Packet Pool Size
Initial: 10 packets, Max: 50 packets Có thể adjust trong constructor nếu cần.
Testing
Unit Tests
Test các component riêng lẻ:
LidarPacketParser.TryParse()với test data- Header và DataBlock property accessors
- Validation logic
Integration Tests
Test với real sensor hoặc UDP packet simulator:
// Send test packet
using var client = new UdpClient();
byte[] testData = CreateTestPacket();
client.Send(testData, testData.Length, "localhost", 2368);
Future Enhancements
- Async/Await Support: Add async version of server
- Recording: Add packet recording to file
- Replay: Add packet replay from file
- Filtering: Add built-in distance/angle filtering
- Visualization: Add real-time visualization support
- Multi-Sensor: Support multiple sensors simultaneously
Dependencies
- .NET 10.0
- System.Buffers (ArrayPool)
- System.Collections.Concurrent (ConcurrentBag)
- System.Net.Sockets (UdpClient)
- System.Runtime.InteropServices (MemoryMarshal)
License
Project này là một phần của RobotNet10.