SickApp/SickBlazorApp/Components/Pages/Sick.razor

315 lines
8.6 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/sick"
@implements IDisposable
@inject IOptions<CanBusOptions> CanOptions
@inject ICanBusService CanService
@inject IJSRuntime JS
<div class="encoder-container">
<div class="encoder-card">
<!-- HEADER -->
<div class="encoder-header">
<div class="encoder-title">Sick Encoder Monitor</div>
<div class="encoder-actions">
<button class="btn btn-start @( _starting ? "loading" : "" )"
disabled="@(_starting || _applying)"
@onclick="OnStartClicked">
@(_starting ? "Starting..." : "Start")
</button>
<button class="btn btn-stop @( _stopping ? "loading" : "" )"
disabled="@(_stopping || _applying)"
@onclick="OnStopClicked">
@(_stopping ? "Stopping..." : "Stop")
</button>
</div>
</div>
<!-- TABLE -->
<table class="encoder-table">
<thead>
<tr>
<th>CAN-ID</th>
<th>Length</th>
<th>Data</th>
<th>Position (mm)</th>
<th>Time</th>
</tr>
</thead>
<tbody>
@foreach (var f in _frames.Values.OrderBy(f => f.CobId))
{
<tr>
<td class="encoder-id">@($"0x{f.CobId:X3}")</td>
<td>@f.Length</td>
<td class="encoder-data">@f.DataHex</td>
<td class="encoder-pos">
@(f.PositionMm.HasValue? f.PositionMm.Value.ToString("F2") : "-")
</td>
<td class="encoder-time">@f.Timestamp.ToString("HH:mm:ss.fff")</td>
</tr>
}
</tbody>
</table>
<!-- STATUS -->
<div class="encoder-status @StateClass"> Node State: @_nodeState | Bitrate: @(CurrentBitrate / 1000) kbit/s | Node ID: @CurrentNodeId</div>
<div class="bitrate-group">
<label class="bitrate-label">Bitrate</label>
<select @bind="_selectedBitrate" class="bitrate-select">
<option value="1000000">1000 kbit/s</option>
<option value="800000">800 kbit/s</option>
<option value="500000">500 kbit/s</option>
<option value="250000">250 kbit/s</option>
<option value="125000">125 kbit/s</option>
<option value="100000">100 kbit/s</option>
<option value="50000">50 kbit/s</option>
<option value="20000">20 kbit/s</option>
</select>
<button class="btn btn-apply @( _applying ? "loading" : "" )"
disabled="@(_starting || _applying)"
@onclick="OnApplyClicked">
@(_applying ? "Applying..." : "Apply Bitrate")
</button>
<label class="nodeid-label">Node ID (1127)</label>
<input type="number"
min="1"
max="127"
@bind="_inputNodeId"
placeholder="@CurrentNodeId"
class="nodeid-input" />
<button class="btn btn-apply"
disabled="@(!_inputNodeId.HasValue)"
@onclick="OnApplyNodeIdClicked">
Apply Node ID
</button>
</div>
</div>
</div>
@code {
private PositionPdo? _current;
private CanNodeState _nodeState = CanNodeState.Unknown;
private int CurrentBitrate => CanService.CurrentBitrate;
private readonly Dictionary<uint, CanFrame> _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;
_applying = true; // Sử dụng flag để disable các nút khác
StateHasChanged();
try
{
byte newNodeId = (byte)_inputNodeId.Value;
byte oldNodeId = CanService.CurrentNodeId;
// 1. Gửi lệnh đổi ID qua CAN bus
await CanService.ApplyNodeIdAsync(oldNodeId, newNodeId);
// 2. DỪNG SERVICE NGAY LẬP TỨC
// Điều này ngăn việc nhận frame từ ID cũ
CanService.Stop();
// 3. Reset UI state
_frames.Clear();
_nodeState = CanNodeState.PreOperational;
_inputNodeId = null;
}
catch (Exception ex)
{
// Log lỗi nếu cần
}
finally
{
_applying = false;
StateHasChanged();
}
}
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();
});
}
}