bigbat 发表于 2024-10-10 17:45

【NUCLEO-WB09KE】PC机与蓝牙设备通讯与web编程

<p><strong>1、测试介绍</strong></p>

<p>本测试 使用开发板模拟心率仪,用来验证连接性、BLE、BLE 协议、BLE 配对、BLE 配置文件等项目。其中的程序可以作为BLE的编程框架来使用。测试使用NUCLEO-WB09KE作为BLE&ldquo;服务端&rdquo;,客户端使用web页面的javascript程序作为&ldquo;客户端&rdquo;。本测试最开始使用移动客户端作为测试工具,最近我在学习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">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Heart Rate Sensor Demo&lt;/title&gt;
    &lt;meta name="description" content="Monitor a heart rate sensor with a Web Bluetooth app."&gt;
    &lt;link rel="icon" sizes="192x192" href="../favicon.png"&gt;
    &lt;link rel="stylesheet" href="styles.css"&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id="container"&gt;
      &lt;div id="statusText"&gt;GET &amp;#x2764;&lt;/div&gt;
      &lt;canvas id="waves"&gt;&lt;/canvas&gt;
    &lt;/div&gt;
    &lt;script src="heartRateSensor.js"&gt;&lt;/script&gt;
    &lt;script src="app.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>程序主要分为app.js,本程序主要是用来在画布&ldquo;canvas&rdquo;上面显示数据和界面交互。画布的id=&quot;waves&quot;。</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(() =&gt; heartRateSensor.startNotificationsHeartRateMeasurement().then(handleHeartRateMeasurement))
.catch(error =&gt; {
    statusText.textContent = error;
});
});

function handleHeartRateMeasurement(heartRateMeasurement) {
heartRateMeasurement.addEventListener('characteristicvaluechanged', event =&gt; {
    var heartRateMeasurement = heartRateSensor.parseHeartRate(event.target.value);
    statusText.innerHTML = heartRateMeasurement.heartRate + ' &amp;#x2764;';
    heartRates.push(heartRateMeasurement.heartRate);
    drawWaves();
});
}

var heartRates = [];
var mode = 'bar';

canvas.addEventListener('click', event =&gt; {
mode = mode === 'bar' ? 'line' : 'bar';
drawWaves();
});

function drawWaves() {
requestAnimationFrame(() =&gt; {
    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 &lt; 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 &lt; 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", () =&gt; {
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 =&gt; {
      this.device = device;
      return device.gatt.connect();
      })
      .then(server =&gt; {
      this.server = server;
      return server.getPrimaryService('heart_rate');
      })
      .then(service =&gt; {
      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 &amp; 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 &amp; 0x2;
      let contactSensorPresent = flags &amp; 0x4;
      if (contactSensorPresent) {
      result.contactDetected = !!contactDetected;
      }
      let energyPresent = flags &amp; 0x8;
      if (energyPresent) {
      result.energyExpended = value.getUint16(index, /*littleEndian=*/true);
      index += 2;
      }
      let rrIntervalPresent = flags &amp; 0x10;
      if (rrIntervalPresent) {
      let rrIntervals = [];
      for (; index + 1 &lt; 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 =&gt; {
      this._characteristics.set(characteristicUuid, characteristic);
      });
    }
    _readCharacteristicValue(characteristicUuid) {
      let characteristic = this._characteristics.get(characteristicUuid);
      return characteristic.readValue()
      .then(value =&gt; {
      // 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(() =&gt; characteristic);
    }
    _stopNotifications(characteristicUuid) {
      let characteristic = this._characteristics.get(characteristicUuid);
      // Returns characteristic to remove characteristicvaluechanged event
      // handlers in the resolved promise.
      return characteristic.stopNotifications()
      .then(() =&gt; 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>&nbsp;(2)使用编译工具烧写到开发板。</p>

<p>&nbsp; 我使用的是keil工具烧写到开发板。烧写完成后,开发板就可以模拟心率仪了。</p>

<p>(3)使用PC机连接和配对心率仪HR_5C</p>

<p> &nbsp;</p>

<p>这里需要注意使用比较高的BLE适配器,最初我使用的是4.0的适配器,只能连接但是会出现问题。这里不知道是我的适配器的问题还是什么问题。我换成BLE 5.4的适配器就没有出现问题了。我的BLE 4.0是需要驱动的。是不是和这个有关系就不知道了,所以还请知道的朋友科普。</p>

<p>(4)配对连接设备</p>

<p> &nbsp;</p>

<p>如果不配对就会连接失败</p>

<p> &nbsp;</p>

<p>这个API会记录上次的配对设备,如果连接不上可以将设备重新上电和运行程序。</p>

<p>(5)配对连接后就可以从页面上显示数据了</p>

<p> &nbsp;</p>

<p><strong>4、总结</strong></p>

<p>蓝牙设备有很多的属性文件。如果想开发BLE设备需要仔细的研究GATT官方。ST官方的协议栈编程的框架学习曲线较为陡直。目前我也是入门,在后期的测试中我会分享一些心得。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

秦天qintian0303 发表于 2024-10-10 23:17

<p>这个页面看着还是挺不错的,这样直接连接还是挺不错的</p>

bigbat 发表于 2024-10-11 12:14

<p>最近正在学习node.js,发现js可以开发的内容大大超出了传统web的范畴,可以覆盖大部分的桌面APP。包括BLE API和摄像头等等外设。</p>

dvacos 发表于 2024-10-11 16:42

<p>你这个是谷歌的web ble的demo改的吧(询问,没恶意)?</p>

<p>这些demo没有服务器是不是不能在手机上运行,要怎么才能在手机上运行啊,网上查到的都很隐晦</p>

bigbat 发表于 2024-10-11 18:05

dvacos 发表于 2024-10-11 16:42
你这个是谷歌的web ble的demo改的吧(询问,没恶意)?

这些demo没有服务器是不是不能在手机上运行,要 ...

<p>是web标准的BLE API,不需要额外的设置。最新的浏览器都支持。可以参考文章中的相关连接</p>

bigbat 发表于 2024-10-11 18:08

dvacos 发表于 2024-10-11 16:42
你这个是谷歌的web ble的demo改的吧(询问,没恶意)?

这些demo没有服务器是不是不能在手机上运行,要 ...

<p>手机上的需要看操作系统的版本,其实是浏览器的内核,我的程序也可以运行在web服务器当中。客户端本地不需要安装任何支持库</p>
页: [1]
查看完整版本: 【NUCLEO-WB09KE】PC机与蓝牙设备通讯与web编程