@page "/sick" @implements IDisposable @inject IOptions CanOptions @inject ICanBusService CanService @inject IJSRuntime JS
Sick Encoder Monitor
@foreach (var f in _frames.Values.OrderBy(f => f.CobId)) { }
CAN-ID Length Data Position (mm) Time
@($"0x{f.CobId:X3}") @f.Length @f.DataHex @(f.PositionMm.HasValue? f.PositionMm.Value.ToString("F2") : "-") @f.Timestamp.ToString("HH:mm:ss.fff")
Node State: @_nodeState | Bitrate: @(CurrentBitrate / 1000) kbit/s | Node ID: @CurrentNodeId
@code { private PositionPdo? _current; private CanNodeState _nodeState = CanNodeState.Unknown; private int CurrentBitrate => CanService.CurrentBitrate; private readonly Dictionary _frames = new(); private int _selectedBitrate; private bool _bitrateApplied = false; private bool _starting; private bool _stopping; private bool _applying; private int? _inputNodeId; private byte CurrentNodeId => CanService.CurrentNodeId; protected override void OnInitialized() { CanService.PositionReceived += OnPositionReceived; CanService.NodeStateChanged += OnNodeStateChanged; CanService.FrameReceived += OnFrameReceived; CanService.NodeIdChanged += OnNodeIdChanged; _selectedBitrate = CanService.CurrentBitrate; } private async Task Start() { // ❗ LUÔN init lại để đảm bảo COB-ID & state sạch await CanService.InitAsync(); // ❗ Reset node theo Node ID MỚI CanService.SendNmtReset(CurrentNodeId); await Task.Delay(800); CanService.SendNmtStart(CurrentNodeId); // ❗ Start read với Node ID MỚI CanService.Start(); } private Task Stop() { CanService.SendNmtStop(CurrentNodeId); return Task.CompletedTask; } private void OnPositionReceived(object? sender, PositionPdo e) { _ = InvokeAsync(() => { _current = e; StateHasChanged(); }); } public void Dispose() { CanService.PositionReceived -= OnPositionReceived; CanService.NodeStateChanged -= OnNodeStateChanged; CanService.FrameReceived -= OnFrameReceived; CanService.NodeIdChanged -= OnNodeIdChanged; CanService.Stop(); } private void OnNodeStateChanged(object? sender, CanNodeState state) { _ = InvokeAsync(() => { _nodeState = state; StateHasChanged(); }); } private string StateClass => _nodeState switch { CanNodeState.Operational => "state-op", CanNodeState.PreOperational => "state-preop", CanNodeState.Stopped => "state-stop", CanNodeState.Timeout => "state-timeout", _ => "state-unknown" }; private void OnFrameReceived(object? sender, CanFrame frame) { _ = InvokeAsync(() => { // mỗi CAN-ID chỉ giữ frame mới nhất _frames[frame.CobId] = frame; StateHasChanged(); }); } private async Task ApplyBitrate() { await CanService.ApplyBitrateAsync( CurrentNodeId, _selectedBitrate ); // UI chỉ báo PreOp / Stopped _nodeState = CanNodeState.PreOperational; } private async Task OnApplyNodeIdClicked() { if (!_inputNodeId.HasValue) return; byte newNodeId = (byte)_inputNodeId.Value; byte oldNodeId = CanService.CurrentNodeId; await CanService.ApplyNodeIdAsync(oldNodeId, newNodeId); _inputNodeId = null; // reset input } private async Task OnStartClicked() { if (_starting) return; _starting = true; StateHasChanged(); try { // ✅ Nếu CHƯA Apply bitrate → dùng bitrate hiện tại if (!_bitrateApplied) { _selectedBitrate = CanService.CurrentBitrate; } await Start(); } finally { _starting = false; StateHasChanged(); } } private async Task OnStopClicked() { if (_stopping) return; _stopping = true; StateHasChanged(); try { await Stop(); } finally { _stopping = false; StateHasChanged(); } } private async Task OnApplyClicked() { if (_applying) return; _applying = true; StateHasChanged(); try { await ApplyBitrate(); // ✅ Đánh dấu đã Apply _bitrateApplied = true; } finally { _applying = false; StateHasChanged(); } } private void OnNodeIdChanged(object? sender, byte newNodeId) { _ = InvokeAsync(() => { // 🔥 XÓA TOÀN BỘ FRAME CŨ _frames.Clear(); // reset input _inputNodeId = null; StateHasChanged(); }); } }