网页通过蓝牙发送指令控制传感器设备

Posted by Kaka Blog on August 4, 2022

环境准备

电脑操作系统:win10

打开蓝牙和其他设备,添加蓝牙或其他设备,添加设备,发现有BLE和SPP两种;

蓝牙协议分为两种,SPP协议和BLE(Bluetooth low energy)

  • BLE:web bluetooth api能扫描到,本文以BLE协议传输数据
  • SPP:蓝牙串口,需要输入PIN码

Web Bluetooth入门

WebBluetooth 是一种已在 Chrome 和三星 Internet中实施的新规范,它允许我们从浏览器直接与蓝牙低功耗设备进行通信。渐进式 Web 应用程序与 WebBluetooth 相结合,提供 Web 应用程序的安全性和便利性,并具有直接与设备对话的能力。

当我们谈论 WebBluetooth 时,我们谈论的是蓝牙规范的一个特定部分,称为 Generic Attribute Profile,它有一个非常明显的缩写 GATT。(显然,GAP 已经被采用了。)

在 GATT 的上下文中,我们不再谈论中央设备和外围设备,而是客户端和服务器。你的灯泡是服务器。这可能看起来违反直觉,但如果你仔细想想,它实际上是有道理的。灯泡提供服务,即光。就像浏览器连接到 Internet 上的服务器一样,您的手机或计算机是一个客户端,连接到灯泡中的 GATT 服务器。

每台服务器都提供一项或多项服务。每个服务都有一个或多个特征。每个特征都有一个可以读取或写入的值。与对象的属性不同,服务和特征不是由字符串标识的。每个服务和特性都有一个唯一的 UUID。

Chrome浏览器地址栏输入:chrome://bluetooth-internals,可查看蓝牙设备

使用WebBluetooth API

通过使用WebBluetooth API,我们只需要几行JavaScript就可以和蓝牙设备进行沟通。

1. 连接设备

let device = await navigator.bluetooth.requestDevice({
                filters: [{
                    namePrefix: 'JDY',
                }],
                //acceptAllDevices: true,
                optionalServices: [coyoteService]
            });

当我们调用此函数时,会弹出一个窗口,其中包含符合我们指定过滤器的设备列表。现在我们必须手动选择要连接的设备。

访问设备后,我们可以通过调用设备属性上的connect()函数连接到 GATT 服务器gatt并等待结果。

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

一旦我们有了服务器,我们就可以getPrimaryService()使用我们想要使用的服务的 UUID 作为参数调用服务器并等待结果。

const service = await server.getPrimaryService(coyoteService);

然后getCharacteristic()以特征的 UUID 作为参数调用服务并再次等待结果。现在有了可以用来写入和读取数据的特征:

const config = await service.getCharacteristic(configCharacteristic);

2. 写入数据

var hex = document.getElementById('battery_level_text').value;
			var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h){
				return parseInt(h,16)
			}));
			log(typedArray);
			config.writeValue(typedArray);

3. 数据返回

监听数据改变:

config.addEventListener('characteristicvaluechanged', handleCharacteristicValueChanged);

function handleCharacteristicValueChanged(event) {
		const valueDataView = event.target.value;
		const hexString = [...new Uint8Array(valueDataView.buffer)].map(b => {
			return b.toString(16).padStart(2, '0');
		}).join(' ');

		log(
			  '\n    (Hex) ' + hexString
		  );
	}

广播:

await config.startNotifications();

3. 断开连接

await device.gatt.disconnect();

完整代码

<button id="connect">连接蓝牙</button>
<button id="disconnect">断开蓝牙</button>
<form>
    <fieldset>
        <legend>Status</legend>
        <input id="battery_level_text" />
    </fieldset>

</form>

<button id="pair">发送指令</button>

<script>
    var ChromeSamples = {
        log: function () {

            var line = Array.prototype.slice.call(arguments).map(function (argument) {
                return typeof argument === 'string' ? argument : JSON.stringify(argument);
            }).join(' ');

            document.querySelector('#log').textContent += line + '\n';
            const elem = document.getElementById('log');
            elem.scrollTop = elem.scrollHeight;
        },

        clearLog: function () {
            document.querySelector('#log').textContent = '';
        },

        setStatus: function (status) {
            document.querySelector('#status').textContent = status;
        },

        setContent: function (newContent) {
            var content = document.querySelector('#content');
            while (content.hasChildNodes()) {
                content.removeChild(content.lastChild);
            }
            content.appendChild(newContent);
        }

    };
