ITで遊ぶ

micro:bit + BLE + ChromeブラウザーそしてPromise

micro:bitをBLE(Bluetooth Low Energy)を使ってブラウザーから操作するという、とても大事な技術の研究。

技術の社会的意義

micro:bitをあきらめた。アメリカの友人が「今はESP32がホットだし安いぜ。」なんだそうな。
日本はやはり後追いだ。

なぜならばみなさんはIoTと騒いでいるけど、もう少し詳細を考えたことはあるだろうか?

IoTデバイスがセンサーをもっているとする。
それをどうやってインターネットにつなぐと思いますか。

ほとんどの人がWi-Fiとか、小型の携帯電話モジュールとかを考えると思います。
おそらく技術を知らないマッキンゼーとかBCGとかの間抜けなコンサルタントもそう考えるんじゃないかな。アクセンチュアみたいに自分でシステム構築していないどころか、売り物のプログラム一本書いたことない「戦略コンサル」がIT技術を語るのは本当に嫌いだ。(と毒を吐く)

でもね、それは今の技術じゃ不可能なんです。

Wi-Fiも携帯電話モジュールも場合によっては3.3V2アンペア近い電力が必要です。電池ではたちまち消耗します。
この単純な事実が知られていない。

考えてもみてください。スマホってリチウムイオン電池という今の最新の技術を使っていても、ほぼ毎日充電しますよね。

ばらまいたIoTデバイスを充電したり電池交換して歩いたりできると思いますか?

それを解決する技術は現在ただひとつBluetooth Low Energy(BLE)しかないんです。

BLEは従来のBluetoothとは互換性のない技術です。

IoTを使おうとするならばBLEで、あなたのもっているスマホにデータを渡し、そこからインターネットに飛ばすしか現状ではちょっと考えにくい。
(もちろん自動販売機や自動車みたいに大きな電源もってて、そこから給電すればいいというのは別ですよ。私の想定は広い工場敷地内とか、通りです。)

よってBLEとアプリケーションを配布しないでいい、ブラウザーの進化から目を離せないのです。
とくにGoogle Chromeはこの分野の最先端を走っています。

2018年6月現在、一部の専門家しか気づいてないと思います。

以下、技術研究。

Javascript

Promise

Promiseとは、ちょっと前にリリースされたJavascript界の大事件である。

私は知らなかった。

もともとJavascriptはシングルスレッドであった。
このあたりから書き起こされたGoogleドキュメントがこれ

なにかのイベントにaddEventListenerでイベントハンドラーをつけたとしても、それは非同期では動かない。イベントを受け取ったら所定のコールバック関数を呼ぶ。

そこでイベント1ができたら、イベント2をやって、とプログラムを書くとどんどんネストが深まった関数ができてしまう、と。

それを解決するのがPromiseです。(個人的には非同期処理の順序を約束するからPromiseなのかなぁ、と)

function taskA () {
  console.log("TaskA");
}

function taskB () {
  console.log("TaskB");
}

function onRejected(error) {
  console.log("error = " + error);
}

var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected);

console.log('Hello World');

これ実行すると、Hello World, Task A, Task Bと出力されます。

これ以上の議論はこのブックレットを読むべし。
忙しい時はこちらでも。なお、ほとんどの解説がよくわからず書かれている模様。

さらにアロー関数が頻繁に出てくるのでアロー関数の勉強

アロー関数

アロー関数は無名関数を簡単に書こうというもの。(thisの扱いが変わるが、今回は割愛)

もともとこう書いて関数を変数にいれちまう。

var func = function(x){ return  x++ }

これを

var func = (x) => {x++;}

return を省略できる。

これだけだとつまんないけど、

引数がいっぱいある時は

var func = (x, y, z) => { x+y+z ;}

引数が1つしかない時は()が省略できて

var func = x=>{ x-2; }

(これを知らず、えらい苦労した)

引数がない時は省略できない。

var func = () =>{return 'zero';}

 

これをふまえて、ブラウザーからのBLEのハンドリングを書くことになります。(Googleさんが、そうしているから)

micro:bit

スケッチ

参照:https://lancaster-university.github.io/microbit-docs/ble/profile/

micro:bitのプロジェクトにBLuetoothのパッケージを追加する。
以下のようなエラーが出るけど無視して置き換える。

すると、「プロジェクトの設定」でBLuetoothのセキュリティレベルを下げられる。

プログラムのセットアップ部分で、Bluetoothの使用可能としておく。

最低限、Bluetoothが繋がった、切れた、イベントを

ここでプログラムをボードへダウンロード

Google Chrome

ブラウザー側にmicro:bitのUUIDを知らせなきゃいけないそうです。

こちらから

BLEの接続シーケンスについてはこちらのサンプルで学ぶ

  1. デバイスの検索
  2. GATTサーバーに接続(device.gatt.connect)
  3. プライマリーサービスの取得(server.getPrimaryService)
  4. キャラクタリスティックの取得(service.getCharacteristic)
  5. キャラクタリスティックにvalueを書き込む(characteristic.writeValue)

UUIDは使うサービスとキャラクタリスティクスを得なくてはいけません。

ボタンサービス
UUID: E95D9882251D470AA062FA1922DFA9A8

ボタンサービス-キャラクタリスティクス
UUID: E95DDA90251D470AA062FA1922DFA9A8

ブラウザーのJavascript

https://qiita.com/yokmama/items/5522fabfb5b9623278e2

デバイスの検索

