@page "/serial" @rendermode InteractiveServer @implements IDisposable @using IOModuleTestBlazor.Services @inject ISerialPortService SerialService @inject IJSRuntime JS Serial Terminal

Serial Terminal

@* ── Connection Bar ──────────────────────────────────────────────────── *@
@if (SerialService.IsOpen) { ● Connected @SerialService.PortName 115200 baud } else { ● Disconnected } @if (SerialService.IsOpen) { } else { } @if (_connectError is not null) {
Connect failed: @_connectError
}
@* ── Terminal Output ─────────────────────────────────────────────────── *@
Output
@GetTerminalText()
@* ── Command Input ───────────────────────────────────────────────────── *@
@* ── Quick Commands ──────────────────────────────────────────── *@
Quick: @foreach (var (label, cmd) in _quickCommands) { }
@code { private string _selectedPort = string.Empty; private string _commandInput = string.Empty; private string? _connectError; private List _availablePorts = new(); private List _displayLines = new(); private IJSObjectReference? _jsModule; private bool _scrollPending = false; private static readonly (string Label, string Cmd)[] _quickCommands = [ ("?", "?"), ("r 0000 4", "r 0000 4"), ("r 0000 10", "r 0000 10"), ("w 0000 AB CD", "w 0000 AB CD"), ("w 0000 FF FF", "w 0000 FF FF"), ]; protected override void OnInitialized() { RefreshPorts(); SerialService.DataReceived += OnDataReceived; _displayLines = SerialService.GetLines().ToList(); } private void RefreshPorts() { _availablePorts = SerialService.GetPortNames().ToList(); if (_availablePorts.Count > 0 && string.IsNullOrEmpty(_selectedPort)) _selectedPort = _availablePorts[0]; } private async Task Connect() { _connectError = null; try { await Task.Run(() => SerialService.Open(_selectedPort, 115200)); } catch (Exception ex) { _connectError = ex.Message; } } private async Task Disconnect() { await Task.Run(() => SerialService.Close()); } private void SendCommand() { if (string.IsNullOrWhiteSpace(_commandInput)) return; SerialService.WriteLine(_commandInput.Trim()); _commandInput = string.Empty; RefreshLines(); } private void SendQuickCommand(string cmd) { SerialService.WriteLine(cmd); RefreshLines(); } private void OnKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter") SendCommand(); } private void OnDataReceived() { InvokeAsync(() => { RefreshLines(); _scrollPending = true; StateHasChanged(); }); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) _jsModule = await JS.InvokeAsync( "import", "./Components/Pages/SerialTerminal.razor.js"); if (_scrollPending && _jsModule is not null) { _scrollPending = false; await _jsModule.InvokeVoidAsync("scrollToBottom", "terminal-output"); } } private void RefreshLines() => _displayLines = SerialService.GetLines().ToList(); private void ClearTerminal() { SerialService.ClearLines(); _displayLines.Clear(); StateHasChanged(); } private string GetTerminalText() => string.Join("\n", _displayLines); public void Dispose() { SerialService.DataReceived -= OnDataReceived; _jsModule?.DisposeAsync(); } }