</script>

<h3>Live Output</h3>
<div id="output" class="output">
    <div id="content"></div>
    <div id="status"></div>
    <pre id="log"></pre>
</div>


<script>
    if (/Chrome\/(\d+\.\d+.\d+.\d+)/.test(navigator.userAgent)) {
        // Let's log a warning if the sample is not supposed to execute on this
        // version of Chrome.
        if (55 > parseInt(RegExp.$1)) {
            ChromeSamples.setStatus('Warning! Keep in mind this sample has been tested with Chrome ' + 55 + '.');
        }
    }
</script>


<script>
    const coyoteService = "0000ffe0-0000-1000-8000-00805f9b34fb";

    const configCharacteristic = "0000ffe1-0000-1000-8000-00805f9b34fb";

    let hasAcceptedWarning = false;
	let device = null;

    document.querySelector('button#pair').addEventListener('click', () => {
        if (isWebBluetoothEnabled()) {
            onButtonClick();
        }
    });
	
	document.querySelector('button#connect').addEventListener('click', () => {
        if (isWebBluetoothEnabled()) {
            ChromeSamples.clearLog();
            onButtonConnect();
        }
    });
	
	document.querySelector('button#disconnect').addEventListener('click', () => {
        if (isWebBluetoothEnabled()) {
            disconnect();
        }
    });
	
	async function onButtonConnect() {
        try {
            log('Requesting Bluetooth Device...');
            device = await navigator.bluetooth.requestDevice({
                filters: [{
                    namePrefix: 'JDY',
                }],
                //acceptAllDevices: true,
                optionalServices: [coyoteService]
            });
			log(device.name);
			log('Connecting to GATT Server...');
            const server = await device.gatt.connect();

            log('Getting Coyote Service...');
            const service = await server.getPrimaryService(coyoteService);

            log('Getting Config Characteristic...');
            const config = await service.getCharacteristic(configCharacteristic);
			config.addEventListener('characteristicvaluechanged', handleCharacteristicValueChanged);
        } catch (error) {
            log('Argh! ' + error);
            throw error;
        }
    }

    async function onButtonClick() {
        try {
            log('Connecting to GATT Server...');
            const server = await device.gatt.connect();

            log('Getting Coyote Service...');
            const service = await server.getPrimaryService(coyoteService);

            log('Getting Config Characteristic...');
            const config = await service.getCharacteristic(configCharacteristic);
			
			var hex = document.getElementById('battery_level_text').value;
			var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h){
				return parseInt(h,16)
			}));
			log(typedArray);
			config.writeValue(typedArray);
			await config.startNotifications();
			log('notifications have been started');
			
        } catch (error) {
            log('Argh! ' + error);
            throw error;
        }
    }
	
	function handleCharacteristicValueChanged(event) {
		const valueDataView = event.target.value;
		const hexString = [...new Uint8Array(valueDataView.buffer)].map(b => {
			return b.toString(16).padStart(2, '0');
		}).join(' ');

		log(
			  '\n    (Hex) ' + hexString
		  );
	}
	
	async function disconnect() {
		try {
			await device.gatt.disconnect();
			log('断开连接');
		} catch {}
		device = null;
    }
</script>


<script>
    log = ChromeSamples.log;


    function isWebBluetoothEnabled() {
        if (navigator.bluetooth) {
            return true;
        } else {
            ChromeSamples.setStatus('Web Bluetooth API is not available.\n' +
                'Please make sure the "Experimental Web Platform features" flag is enabled.');
            return false;
        }
    }
</script>

测试

1、开启蓝牙设备,点击连接蓝牙按钮,选择蓝牙,配对;

2、输入指令:ff5508000208090441ca09,打开像素灯;

3、输入指令:ff55050002080901,关闭像素灯。

4、点击断开蓝牙,蓝牙连接断开。

参考