navigator.bluetooth.requestDevice({ // デバイスの検索
  filters: [{
    namePrefix: 'BBC micro:bit',
  }],
  // 使いたいSERVICEのUUIDを列挙する
  optionalServices: [this.ACCELEROMETERSERVICE_SERVICE_UUID, this.BUTTON_SERVICE_UUID]
})

GATTへ接続

.then(device => {
  this.ble_device = device;
  console.log("device", device);
  // GATTサーバへの接続
  return device.gatt.connect();
})

プライマリーサービスの取得

.then(server =>{
  console.log("server", server)
  // Promise.allは、全てを並列処理して、全てが終わったら次に進む
  return Promise.all([
    // 
    server.getPrimaryService(this.BUTTON_SERVICE_UUID)
  ]);
})

getPrimaryService()で使いたいサービスを指定しますが、Promise.all([])内で使いたいSERVICEのUUIDを指定します。

Characteristicsの取得

.then(service => {
  console.log("service", service)
  return Promise.all([
    service[0].getCharacteristic(this.BUTTON_A_CHARACTERISTIC_UUID),
    service[0].getCharacteristic(this.BUTTON_B_CHARACTERISTIC_UUID)
  ]);
})

Characteristicsの値を保持、保管

.then(chara => {
  console.log("ACCELEROMETER:", chara)
  alert("BLE接続が完了しました。");

  //ボタンが押された際の通知を有効化し、ボタンクリックイベントにコールバックを設定
  chara[0].startNotifications();
  chara[0].addEventListener('characteristicvaluechanged', this.onchangeABtn.bind(this));
  chara[1].startNotifications();
  chara[1].addEventListener('characteristicvaluechanged', this.onchangeBBtn.bind(this));
})

全部のHTML(すいません。オリジナルはきっちりとHTML,CSS,JSをわけていますが勉強用に一緒くたにしてしまいました。)

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>Web Bluetooth micro:bit TEST</title>
    <link rel="stylesheet" href="assets/css/style.css">
  </head>
  <body data-role="page">
    <div id="connect">
      <button type="button" id="connect-button">CONNECT</button>
      <button type="button" id="disconnect-button">DISCONNECT</button>
    </div>
    <script>
    /*jshint esversion: 6 */
var MicrobitBLE = function() {
  // デバイス情報保持用変数
  this.ble_device = null;
};
MicrobitBLE.prototype = {
  // Class定数定義

  // micro:bit BLE UUID ボタン関連
  BUTTON_SERVICE_UUID: 'e95d9882-251d-470a-a062-fa1922dfa9a8',
  BUTTON_A_CHARACTERISTIC_UUID: 'e95dda90-251d-470a-a062-fa1922dfa9a8',
  BUTTON_B_CHARACTERISTIC_UUID: 'e95dda91-251d-470a-a062-fa1922dfa9a8',

  // 接続関数(Promiseによる非同期逐次処理(.thenの部分))
  connect: function() {
    navigator.bluetooth.requestDevice({ // デバイスの検索
      filters: [{
        namePrefix: 'BBC micro:bit',
      }],
      // 使いたいSERVICEのUUIDを列挙する
      optionalServices: [this.BUTTON_SERVICE_UUID]
    })
    .then(device => {
      this.ble_device = device;
      console.log("device", device);
      // GATTサーバへの接続
      return device.gatt.connect();
    })
    .then(server =>{
      console.log("server", server);
      // Promise.allは、全てを並列処理して、全てが終わったら次に進む
      return Promise.all([
          server.getPrimaryService(this.BUTTON_SERVICE_UUID)
      ]);
    })
    .then(service => {
      console.log("service", service);
      return Promise.all([
        service[0].getCharacteristic(this.BUTTON_A_CHARACTERISTIC_UUID),
        service[0].getCharacteristic(this.BUTTON_B_CHARACTERISTIC_UUID)
      ]);
    })
    .then(chara => {
      console.log("ACCELEROMETER:", chara);
      alert("BLE接続が完了しました。");

      //ボタンが押された際の通知を有効化し、ボタンクリックイベントにコールバックを設定
      chara[0].startNotifications();
      chara[0].addEventListener('characteristicvaluechanged', this.onchangeABtn.bind(this));
      chara[1].startNotifications();
      chara[1].addEventListener('characteristicvaluechanged', this.onchangeBBtn.bind(this));
    })
    .catch(error => {
      alert("BLE接続に失敗しました。もう一度試してみてください");
      console.log(error);
    });
  },
  disconnect: function() {
    if (!this.ble_device || !this.ble_device.gatt.connected) return ;
    this.ble_device.gatt.disconnect();
    alert("BLE接続を切断しました。");
  },
  onchangeABtn: function() {
    alert("A Button");
    console.log("A Button");
  },
  onchangeBBtn: function() {
    alert("B Button");
    console.log("B Button");
  }
};

window.onload = function () {
  var microbitBLE = new MicrobitBLE();
  document.getElementById("connect-button").addEventListener("click", microbitBLE.connect.bind(microbitBLE), false);
  document.getElementById("disconnect-button").addEventListener("click", microbitBLE.disconnect.bind(microbitBLE), false);
};
    
    </script>
  </body>
</html>

 

注意事項

サーバーに乗っけたらhttpsであること。

 

関連記事

  1. WooCommerceからB2Webへ(2)

  2. やっぱりドットインストールは勉強にいい

  3. CakePHP(1)

  4. Google Map APIでとりあえずマーカーを立てる

  5. 小学生にプログラミング?言ってるおまえ、プログラム書けないだろ

  6. PIC16F1705

  7. PythonのGUI(ジオメトリーマネージャ)

  8. web APIサーバーで語られないこと