Maui Blazor 中文社区 QQ群:645660665

Blazor组件自做九 : 使用JS隔离制作蓝牙打印组件(通用跨平台隔空打印小票/标签方案)

各位,好久不见,这段时间事情太多了,一直没空更新文章,sosososorry.

如果我告诉您网站能以安全和隐私保护的方式与附近的蓝牙设备进行通信,您会怎么想?如此一来,心率监测器、会唱歌的灯,甚至海龟都可以直接与网站交互了。到目前为止,仅有部分针对特定平台的应用可以实现与蓝牙设备的交互。Web Bluetooth API 旨在改变这一现状,以期将此功能赋予 Web 浏览器。

笔者所在的POS行业更是经常用到各种蓝牙小票机,蓝牙标签机,如此一来整个 Blazor ,app客户端都不用了, 很是利好啊! 话不多说,开整!

原文链接:https://www.cnblogs.com/densen2014/p/16749910.html

1. 运行截图

演示地址

连接设备

连接成功

打印标签

我测试这台打印机使用cpcl语言,测试的指令是

! 10 200 200 400 1
BEEP 1
PW 380
SETMAG 1 1
CENTER
TEXT 10 2 10 40 Micro Bar
TEXT 12 3 10 75 Blazor
TEXT 10 2 10 350 eMenu
B QR 30 150 M 2 U 7
MA,https://google.com
ENDQR
FORM
PRINT

2. 源码大家参考开源地址,仅摘抄要点和写组件使用方式

本组件主要是调用浏览器API实现基于浏览器的蓝牙功能,现代桌面和安卓移动端都支持.包括MAUI Blazor 😃

navigator.bluetooth.requestDevice 

2.1 关键js代码

    async function onConnect() {
        bluetoothDevice = null;

        if (opt.serviceUuid && opt.serviceUuid.toString().startsWith('0x')) {
            opt.serviceUuid = parseInt(opt.serviceUuid);
        }
        if (opt.characteristicUuid && opt.characteristicUuid.toString().startsWith('0x')) {
            opt.characteristicUuid = parseInt(opt.characteristicUuid);
        }

        try {
            logII('Requesting any Bluetooth Device...');
            var option = {
                //acceptAllDevices: true,
                //"filters": [{
                //    "namePrefix": "BMAU"
                //}],
                optionalServices: [opt.serviceUuid]
            }
            if (opt.namePrefix)
                option.filters = '[{ "namePrefix": "' + opt.namePrefix + '"}]';
            else
                option.acceptAllDevices = true;

            bluetoothDevice = await navigator.bluetooth.requestDevice(option);
            bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);
            await connect();
        } catch (error) {
            logErr('Argh! ' + error);
        }
    }

    async function connect() {
        exponentialBackoff(3 /* max retries */, 2 /* seconds delay */,
            async function toTry() {
                time('Connecting to Bluetooth Device... ');
                logII('Connecting to GATT Server...');
                logII('> Name:             ' + bluetoothDevice.name);
                logII('> Id:               ' + bluetoothDevice.id);
                opt.devicename = bluetoothDevice.name;
                opt.deviceID = bluetoothDevice.id;
                wrapper.invokeMethodAsync('GetResult', opt, "连接中...");
                wrapper.invokeMethodAsync('UpdateError', '');

                const server = await bluetoothDevice.gatt.connect();

                logII('Getting Services...');
                const services = await server.getPrimaryServices();
                logII('Getting Characteristics...');
                for (const service of services) {
                    logII('> Service: ' + service.uuid);
                    const characteristics = await service.getCharacteristics();

                    characteristics.forEach(characteristic => {
                        if (getSupportedPropertiesWrite(characteristic)) {
                            logII('>> Characteristic: ' + characteristic.uuid);
                            opt.characteristicUuid = characteristic.uuid;
                            myDescriptor = characteristic;
                            return;
                        }
                    });
                    if (opt.characteristicUuid) {
                        if (tools) tools.classList.remove('hidden');
                        if (btnDisconnect) btnDisconnect.classList.remove('hidden');
                        if (btnReconnect) btnReconnect.classList.remove('hidden');
                        wrapper.invokeMethodAsync('GetResult', opt, "已连接");
                        wrapper.invokeMethodAsync('UpdateError', '');

                        return;
                    }
                }
            },
            function success() {
                logII(`> Bluetooth Device ${bluetoothDevice.name} connected. `);
            },
            function fail() {
                time('Failed to reconnect.');
            });
    }

2.2 组件页面发送指令执行.

其中我设置的MaxChunk是100,如果安卓平台出现打印不全等莫名故障,可以把MaxChunk设置为20试试.具体请自行百度.

