【NUCLEO-WB09KE】PC机与蓝牙设备通讯与web编程
<p><strong>1、测试介绍</strong></p><p>本测试 使用开发板模拟心率仪,用来验证连接性、BLE、BLE 协议、BLE 配对、BLE 配置文件等项目。其中的程序可以作为BLE的编程框架来使用。测试使用NUCLEO-WB09KE作为BLE“服务端”,客户端使用web页面的javascript程序作为“客户端”。本测试最开始使用移动客户端作为测试工具,最近我在学习scratch编程时,发现现在的node.js也可以支持BLE的API,并且可以很好的使用web浏览器作为客户端向web网站发送测试数据,于是就改用javascript作为客户端了。</p>
<p><strong>2、客户端介绍</strong></p>
<p>客户端的源码是我从<a href="https://github.com/WebBluetoothCG/demos" target="_blank">https://github.com/WebBluetoothCG/demos</a>项目下载的。心率仪作为BLE的标准设备。通讯和数据格式已经被固定格式化了。只要提供规定的API函数就可以使用任何的客户端程序和设备链接。</p>
<p>首先是定义一个index.html文件作为页面</p>
<pre>
<code class="language-html"><!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Heart Rate Sensor Demo</title>
<meta name="description" content="Monitor a heart rate sensor with a Web Bluetooth app.">
<link rel="icon" sizes="192x192" href="../favicon.png">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="container">
<div id="statusText">GET &#x2764;</div>
<canvas id="waves"></canvas>
</div>
<script src="heartRateSensor.js"></script>
<script src="app.js"></script>
</body>
</html>
</code></pre>
<p>程序主要分为app.js,本程序主要是用来在画布“canvas”上面显示数据和界面交互。画布的id="waves"。</p>
<pre>
<code class="language-javascript">var canvas = document.querySelector('canvas');
var statusText = document.querySelector('#statusText');
statusText.addEventListener('click', function() {
statusText.textContent = 'Breathe...';
heartRates = [];
heartRateSensor.connect()
.then(() => heartRateSensor.startNotificationsHeartRateMeasurement().then(handleHeartRateMeasurement))
.catch(error => {
statusText.textContent = error;
});
});
function handleHeartRateMeasurement(heartRateMeasurement) {
heartRateMeasurement.addEventListener('characteristicvaluechanged', event => {
var heartRateMeasurement = heartRateSensor.parseHeartRate(event.target.value);
statusText.innerHTML = heartRateMeasurement.heartRate + ' &#x2764;';
heartRates.push(heartRateMeasurement.heartRate);
drawWaves();
});
}
var heartRates = [];
var mode = 'bar';
canvas.addEventListener('click', event => {
mode = mode === 'bar' ? 'line' : 'bar';
drawWaves();
});
function drawWaves() {
requestAnimationFrame(() => {
canvas.width = parseInt(getComputedStyle(canvas).width.slice(0, -2)) * devicePixelRatio;
canvas.height = parseInt(getComputedStyle(canvas).height.slice(0, -2)) * devicePixelRatio;
var context = canvas.getContext('2d');
var margin = 2;
var max = Math.max(0, Math.round(canvas.width / 11));
var offset = Math.max(0, heartRates.length - max);
context.clearRect(0, 0, canvas.width, canvas.height);
context.strokeStyle = '#00796B';
if (mode === 'bar') {
for (var i = 0; i < Math.max(heartRates.length, max); i++) {
var barHeight = Math.round(heartRates * canvas.height / 200);
context.rect(11 * i + margin, canvas.height - barHeight, margin, Math.max(0, barHeight - margin));
context.stroke();
}
} else if (mode === 'line') {
context.beginPath();
context.lineWidth = 6;
context.lineJoin = 'round';
context.shadowBlur = '1';
context.shadowColor = '#333';
context.shadowOffsetY = '1';
for (var i = 0; i < Math.max(heartRates.length, max); i++) {
var lineHeight = Math.round(heartRates * canvas.height / 200);
if (i === 0) {
context.moveTo(11 * i, canvas.height - lineHeight);
} else {
context.lineTo(11 * i, canvas.height - lineHeight);
}
context.stroke();
}
}
});
}
window.onresize = drawWaves;
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
drawWaves();
}
});
</code></pre>
<p>heartRateSensor.js文件,js使用BLE通过API从设备中调测试数据。设备BLE被定义在navigator.bluetooth.requestDevice中,函数将返回一个设备class,成功后装配该对象。</p>
<pre>
<code class="language-javascript">(function() {
'use strict';
class HeartRateSensor {
constructor() {
this.device = null;
this.server = null;
this._characteristics = new Map();
}
connect() {
return navigator.bluetooth.requestDevice({filters:[{services:[ 'heart_rate' ]}]})
.then(device => {
this.device = device;
return device.gatt.connect();
})
.then(server => {
this.server = server;
return server.getPrimaryService('heart_rate');
})
.then(service => {
return this._cacheCharacteristic(service, 'heart_rate_measurement');
})
}
/* Heart Rate Service */
startNotificationsHeartRateMeasurement() {
return this._startNotifications('heart_rate_measurement');
}
stopNotificationsHeartRateMeasurement() {
return this._stopNotifications('heart_rate_measurement');
}
parseHeartRate(value) {
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
value = value.buffer ? value : new DataView(value);
let flags = value.getUint8(0);
let rate16Bits = flags & 0x1;
let result = {};
let index = 1;
if (rate16Bits) {
result.heartRate = value.getUint16(index, /*littleEndian=*/true);
index += 2;
} else {
result.heartRate = value.getUint8(index);
index += 1;
}
let contactDetected = flags & 0x2;
let contactSensorPresent = flags & 0x4;
if (contactSensorPresent) {
result.contactDetected = !!contactDetected;
}
let energyPresent = flags & 0x8;
if (energyPresent) {
result.energyExpended = value.getUint16(index, /*littleEndian=*/true);
index += 2;
}
let rrIntervalPresent = flags & 0x10;
if (rrIntervalPresent) {
let rrIntervals = [];
for (; index + 1 < value.byteLength; index += 2) {
rrIntervals.push(value.getUint16(index, /*littleEndian=*/true));
}
result.rrIntervals = rrIntervals;
}
return result;
}
/* Utils */
_cacheCharacteristic(service, characteristicUuid) {
return service.getCharacteristic(characteristicUuid)
.then(characteristic => {
this._characteristics.set(characteristicUuid, characteristic);
});
}
_readCharacteristicValue(characteristicUuid) {
let characteristic = this._characteristics.get(characteristicUuid);
return characteristic.readValue()
.then(value => {
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
value = value.buffer ? value : new DataView(value);
return value;
});
}
_writeCharacteristicValue(characteristicUuid, value) {
let characteristic = this._characteristics.get(characteristicUuid);
return characteristic.writeValue(value);
}
_startNotifications(characteristicUuid) {
let characteristic = this._characteristics.get(characteristicUuid);
// Returns characteristic to set up characteristicvaluechanged event
// handlers in the resolved promise.
return characteristic.startNotifications()
.then(() => characteristic);
}
_stopNotifications(characteristicUuid) {
let characteristic = this._characteristics.get(characteristicUuid);
// Returns characteristic to remove characteristicvaluechanged event
// handlers in the resolved promise.
return characteristic.stopNotifications()
.then(() => characteristic);
}
}
window.heartRateSensor = new HeartRateSensor();
})();
</code></pre>
<p>当收到数据后调用相关的事件函数。BLE的API请参考:<a href="https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice" target="_blank">https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice</a></p>
<p><strong>3、测试过程</strong></p>
<p>(1)首先使用STM32Cub下载BLE_HeartRate程序到本地磁盘。</p>
<p> (2)使用编译工具烧写到开发板。</p>
<p> 我使用的是keil工具烧写到开发板。烧写完成后,开发板就可以模拟心率仪了。</p>
<p>(3)使用PC机连接和配对心率仪HR_5C</p>
<p> </p>
<p>这里需要注意使用比较高的BLE适配器,最初我使用的是4.0的适配器,只能连接但是会出现问题。这里不知道是我的适配器的问题还是什么问题。我换成BLE 5.4的适配器就没有出现问题了。我的BLE 4.0是需要驱动的。是不是和这个有关系就不知道了,所以还请知道的朋友科普。</p>
<p>(4)配对连接设备</p>
<p> </p>
<p>如果不配对就会连接失败</p>
<p> </p>
<p>这个API会记录上次的配对设备,如果连接不上可以将设备重新上电和运行程序。</p>
<p>(5)配对连接后就可以从页面上显示数据了</p>
<p> </p>
<p><strong>4、总结</strong></p>
<p>蓝牙设备有很多的属性文件。如果想开发BLE设备需要仔细的研究GATT官方。ST官方的协议栈编程的框架学习曲线较为陡直。目前我也是入门,在后期的测试中我会分享一些心得。</p>
<p> </p>
<p> </p>
<p>这个页面看着还是挺不错的,这样直接连接还是挺不错的</p>
<p>最近正在学习node.js,发现js可以开发的内容大大超出了传统web的范畴,可以覆盖大部分的桌面APP。包括BLE API和摄像头等等外设。</p>
<p>你这个是谷歌的web ble的demo改的吧(询问,没恶意)?</p>
<p>这些demo没有服务器是不是不能在手机上运行,要怎么才能在手机上运行啊,网上查到的都很隐晦</p>
dvacos 发表于 2024-10-11 16:42
你这个是谷歌的web ble的demo改的吧(询问,没恶意)?
这些demo没有服务器是不是不能在手机上运行,要 ...
<p>是web标准的BLE API,不需要额外的设置。最新的浏览器都支持。可以参考文章中的相关连接</p>
dvacos 发表于 2024-10-11 16:42
你这个是谷歌的web ble的demo改的吧(询问,没恶意)?
这些demo没有服务器是不是不能在手机上运行,要 ...
<p>手机上的需要看操作系统的版本,其实是浏览器的内核,我的程序也可以运行在web服务器当中。客户端本地不需要安装任何支持库</p>
页:
[1]