LoginSignup
15
11

More than 3 years have passed since last update.

ESP32+BLE+Web bluetoothでWeb上にデータをリアルタイム表示

Last updated at Posted at 2019-07-19

はじめに

ESP32-DevkitCが手元にあるので、ESP32のBLEとWeb bluetoothを使って、Web上にデータをリアルタイム表示させてみました。

マイコン(ESP32)からのデータ送信は下記を参考にしました。

つかったもの

ハードウェア

  • ESP32-DevkitC
    • 今回はセンサを使わずにランダム数を送信します

ソフトウェア

  • Web bluetooth
  • chart.js
    • いい感じのグラフを生成してくれるjs
  • chartjs-plugin-streaming.js
    • リアルタイムストリーミングデータ向けのchart.jsのプラグインで、ストリーミングチャートを簡単に生成できる
    • リンク先のサンプルプログラムをがっつり参考にした
  • moment.js
    • 日付をいい感じに使えるようにしてくれjs

プログラム

Esp32



#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
uint8_t value = 0;
int j = 0;
int len = 0;
char buf[100];
char buf_serialinput[100];

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "d5875408-fa51-4763-a75d-7d33cecebc31"
#define CHARACTERISTIC_UUID "a4f01d8c-a037-43b6-9050-1876a8c23584"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string j = pCharacteristic->getValue();
      int len = j.length();
      Serial.println(len);
      Serial.println(j.c_str());
    }
};
MyCallbacks myCallbacks;

void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("NefryBT");

  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pCharacteristic->setCallbacks(&myCallbacks);
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  int index = 0;
  bool hasData = false;

  if (deviceConnected) {
    char buffer[32];
    int random_num = random(255);
    sprintf(buffer, "%d", random_num);
    Serial.printf("%d\n", random_num);
    pCharacteristic->setValue(buffer);
    pCharacteristic->notify();
    //pCharacteristic->indicate();
    value++;
  }  
  delay(2000);
}

WEB側

HTML



<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <link rel="stylesheet" href="css/flat-ui.min.css" />
    <link rel="stylesheet" href="css/index.css" />

    <script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/min/moment.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.8.0"></script>

    <script src="js/main.js"></script>

    <title>chart.js</title>
</head>
<body>
    <div id="search-button" class="show">
        <div class="view-inner">
            <div class="btn btn-block btn-lg btn-primary">SEARCH</div>
        </div>
    </div>

    <canvas id="myChart" width="400" height="350"></canvas>
</body>
</html>

JavaScript



// UUID
const SERVICE_UUID = "d5875408-fa51-4763-a75d-7d33cecebc31";
const RX_CHARACTERISTIC_UUID = "a4f01d8c-a037-43b6-9050-1876a8c23584";
const TX_CHARACTERISTIC_UUID = "a4f01d8c-a037-43b6-9050-1876a8c23584";

// Characteristic
let txCharacteristic;
let rxCharacteristic;

let searchButton;
let readButton;

let loading;

function init() {
  searchButton = document.querySelector("#search-button");
  searchButton.addEventListener("click", searchBLE);
}

// search & connect
function searchBLE() {

  // acceptAllDevicesの場合optionalServicesが必要みたい
  navigator.bluetooth.requestDevice({
    optionalServices:[SERVICE_UUID],
    acceptAllDevices:true
  })

    .then(device => { 
      console.log("devicename:" + device.name);
      console.log("id:" + device.id);

      // 選択したデバイスに接続
      return device.gatt.connect();
    })

    .then(server => {
      console.log("success:connect to device");

      // UUIDに合致するサービス(機能)を取得
      return server.getPrimaryService(SERVICE_UUID);
    })

    .then(service => {
      console.log("success:service");
      // UUIDに合致するキャラクタリスティック(サービスが扱うデータ)を取得
      // 配列で複数のキャラクタリスティックの取得が可能
      return Promise.all([
        service.getCharacteristic(RX_CHARACTERISTIC_UUID),
        service.getCharacteristic(TX_CHARACTERISTIC_UUID)
      ]);

    })
    .then(characteristic => {
      console.log("success:txcharacteristic");

      rxCharacteristic = characteristic[0];
      txCharacteristic = characteristic[1];

      console.log("success:connect BLE");      
      loading.className = "hide";
    })

    .catch(error => {
      console.log("Error : " + error);

      // loading非表示
      loading.className = "hide";
    });
}

//グローバル変数に変更した
let message;

function readValueBLE() {
  let message;

  try {
    rxCharacteristic.readValue()
      .then(value => {
        message = value.buffer;
        console.log(new Uint8Array(message));
        var value = new TextDecoder("utf-8").decode(message)
        var num = parseInt(value, 10);
        console.log(num);
        document.getElementById("data-form").value = new TextDecoder("utf-8").decode(message);
      });
  }
  catch (e) {
    console.log(e);
  }
}