WriteChunk(commands);

    async function WriteChunk(string) {
        if (!myDescriptor && !serialWriter) {
            console.log(' > !myDescriptor serialWriter null!');
            return;
        }
        console.log('WriteChunk', string);
        var buffer = GBK.encode(string);
        var buffer1 = new Uint8Array(buffer).buffer;

        for (let i = 0, j = 0, length = buffer1.byteLength; i < length; i += opt.maxChunk, j++) {
            let subPackage = buffer1.slice(i, i + opt.maxChunk <= length ? (i + opt.maxChunk) : length);
            await _print(subPackage);
        }
    }

    async function _print(buffer) {
        try {
            logII('Setting Characteristic User Description...');
            console.log(buffer);
            if (myDescriptor)
                await myDescriptor.writeValue(buffer);
        } catch (error) {
            logErr('Argh! ' + error);
        }
        try {
            if (serialWriter) {
                await serialWriter.write(buffer);
            }
        } catch (error) {
            logErr('Argh! ' + error);
        }
    }

3. Demo工程页面

工程引用 BootstrapBlazor.Bluetooth

_Imports.razor加入一行
@using BootstrapBlazor.Components

3.1 新建PrintDemo.razor测试页

完整例子 https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtPrinterPage.razor

razor代码

<Printer @ref="printer" OnResult="OnResult" OnError="OnError" OnUpdateStatus="OnUpdateStatus" OnUpdateError="OnError" OnGetDevices="OnGetDevices" />
    <div @ref="printer.PrinterElement">
        <button data-action="btnConnect" class="btn btn-outline-primary">连接</button>
        <button data-action="btnDisconnect" class="btn btn-outline-danger">断开</button>
        @*<button data-action="btnReconnect" class="btn btn-outline-secondary">重连</button>*@
        <button data-action="tools" class="btn btn-outline-primary" @onclick="printer.Print">@printer.PrintButtonText</button>
</div>

<pre>@message</pre>
<pre style="color:green">@statusmessage</pre>
<pre style="color:red">@errmessage</pre>
<p/>

3.2 cs代码

