• 欢迎光临~

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

开发技术 开发技术 2022-10-03 次浏览

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

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

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

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

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

1. 运行截图

演示地址

连接设备

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

连接成功

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

打印标签

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

我测试这台打印机使用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;
    } 

}

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

内置的其他两个组件介绍

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

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();
    }

}

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

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组件自做九 : 使用JS隔离制作蓝牙打印组件(通用跨平台隔空打印小票/标签方案)

其他资料

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 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。

喜欢 (0)