function writeValueBLE() {
  var form_d = document.getElementById("data-form").value;
  var ary_u8 = new Uint8Array( new TextEncoder("utf-8").encode(form_d) );
  console.log(ary_u8);
  try {
    txCharacteristic.writeValue(ary_u8);
  }
  catch (e) {
    console.log(e);
  }
}

window.addEventListener("load", init);

window.onload = function(){
  var dps = []; //dataPoints
  var chart = new CanvasJS.Chart("chartContainer", {
    width: 450,
    height: 350,
    axisY:{
          includeZero: true
    },
    data:[{
      type: "line",
      dataPoints: dps,
    }]
  });


  var xVal = 0;
  var yVal = 255;
  //下記は2000[ms](2秒)ごとにグラフを更新するように変数を指定。
  //ESP32からも2秒ごとにデータを送信するようにしているので、intervalの時間を合わせておくとわかりやすい。
  var updateInterval = 2000; 
  var dataLength = 20;
  var updateChart = function(count){

    try {
      rxCharacteristic.readValue()
        .then(value => {
          message = value.buffer;
          console.log(new Uint8Array(message));
          document.getElementById("data-form").value = new TextDecoder("utf-8").decode(message);
        });
    }
    catch (e) {
      console.log(e);
    }

    count = count || 1;

    for (var j = 0; j < count; j++) {

      //ESP32からBLEで送られてきたデータをInt型に変換し、変数にnumに代入//
      var value = new TextDecoder("utf-8").decode(message)
      var num = parseInt(value, 10);
      console.log(num);
      ///////////////////////////////////////////////////////////

      yVal = num;
      dps.push({
        x: xVal*2, //intervalが2秒なのでこうした
        y: yVal
      });
      xVal++;
    }

    if (dps.length > dataLength) {
      dps.shift();
    }

    chart.render();
  }

  updateChart(dataLength);
  setInterval(function(){updateChart()}, updateInterval);

}

///////////////////////////////////////////////////////////
var chartColors = {
    red: 'rgb(255, 99, 132)',
    orange: 'rgb(255, 159, 64)',
    yellow: 'rgb(255, 205, 86)',
    green: 'rgb(75, 192, 192)',
    blue: 'rgb(54, 162, 235)',
    purple: 'rgb(153, 102, 255)',
    grey: 'rgb(201, 203, 207)'
};

function randomScalingFactor() {
    return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
}

function onRefresh(chart) {

  try {
    rxCharacteristic.readValue()
      .then(value => {
        message = value.buffer;
        console.log(new Uint8Array(message));
        document.getElementById("data-form").value = new TextDecoder("utf-8").decode(message);
      });
  }
  catch (e) {
    console.log(e);
  }

  var value = new TextDecoder("utf-8").decode(message)
  var num = parseInt(value, 10);
  console.log(num);

    chart.config.data.datasets.forEach(function(dataset) {
        dataset.data.push({
      x: Date.now(),
      y: num //y軸の値はESP32から送られてくるデータ
        });
    });
}

var color = Chart.helpers.color;
var config = {
    type: 'line',
    data: {
        datasets: [{
            label: 'Dataset 1 (linear interpolation)',
            backgroundColor: color(chartColors.red).alpha(0.5).rgbString(),
            borderColor: chartColors.red,
            fill: false,
            lineTension: 0,
            borderDash: [8, 4],
            data: []
        }]
    },
    options: {
        title: {
            display: true,
            text: 'Line chart (hotizontal scroll) sample'
        },
        scales: {
            xAxes: [{
                type: 'realtime',
                realtime: {
                    duration: 20000,
                    refresh: 2000,
                    delay: 2000,
                    onRefresh: onRefresh
                }
            }],
            yAxes: [{
                scaleLabel: {
                    display: true,
                    labelString: 'value'
                }
            }]
        },
        tooltips: {
            mode: 'nearest',
            intersect: false
        },
        hover: {
            mode: 'nearest',
            intersect: false
        }
    }
};

window.onload = function() {
    var ctx = document.getElementById('myChart').getContext('2d');
    window.myChart = new Chart(ctx, config);
};

CSS

CSSは省略

実際の動き

a3d037d662da89ec9b101c4e0e21a189.gif

index.htmlを起動し、SEARCHボタンを押して、電源に繋げてあるESP32とペアリングします。あとは、ESP32から0〜255までのランダム数が送られ、グラフがリアルタイムで描画されていきます。

おわりに

ESP32のBLEやWEB Bluetoothいい感じに使えそうです。

15
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11