@code{

    Printer printer { get; set; } = new Printer();

    /// <summary>
    /// 显示内置界面
    /// </summary>
    bool ShowUI { get; set; } = false;

    private string? message;
    private string? statusmessage;
    private string? errmessage;

    private Task OnResult(string message)
    {
        this.message = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnUpdateStatus(string message)
    {
        this.statusmessage = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnError(string message)
    {
        this.errmessage = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnGetDevices(List<string>? devices)
    {
        this.message = "";
        if (devices == null || devices.Count == 0) return Task.CompletedTask;
        this.message += $"已配对设备{devices.Count}:{Environment.NewLine}";
        devices.ForEach(a => this.message += $"   {a}{Environment.NewLine}");
        //this.message = this.message.Replace(Environment.NewLine, "<br/>");
        StateHasChanged();
        return Task.CompletedTask;
    } 

}

内置的其他两个组件介绍

4. 蓝牙设备电量组件

完整例子 https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtBatteryLevelPage.razor

<button class="btn btn-outline-secondary" @onclick="GetBatteryLevel ">查询电量</button>
<BatteryLevel @ref="batteryLevel" OnUpdateValue="OnUpdateValue" OnUpdateStatus="OnUpdateStatus" OnUpdateError="OnError" />
<br/>
<progress max="100" value="@value"> @value % </progress>
<pre>@message</pre>
<pre style="color:green">@statusmessage</pre>
<pre style="color:red">@errmessage</pre>

@code{

    BatteryLevel batteryLevel { get; set; } = new BatteryLevel();

    private decimal? value=0;
    private string? message;
    private string? statusmessage;
    private string? errmessage;

    private Task OnResult(string message)
    {
        this.message = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnUpdateValue(decimal value)
    {
        this.value = value;
        this.statusmessage = $"设备电量{value}%";
        StateHasChanged();
        return Task.CompletedTask;
    }


    private Task OnUpdateStatus(BluetoothDevice device)
    {
        this.statusmessage = device.Status;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnError(string message)
    {
        this.errmessage = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    public async void GetBatteryLevel()
    {
        await batteryLevel.GetBatteryLevel();
    }

}

5. 蓝牙设备电量组件

完整例子 https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtHeartratePage.razor

<button class="btn btn-outline-secondary" @onclick="GetHeartrate ">查询心率</button>
<button class="btn btn-outline-secondary" @onclick="StopHeartrate ">停止读取</button>
<Heartrate @ref="heartrate" OnUpdateValue="OnUpdateValue" OnUpdateStatus="OnUpdateStatus" OnUpdateError="OnError" />
<h2 style="color:red" data-action="heartrate"/>
<pre>@message</pre>
<pre style="color:green">@statusmessage</pre>
<pre style="color:red">@errmessage</pre>

@code{
    string heartrateIcon;// { get => (heartrateIcon == "&#10084;" ? "&hearts;" : "&#10084;"); }

    Heartrate heartrate { get; set; } = new Heartrate();

    private string? message;
    private int? value;
    private string? statusmessage;
    private string? errmessage;

    private Task OnResult(string message)
    {
        this.message = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnUpdateValue(int value)
    {
        this.value = value;
        this.statusmessage = $"心率{value}";
        StateHasChanged();
        return Task.CompletedTask;
    }


    private Task OnUpdateStatus(BluetoothDevice device)
    {
        this.statusmessage = device.Status;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnError(string message)
    {
        this.errmessage = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

    public async void GetHeartrate()
    {
        await heartrate.GetHeartrate();
    }

    public async void StopHeartrate()
    {
        await heartrate.StopHeartrate();
    }

}

Blazor Bluetooth & Printer 蓝牙和打印 组件

1. 蓝牙打印机 Printer

2. 蓝牙心率带 Heart Rate

3. 蓝牙设备电量 Battery Level

示例

https://www.blazor.zone/bluetooth

https://blazor.app1.es/bluetooth

使用方法:

  1. nuget包

    BootstrapBlazor.Bluetooth

  2. _Imports.razor 文件 或者页面添加 添加组件库引用

    @using BootstrapBlazor.Components

  3. Razor页面

    蓝牙打印机 BT Printer
    https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtPrinterPage.razor

    @using BootstrapBlazor.Components
    
    <Printer OnResult="OnResult" ShowUI="true" Debug="true" />
    
    

    蓝牙心率带
    https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtHeartratePage.razor

    @using BootstrapBlazor.Components
    
    <button class="btn btn-outline-secondary" @onclick="GetHeartrate ">查询心率</button>
    <button class="btn btn-outline-secondary" @onclick="StopHeartrate ">停止读取</button>
    <Heartrate @ref="heartrate" OnUpdateValue="OnUpdateValue" />
    <h2 style="color:red" data-action="heartrate"/>
    
    @code{
        Heartrate heartrate { get; set; } = new Heartrate();
        private int? value;
        
        private Task OnUpdateValue(int value)
        {
            this.value = value;
            StateHasChanged();
            return Task.CompletedTask;
        }
    }
    

    蓝牙设备电量
    https://github.com/densen2014/Densen.Extensions/blob/master/Demo/DemoShared/Pages/BtBatteryLevelPage.razor

    @using BootstrapBlazor.Components
    
    <button class="btn btn-outline-secondary" @onclick="GetBatteryLevel ">查询电量</button>
    <BatteryLevel @ref="batteryLevel" OnUpdateValue="OnUpdateValue" />
    <pre>@message</pre>
    
    @code{
        Heartrate heartrate { get; set; } = new Heartrate();
        private int? value;
        
        private Task OnUpdateValue(decimal value)
        {
            this.value = value;
            this.statusmessage = $"设备电量{value}%";
            StateHasChanged();
            return Task.CompletedTask;
        }
    }
    
    
  4. 更多信息请参考

    Bootstrap 风格的 Blazor UI 组件库
    基于 Bootstrap 样式库精心打造,并且额外增加了 100 多种常用的组件,为您快速开发项目带来非一般的感觉

    https://www.blazor.zone

    https://www.blazor.zone/bluetooth

其他资料

Web Bluetooth API 网站通过 JavaScript 与蓝牙设备进行通信 ,在Windows下水土不服解决办法

Bluetooth组件源码

Blazor组件自做系列

Blazor组件自做一 : 使用JS隔离封装viewerjs库
Blazor组件自做二 : 使用JS隔离制作手写签名组件
Blazor组件自做三 : 使用JS隔离封装ZXing扫码
Blazor组件自做四: 使用JS隔离封装signature_pad签名组件
Blazor组件自做五: 使用JS隔离封装Google地图
Blazor组件自做六: 使用JS隔离封装Baidu地图
Blazor组件自做七: 使用JS隔离制作定位/持续定位组件
Blazor组件自做八: 使用JS隔离封装屏幕键盘kioskboard.js组件
Blazor组件自做九: 使用JS隔离制作蓝牙打印组件(通用跨平台隔空打印小票/标签方案)

项目源码 Github | Gitee

Bluetooth组件源码

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

posted @ 2022-10-03 04:48  AlexChow  阅读(864)  评论(0编辑  收藏  举报