-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- @if (Flag)
- {
-
-
-
-
-
-
-
- @if (!IsConnected)
- {
-
- }
- else
- {
-
- }
-
-
-
-
- @if (WebSerial != null && IsConnected)
- {
-
-
-
- RING
-
-
-
- DSR
-
-
-
- CTS
-
-
-
- DCD
-
-
-
- }
-
-
- }
- else
- {
-
-
-
- }
-
-
+
1. 服务注入
-
- 注意:本例子改变设置要点关闭再重新应用
- @_message
- @_statusMessage
- @_errorMessage
-
- 附件: Arduino源码示例
-
-
+
[Inject]
+[NotNull]
+private ISerialService? SerialService { get; set; }
+
2. 申请串口权限
+
调用 SerialService 实例方法 GetPort 即可,通过 IsSupport 进行浏览器是否支持判断
+
+
_serialPort = await SerialService.GetPort();
+if (SerialService.IsSupport == false)
+{
+ await ToastService.Error(Localizer["NotSupportSerialTitle"], Localizer["NotSupportSerialContent"]);
+}
+
+
3. 打开串口
+
+
+ - 如果需要读取数据,请先设置
ISerialPort 实例 DataReceive 参数
+ - 调用
ISerialPort 实例方法 Open 打开串口,可通过 SerialPortOptions 参数设置 波特率 等信息
+
+
+
private async Task OpenPort()
+{
+ if (_serialPort != null)
+ {
+ _serialPort.DataReceive = async data =>
+ {
+ _messages.Add(new ConsoleMessageItem()
+ {
+ IsHtml = true,
+ Message = $"{DateTime.Now}: --> Text: {Encoding.ASCII.GetString(data)} HEX: {Convert.ToHexString(data)}"
+ });
+ await InvokeAsync(StateHasChanged);
+ };
+ await _serialPort.Open(_serialOptions);
+ }
+}
-
+
4. 关闭串口
-
+
调用 ISerialPort 实例方法 Close 关闭串口,请注意路由切换时,请调用其 DisposeAsync 方法释放资源
+
private async Task ClosePort()
+{
+ if (_serialPort != null)
+ {
+ await _serialPort.Close();
+ }
+}
diff --git a/src/BootstrapBlazor.Server/Components/Samples/WebSerials.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/WebSerials.razor.cs
index c4df7f9e563..26264bee7ed 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/WebSerials.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Samples/WebSerials.razor.cs
@@ -2,319 +2,205 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
-using System.ComponentModel;
+using System.Text;
namespace BootstrapBlazor.Server.Components.Samples;
///
-/// WebSerials
+/// WebSerials 组件
///
-public partial class WebSerials
+public partial class WebSerials : IAsyncDisposable
{
+ private string _sendData = "";
+ private int _sendInterval = 1000;
+ private bool _appendCRLF;
+ private bool _isHEX;
+ private bool _isLoop;
+ private readonly ConsoleMessageCollection _messages = new(8);
+
+ private readonly List
_baudRateList =
+ [
+ new("300", "300"),
+ new("600", "600"),
+ new("1200", "1200"),
+ new("2400", "2400"),
+ new("4800", "4800"),
+ new("9600", "9600"),
+ new("14400", "14400"),
+ new("19200", "19200"),
+ ];
- private string? _message;
- private string? _statusMessage;
- private string? _errorMessage;
- private readonly WebSerialOptions options = new() { BaudRate = 115200, AutoGetSignals = true };
+ private readonly List _bufferSizes =
+ [
+ new("255", "255"),
+ new("1024", "1024")
+ ];
- [NotNull]
- private IEnumerable BaudRateList { get; set; } = ListToSelectedItem();
+ private readonly List _dataBits = [new("7", "7"), new("8", "8")];
- [DisplayName("波特率")]
- private int SelectedBaudRate { get; set; } = 115200;
+ private readonly List _stopBits = [new("1", "1"), new("2", "2")];
- private bool Flag { get; set; }
+ [Inject, NotNull]
+ private ISerialService? SerialService { get; set; }
- private bool IsConnected { get; set; }
+ [Inject, NotNull]
+ private ToastService? ToastService { get; set; }
- ///
- /// 收到的信号数据
- ///
- public WebSerialSignals Signals { get; set; } = new WebSerialSignals();
+ private ISerialPort? _serialPort;
+
+ private readonly SerialPortOptions _serialOptions = new();
- [NotNull]
- private WebSerial? WebSerial { get; set; }
+ private bool CheckOpen => _serialPort is not { IsOpen: false };
- private Task OnReceive(string? message)
+ private bool CheckClose => _serialPort is not { IsOpen: true };
+
+ private async Task GetPort()
{
- _message = $"{DateTime.Now:hh:mm:ss} 收到数据: {message}{Environment.NewLine}" + _message;
- StateHasChanged();
- return Task.CompletedTask;
+ _serialPort = await SerialService.GetPort();
+ if (SerialService.IsSupport == false)
+ {
+ await ToastService.Error(Localizer["NotSupportSerialTitle"], Localizer["NotSupportSerialContent"]);
+ }
+ }
+
+ private async Task OpenPort()
+ {
+ if (_serialPort != null)
+ {
+ _serialPort.DataReceive = async data =>
+ {
+ _messages.Add(new ConsoleMessageItem()
+ {
+ IsHtml = true,
+ Message = $"{DateTime.Now}: -->
Text: {Encoding.ASCII.GetString(data)}
HEX: {Convert.ToHexString(data)}"
+ });
+ await InvokeAsync(StateHasChanged);
+ };
+ await _serialPort.Open(_serialOptions);
+
+ if (_serialPort.IsOpen == false)
+ {
+ await ToastService.Error(Localizer["OpenPortSerialTitle"], Localizer["OpenPortSerialContent"]);
+ }
+ }
}
- private Task OnSignals(WebSerialSignals? signals)
+ private async Task ClosePort()
{
- if (signals is null) return Task.CompletedTask;
+ if (_serialPort != null)
+ {
+ await _serialPort.Close();
+ }
+ }
- Signals = signals;
+ private CancellationTokenSource? _loopSendTokenSource;
- if (!options.AutoGetSignals)
+ private async Task Write()
+ {
+ if (_serialPort == null)
{
- // 仅在不自动获取信号时才显示
- _message = $"{DateTime.Now:hh:mm:ss} 收到信号数据: {Environment.NewLine}" +
- $"RING: {signals.RING}{Environment.NewLine}" +
- $"DSR: {signals.DSR}{Environment.NewLine}" +
- $"CTS: {signals.CTS}{Environment.NewLine}" +
- $"DCD: {signals.DCD}{Environment.NewLine}" +
- $"{_message}{Environment.NewLine}";
+ return;
}
- StateHasChanged();
- return Task.CompletedTask;
+ if (_isLoop)
+ {
+ _loopSendTokenSource ??= new CancellationTokenSource();
+ while (_isLoop && _loopSendTokenSource is { IsCancellationRequested: false } && _sendInterval > 500)
+ {
+ try
+ {
+ await InternalSend(_serialPort);
+ await Task.Delay(_sendInterval, _loopSendTokenSource.Token);
+ }
+ catch { }
+ }
+ }
+ else
+ {
+ await InternalSend(_serialPort);
+ }
}
- private Task OnConnect(bool flag)
+ private async Task InternalSend(ISerialPort serialPort)
{
- IsConnected = flag;
- if (flag)
+ var data = _sendData;
+ if (_appendCRLF)
{
- _message = null;
- _statusMessage = null;
- _errorMessage = null;
+ data += "\r\n";
}
- StateHasChanged();
- return Task.CompletedTask;
+ var buffer = _isHEX
+ ? ConvertToHex(data)
+ : Encoding.ASCII.GetBytes(data);
+ await serialPort.Write(buffer);
}
- private Task OnLog(string message)
+ private static byte[] ConvertToHex(string data)
{
- _statusMessage = message;
- StateHasChanged();
- return Task.CompletedTask;
+ var ret = new List();
+ for (int i = 0; i < data.Length;)
+ {
+ if (i + 2 <= data.Length)
+ {
+ var seg = data.Substring(i, 2);
+ ret.Add(Convert.ToByte(seg, 16));
+ }
+ i = i + 2;
+ }
+ return [.. ret];
}
- private Task OnError(string message)
+ private SerialPortSignals? _signals;
+
+ private bool _ring => _signals?.RING ?? false;
+
+ private bool _dsr => _signals?.DSR ?? false;
+
+ private bool _dcd => _signals?.DCD ?? false;
+
+ private bool _cts => _signals?.CTS ?? false;
+
+ private async Task GetSignals()
{
- _errorMessage = message;
- StateHasChanged();
- return Task.CompletedTask;
+ if (_serialPort == null)
+ {
+ return;
+ }
+
+ _signals = await _serialPort.GetSignals();
}
- private static IEnumerable ListToSelectedItem()
+ private SerialPortUsbInfo? _usbInfo;
+
+ private async Task GetInfo()
{
- foreach (var item in WebSerialOptions.BaudRateList)
+ if (_serialPort == null)
{
- yield return new SelectedItem(item.ToString(), item.ToString());
+ return;
}
+
+ _usbInfo = await _serialPort.GetUsbInfo();
}
- private void OnApply()
+ private async ValueTask DisposeAsync(bool disposing)
{
- options.BaudRate = SelectedBaudRate;
- Flag = !Flag;
+ if (disposing)
+ if (_loopSendTokenSource != null)
+ {
+ _loopSendTokenSource.Cancel();
+ _loopSendTokenSource = null;
+ }
+ if (_serialPort != null)
+ {
+ await _serialPort.DisposeAsync();
+ }
}
///
- /// 获得属性方法
+ ///
///
- ///
- private static AttributeItem[] GetAttributes() =>
- [
- new()
- {
- Name = "OnReceive",
- Description = "收到数据回调方法",
- Type = "Func?",
- ValueList = "-",
- DefaultValue = "-"
- },
- new AttributeItem() {
- Name = "OnSignals",
- Description = "收到信号数据回调方法",
- Type = "Func?",
- ValueList = "-",
- DefaultValue = "-"
- },
- new()
- {
- Name = "OnLog",
- Description = "Log回调方法",
- Type = "Func?",
- ValueList = "-",
- DefaultValue = "-"
- },
- new()
- {
- Name = "OnError",
- Description = "错误回调方法",
- Type = "Func?",
- ValueList = "-",
- DefaultValue = "-"
- },
- new()
- {
- Name = "Element",
- Description = "UI界面元素的引用对象,为空则使用整个页面",
- Type = "ElementReference",
- ValueList = "-",
- DefaultValue = "-"
- },
- new()
- {
- Name = "ConnectBtnTitle",
- Description = "获得/设置 连接按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "连接"
- },
- new()
- {
- Name = "DisconnectBtnTitle",
- Description = "获得/设置 断开连接按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "断开连接"
- },
- new()
- {
- Name = "WriteBtnTitle",
- Description = "获得/设置 写入按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "写入"
- },
- new()
- {
- Name = "ShowUI",
- Description = "获得/设置 显示内置 UI",
- Type = "bool",
- ValueList = "True|False",
- DefaultValue = "False"
- },
- new()
- {
- Name = "Debug",
- Description = "获得/设置 显示 log",
- Type = "bool",
- ValueList = "True|False",
- DefaultValue = "False"
- }
- ];
-
- /// s
- /// 获得WebSerialOptions属性方法
- ///
- ///
- private static AttributeItem[] GetWebSerialOptionsAttributes() =>
- [
- new()
- {
- Name = "BaudRate",
- Description = "波特率",
- Type = "int",
- ValueList = "-",
- DefaultValue = "9600"
- },
- new()
- {
- Name = "DataBits",
- Description = "数据位",
- Type = "int",
- ValueList = "7|8",
- DefaultValue = "8"
- },
- new()
- {
- Name = "StopBits",
- Description = "停止位",
- Type = "int",
- ValueList = "1|2",
- DefaultValue = "1"
- },
- new()
- {
- Name = "ParityType",
- Description = "流控制",
- Type = "WebSerialFlowControlType",
- ValueList = "none|even|odd",
- DefaultValue = "none"
- },
- new()
- {
- Name = "BufferSize",
- Description = "读写缓冲区",
- Type = "int",
- ValueList = "-",
- DefaultValue = "255"
- },
- new()
- {
- Name = "FlowControlType",
- Description = "校验",
- Type = "WebSerialParityType",
- ValueList = "none|hardware",
- DefaultValue = "none"
- },
- new()
- {
- Name =nameof(WebSerialOptions.InputWithHex),
- Description = "HEX发送",
- Type = "bool",
- ValueList = "-",
- DefaultValue = "false"
- },
- new()
- {
- Name =nameof(WebSerialOptions.OutputInHex),
- Description = "HEX接收",
- Type = "bool",
- ValueList = "-",
- DefaultValue ="false"
- },
- new()
- {
- Name =nameof(WebSerialOptions.AutoConnect),
- Description = "自动连接设备",
- Type = "bool",
- ValueList = "-",
- DefaultValue ="true"
- },
- new()
- {
- Name =nameof(WebSerialOptions.AutoFrameBreakType),
- Description = "自动断帧方式",
- Type = "AutoFrameBreakType",
- ValueList = "-",
- DefaultValue ="Character"
- },
- new(){
- Name =nameof(WebSerialOptions.FrameBreakChar),
- Description = "断帧字符",
- Type = "string",
- ValueList = "-",
- DefaultValue ="\\n"
- },
- new()
- {
- Name = nameof(WebSerialOptions.ConnectBtnTitle),
- Description = "获得/设置 连接按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "连接"
- },
- new()
- {
- Name = nameof(WebSerialOptions.DisconnectBtnTitle),
- Description = "获得/设置 断开连接按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "连接"
- },
- new()
- {
- Name = nameof(WebSerialOptions.WriteBtnTitle),
- Description = "获得/设置 写入按钮文本",
- Type = "string",
- ValueList = "",
- DefaultValue = "写入"
- },
- new()
- {
- Name = nameof(WebSerialOptions.AutoGetSignals),
- Description = "获得/设置 自动检查状态",
- Type = "bool",
- ValueList = "-",
- DefaultValue ="false"
- }
- ];
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsync(true);
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs
index 2a88f3f71f4..ef631fb4efc 100644
--- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs
+++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs
@@ -755,11 +755,6 @@ void AddData(DemoMenuItem item)
{
Text = Localizer["Waterfall"],
Url = "tutorials/waterfall"
- },
- new()
- {
- Text = Localizer["WebSerial"],
- Url = "web-serial"
}
};
AddBadge(item);
@@ -1491,6 +1486,12 @@ void AddServices(DemoMenuItem item)
Url = "title"
},
new()
+ {
+ IsNew = true,
+ Text = Localizer["WebSerial"],
+ Url = "web-serial"
+ },
+ new()
{
Text = Localizer["ZipArchive"],
Url = "zip-archive"
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
index 4e191485e95..e14050c5a3f 100644
--- a/src/BootstrapBlazor.Server/Locales/en-US.json
+++ b/src/BootstrapBlazor.Server/Locales/en-US.json
@@ -1687,7 +1687,7 @@
"PdfReaderText": "PDF Reader",
"VideoPlayerText": "VideoPlayer",
"FileViewerText": "FileViewer",
- "WebSerialText": "WebSerial",
+ "WebSerialText": "SerialService",
"MindMapText": "MindMap",
"WebSpeechText": "WebSpeech",
"ImageCropperText": "ImageCropper",
@@ -4746,7 +4746,7 @@
"CountButton": "CountButton",
"Splitting": "Splitting",
"QueryBuilder": "QueryBuilder",
- "WebSerial": "Web Serial",
+ "SerialService": "ISerialService",
"MindMap": "Mind Map",
"Marquee": "Marquee",
"Stack": "Stack",
@@ -6298,14 +6298,34 @@
"BootstrapBlazor.Server.Components.Samples.WebSerials": {
"WebSerialTitle": "Web Serial",
"WebSerialIntro": "Serial port is a widely used communication protocol that enables data transmission between computers",
- "WebSerialDescription": "allow websites to communicate with peripherals connected to a user's computer. It provides the ability to connect to devices that are required by the operating system to communicate via the serial API, see WebUSB,or WebHID.",
- "WebSerialNormalIntro": "read and write serial devices",
+ "WebSerialDescription": "allow websites to communicate with peripherals connected to a user's computer. It provides the ability to connect to devices that are required by the operating system to communicate via the serial API",
"WebSerialNormalTitle": "Basic usage",
- "ConnectButtonText": "Connect",
+ "WebSerialNormalIntro": "By calling the ISerialService service, you can connect, open, close, read and write to the serial port device.",
+ "BaudRateText": "BaudRate",
+ "DataBitsText": "DataBits",
+ "StopBitsText": "StopBits",
+ "ParityTypeText": "ParityType",
+ "BufferSizeText": "BufferSize",
+ "FlowControlTypeText": "FlowControlType",
+ "RequestPortText": "Request",
+ "OpenPortText": "Open",
+ "ClosePortText": "Close",
"WriteButtonText": "Write",
- "DisconnectButtonText": "Disonnect",
- "ApplyButtonText": "Apply",
- "WebSerialOptionsText": "WebSerialOptions"
+ "WriteDataText": "Send Data",
+ "ReadDataText": "Receive Data",
+ "CRLFText": "CRLF",
+ "HEXText": "HEX",
+ "LoopSendText": "Loop",
+ "LoopIntervalText": "Interval(ms)",
+ "WebSerialTipsLi1": "This feature is available only in secure contexts (HTTPS)",
+ "WebSerialTipsLi2": "This is an experimental technology Check the Browser compatibility table carefully before using this in production",
+ "WebSerialTipsTitle": "Note: The ISerialPort interface instance inherits IAsyncDisposable. When switching routes, you need to release its resources by calling its DisposeAsync",
+ "NotSupportSerialTitle": "Get Port",
+ "NotSupportSerialContent": "The current browser does not support serial port operations. Please change to Edge or Chrome browser.",
+ "OpenPortSerialTitle": "Open Port",
+ "OpenPortSerialContent": "Failed to open the serial port",
+ "GetSignalsButtonText": "Signals",
+ "GetInfoButtonText": "Infos"
},
"BootstrapBlazor.Server.Components.Samples.MindMaps": {
"MindMapTitle": "Mind Map",
diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
index 182a0ccee92..95c943a0580 100644
--- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
+++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
@@ -1687,7 +1687,7 @@
"PdfReaderText": "PDF阅读器 PDF Reader",
"VideoPlayerText": "视频播放器 VideoPlayer",
"FileViewerText": "文件预览器 FileViewer",
- "WebSerialText": "串口读写 WebSerial",
+ "WebSerialText": "串口服务 ISerialService",
"MindMapText": "思维导图 Mind Map",
"WebSpeechText": "语音识别/合成 WebSpeech",
"ImageCropperText": "图像裁剪 ImageCropper",
@@ -4746,7 +4746,7 @@
"CountButton": "倒计时按钮 CountButton",
"Splitting": "动画组件 Splitting",
"QueryBuilder": "条件生成器 QueryBuilder",
- "WebSerial": "串口读写 WebSerial",
+ "WebSerial": "串口服务 ISerialService",
"MindMap": "思维导图 MindMap",
"Marquee": "文字滚动 Marquee",
"Stack": "堆叠布局 Stack",
@@ -6298,14 +6298,34 @@
"BootstrapBlazor.Server.Components.Samples.WebSerials": {
"WebSerialTitle": "WebSerial 串口读写",
"WebSerialIntro": "串口是一种具有广泛使用的通信协议,它可以实现计算机之间的数据传输",
- "WebSerialDescription": "允许网站与连接到用户计算机的外围设备进行通信。它提供了连接到操作系统需要通过串行 API 进行通信的设备的能力,参考 WebUSB,或 WebHID。",
- "WebSerialNormalIntro": "读写串口设备",
+ "WebSerialDescription": "允许网站与连接到用户计算机的外围设备进行通信。它提供了连接到操作系统需要通过串行 API 进行通信的设备的能力",
"WebSerialNormalTitle": "基本用法",
- "ConnectButtonText": "连接",
+ "WebSerialNormalIntro": "通过调用 ISerialService 服务,对串口设备进行连接、打开、关闭、读写操作",
+ "BaudRateText": "波特率",
+ "DataBitsText": "数据位",
+ "StopBitsText": "停止位",
+ "ParityTypeText": "校验位",
+ "BufferSizeText": "缓冲区",
+ "FlowControlTypeText": "流控制",
+ "RequestPortText": "选择",
+ "OpenPortText": "打开",
+ "ClosePortText": "关闭",
"WriteButtonText": "写入",
- "DisconnectButtonText": "断开",
- "ApplyButtonText": "应用",
- "WebSerialOptionsText": "WebSerialOptions"
+ "WriteDataText": "发送数据",
+ "ReadDataText": "接收数据",
+ "CRLFText": "末尾加回车换行",
+ "HEXText": "HEX 发送",
+ "LoopSendText": "循环发送",
+ "LoopIntervalText": "发送间隔(ms)",
+ "WebSerialTipsLi1": "该功能仅在部分或所有支持浏览器的安全上下文(HTTPS)中可用",
+ "WebSerialTipsLi2": "这是一项实验性技术,在生产中使用之前请仔细,检查 浏览器兼容性表",
+ "WebSerialTipsTitle": "注意:ISerialPort 接口实例继承 IAsyncDisposable 路由切换时需要对其进行资源释放,调用其 DisposeAsync 即可",
+ "NotSupportSerialTitle": "申请串口权限",
+ "NotSupportSerialContent": "当前浏览器不支持串口操作,请更换 Edge 或者 Chrome 浏览器",
+ "OpenPortSerialTitle": "打开串口操作",
+ "OpenPortSerialContent": "打开串口失败",
+ "GetSignalsButtonText": "串口参数",
+ "GetInfoButtonText": "USB 信息"
},
"BootstrapBlazor.Server.Components.Samples.MindMaps": {
"MindMapTitle": "Mind Map 思维导图",
diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj
index af2de39c1ab..53074d70cd1 100644
--- a/src/BootstrapBlazor/BootstrapBlazor.csproj
+++ b/src/BootstrapBlazor/BootstrapBlazor.csproj
@@ -1,7 +1,7 @@
- 8.10.4-beta02
+ 8.10.4-beta03
diff --git a/src/BootstrapBlazor/Components/Console/Console.razor b/src/BootstrapBlazor/Components/Console/Console.razor
index 80761f19807..e8257a24540 100644
--- a/src/BootstrapBlazor/Components/Console/Console.razor
+++ b/src/BootstrapBlazor/Components/Console/Console.razor
@@ -3,14 +3,14 @@
@attribute [BootstrapModuleAutoLoader]
-