775|5

2934

帖子

4

TA的资源

五彩晶圆(中级)

楼主
 

【NUCLEO-WB09KE】PC机与蓝牙设备通讯与web编程 [复制链接]

1、测试介绍

本测试 使用开发板模拟心率仪,用来验证连接性、BLE、BLE 协议、BLE 配对、BLE 配置文件等项目。其中的程序可以作为BLE的编程框架来使用。测试使用NUCLEO-WB09KE作为BLE“服务端”,客户端使用web页面的javascript程序作为“客户端”。本测试最开始使用移动客户端作为测试工具,最近我在学习scratch编程时,发现现在的node.js也可以支持BLE的API,并且可以很好的使用web浏览器作为客户端向web网站发送测试数据,于是就改用javascript作为客户端了。

2、客户端介绍

客户端的源码是我从

链接已隐藏,如需查看请登录或者注册
项目下载的。心率仪作为BLE的标准设备。通讯和数据格式已经被固定格式化了。只要提供规定的API函数就可以使用任何的客户端程序和设备链接。

首先是定义一个index.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 ❤</div>
      <canvas id="waves"></canvas>
    </div>
    <script src="heartRateSensor.js"></script>
    <script src="app.js"></script>
  </body>
</html>

程序主要分为app.js,本程序主要是用来在画布“canvas”上面显示数据和界面交互。画布的id="waves"。

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 + ' ❤';
    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[i + offset ] * 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[i + offset ] * 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();
  }
});

heartRateSensor.js文件,js使用BLE通过API从设备中调测试数据。设备BLE被定义在navigator.bluetooth.requestDevice中,函数将返回一个设备class,成功后装配该对象。

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

})();

当收到数据后调用相关的事件函数。BLE的API请参考:https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice

3、测试过程

(1)首先使用STM32Cub下载BLE_HeartRate程序到本地磁盘。

 (2)使用编译工具烧写到开发板。

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

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

 

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

(4)配对连接设备

 

如果不配对就会连接失败

 

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

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

 

4、总结

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

 

 

此帖出自RF/无线论坛

最新回复

你这个是谷歌的web ble的demo改的吧(询问,没恶意)? 这些demo没有服务器是不是不能在手机上运行,要怎么才能在手机上运行啊,网上查到的都很隐晦   详情 回复 发表于 2024-10-11 16:42
点赞 关注
 

回复
举报

6449

帖子

10

TA的资源

版主

沙发
 

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

此帖出自RF/无线论坛
个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 

回复

2934

帖子

4

TA的资源

五彩晶圆(中级)

板凳
 

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

此帖出自RF/无线论坛
 
 
 

回复

47

帖子

0

TA的资源

一粒金砂(中级)

4
 

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

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

此帖出自RF/无线论坛

点评

手机上的需要看操作系统的版本,其实是浏览器的内核,我的程序也可以运行在web服务器当中。客户端本地不需要安装任何支持库  详情 回复 发表于 2024-10-11 18:08
是web标准的BLE API,不需要额外的设置。最新的浏览器都支持。可以参考文章中的相关连接  详情 回复 发表于 2024-10-11 18:05
 
 
 

回复

2934

帖子

4

TA的资源

五彩晶圆(中级)

5
 
dvacos 发表于 2024-10-11 16:42 你这个是谷歌的web ble的demo改的吧(询问,没恶意)? 这些demo没有服务器是不是不能在手机上运行,要 ...

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

此帖出自RF/无线论坛
 
 
 

回复

2934

帖子

4

TA的资源

五彩晶圆(中级)

6
 
dvacos 发表于 2024-10-11 16:42 你这个是谷歌的web ble的demo改的吧(询问,没恶意)? 这些demo没有服务器是不是不能在手机上运行,要 ...

手机上的需要看操作系统的版本,其实是浏览器的内核,我的程序也可以运行在web服务器当中。客户端本地不需要安装任何支持库

此帖出自RF/无线论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表