Nuxt3 で作った仕組みで Sony MESH 温度湿度ブロックに WebBluetooth API で値を取得するメモ

Nuxt3 で作った仕組みで Sony MESH 温度湿度ブロックに WebBluetooth API で値を取得するメモです。

背景

Nuxt3 で Basic 認証と Bootstrap の環境をひとまず用意するメモで、基本的な仕組みを作り。
Nuxt3 で作った仕組みで Sony MESH に WebBluetooth API で接続するメモでデバイスの接続を行って、Nuxt3 で作った仕組みで Sony MESH に WebBluetooth API でステータス LED 操作するメモで、全 MESH 共通のステータス LEDを動作させるまでできています。

pages/index.vue を変更

前回 UI をつくった pages/index.vue を変更します。

<script setup lang="ts">

import { ref, onMounted } from 'vue'
import { BIconBluetooth } from 'bootstrap-icons-vue';

let service : any;
let device : any;

const currentStatus = ref('--')
const currentDeviceName = ref('--')
const currentDeviceID = ref('--')
const currentTempHumdValue = ref('--')

// MESH Service UUID と Characteristics UUID
// https://developer.meshprj.com/hc/ja/articles/8286360648089-%E9%80%9A%E4%BF%A1%E4%BB%95%E6%A7%98%E3%81%AE%E6%A6%82%E8%A6%81
const serviceUuid = '72c90001-57a9-4d40-b746-534e22ec9f9e';
const characteristicUuid_Indicate = '72c90005-57a9-4d40-b746-534e22ec9f9e';
const characteristicUuid_Write = '72c90004-57a9-4d40-b746-534e22ec9f9e';
const characteristicUuid_WriteWithoutResponse = '72c90002-57a9-4d40-b746-534e22ec9f9e';
const characteristicUuid_Notify = '72c90003-57a9-4d40-b746-534e22ec9f9e';

let flagIndicated :boolean;
let characteristic_Write : any;

// リクエスト ID
const requestID = 0x05;

const getBLEConnect = async () => {
  try {

    // 名前で絞り込んだりサービス明示的に指定してフィルタしたり
    // https://developer.meshprj.com/hc/ja/articles/8286360648089-%E9%80%9A%E4%BF%A1%E4%BB%95%E6%A7%98%E3%81%AE%E6%A6%82%E8%A6%81
    // namePrefix は MESH-100 ではじまるデバイスで絞り込む
    // services で  Characteristics UUID を許可
    let options : any = {};
    options.filters = [
        {namePrefix: 'MESH-100TH'}, // MESH-100 から MESH-100TH にして温度湿度ブロックだけ判定
        {services: [
          characteristicUuid_Indicate,
          characteristicUuid_Write,
          characteristicUuid_WriteWithoutResponse,
          characteristicUuid_Notify
        ]}
    ];
    // optionalServices で MESH の Service UUID を許可
    options.optionalServices = [serviceUuid];

    console.log('Requesting Bluetooth Device...');

    // 初期化
    currentDeviceName.value = '--';
    currentDeviceID.value = '--';

    // Indicate の状態
    flagIndicated = false;

    // 温度情報
    currentTempHumdValue.value = '--';
    
    // 接続開始
    device = await navigator.bluetooth.requestDevice(options);

    console.log('> Name: ' + device.name);
    console.log('> Id: ' + device.id);
    console.log('> Connected: ' + device.gatt.connected);

    currentDeviceName.value = device.name;
    currentDeviceID.value = device.id;

    console.log('Connecting to GATT Server...');
    currentStatus.value = 'Connecting to GATT Server...';
    const server = await device.gatt.connect();
    console.log(server);
        
    console.log('Getting Service...');
    currentStatus.value = 'Getting Service...';
    service = await server.getPrimaryService(serviceUuid);
    console.log(service);

    console.log('MESH Connected!');
    currentStatus.value = 'MESH Connected!';
    
    // Indicate、Notify の有効化
    //
    // 参考
    // https://googlechrome.github.io/samples/web-bluetooth/notifications.html
    // https://developer.meshprj.com/hc/ja/articles/8286377144729#h_01GBQDKSBPA60Z7557Z35V16MR
    // https://qiita.com/umi_kappa/items/45761c7454d5d4c10bf0

    // Indicate のイベントを待つ処理
    const characteristic_Indicate = await service.getCharacteristic(characteristicUuid_Indicate);
    await characteristic_Indicate.startNotifications();
    console.log('characteristic_Indicate.startNotifications');
    characteristic_Indicate.addEventListener('characteristicvaluechanged', handleIndicateNotifications);
    
    // Notification のイベントを待つ処理
    const characteristic_Notify = await service.getCharacteristic(characteristicUuid_Notify);
    await characteristic_Notify.startNotifications();
    console.log('characteristic_Notify.startNotifications');
    characteristic_Notify.addEventListener('characteristicvaluechanged', handleNotifyNotifications);

    // ブロック機能の有効化
    // 0x00020103 を送る。
    // Indicate イベントが来れば、いろいろと MESH にお願いができるようになる。
    //
    // 参考
    // 全部ブロック共通の処理
    // https://developer.meshprj.com/hc/ja/articles/8286379681945

    characteristic_Write = await service.getCharacteristic(characteristicUuid_Write);
    console.log('start characteristic_Write');
    console.log('ブロック機能の有効化');
    currentStatus.value = 'ブロック機能の有効化 Indicating...';
    characteristic_Write.writeValue(new Uint8Array([0x00,0x02,0x01,0x03]));
    console.log('wrote characteristic_Write');
    
  } catch (e){
      console.log('Error!');
      console.log(e);
      currentStatus.value = '[Error!] ' + e;
  }
}

const handleIndicateNotifications = async (event : any ) => {
  console.log('handleIndicateNotifications');
  // console.log(event);
  let value = event.target.value;
  const valueUint8Array = new Uint8Array(value.buffer)
  console.log(valueUint8Array);

  console.log('Indicate OK');
  currentStatus.value = 'Indicate OK! 操作可能!';
  flagIndicated = true;
};

const handleNotifyNotifications = async (event : any ) => {
  console.log('handleNotifyNotifications');
  // console.log(event);
  let value = event.target.value;
  const valueUint8Array = new Uint8Array(value.buffer);
  const valueDataview = new DataView(value.buffer);

  if( valueDataview.getUint8(2) == requestID ){
    // 温度・湿度ブロック (MESH-100TH) 仕様
    // https://developer.meshprj.com/hc/ja/articles/8286425847961
    // リクエスト ID で識別
    console.log('温度湿度データの取得');
    console.log(valueDataview.buffer);
    const temp = (valueDataview.getUint8(5) * 256 + valueDataview.getUint8(4))/10;
    const humd = valueDataview.getUint8(7) * 256 + valueDataview.getUint8(6);

    currentTempHumdValue.value = `温度 ${temp} ℃ / 湿度 ${humd} %`
  } else {
    console.log('その他の通知');
    console.log(valueDataview.buffer);
  }
};

// 温度・湿度データの取得
const getTempHumdSensor = async () => {
  console.log("getTempHumdSensor");
  if(flagIndicated){
    // 温度・湿度ブロック (MESH-100TH) 仕様
    // https://developer.meshprj.com/hc/ja/articles/8286425847961

    let baseArray = [
      0x01, // Message Type ID
      0x00, // Event Type ID
      requestID, // リクエスト ID(任意のID)
      0xF4, // 温度通知イベントの範囲 (上限値、LSB)
      0x01, // 温度通知イベントの範囲 (上限値、MSB) LSB 0xf4、MSB 0x01(50 ℃) 0x01f4
      0x00, // 温度通知イベントの範囲 (下限値、LSB)
      0x00, // 温度通知イベントの範囲 (下限値、MSB) LSB 0x00、MSB 0x00(0 ℃)
      0x64, // 湿度通知イベントの範囲 (上限値、LSB)
      0x00, // 湿度通知イベントの範囲 (上限値、MSB) LSB 0x64、MSB 0x00(100 %) 0x0064
      0x00, // 湿度通知イベントの範囲 (下限値、LSB)
      0x00, // 湿度通知イベントの範囲 (下限値、MSB) LSB 0x00、MSB 0x00(0 %)
      0x11, // 温度通知イベントの通知条件 0x11(下限値以上、上限値以下)
      0x11, // 湿度通知イベントの通知条件 0x11(下限値以上、上限値以下)
      0x10, // 通知モード 0x10 現在の値を 1 回通知
      0x00, // チェックサム
    ];

    // チェックサム 0-14 の総和
    for(let i = 0; i < baseArray.length - 1; i++){
      baseArray[14] += baseArray[i];
    }
    
    // 書き込む
    const sendValue = new Uint8Array(baseArray);
    const result = await characteristic_Write.writeValue(sendValue);
    console.log("wrote!");

  }
}

// ステータスバーの点灯 赤
// https://developer.meshprj.com/hc/ja/articles/8286379681945#h_01GBQTH1222DGNRKQ1QCTHST11
const doStatusLED_RED = async () => {
  if(flagIndicated){
    characteristic_Write.writeValue(new Uint8Array([
      0x00,
      0x00,
      0x01,
      0x00,
      0x00,
      0x01,
      0x02
    ]));
  }
}

// ステータスバーの点灯 青
// https://developer.meshprj.com/hc/ja/articles/8286379681945#h_01GBQTH1222DGNRKQ1QCTHST11
const doStatusLED_BLUE = async () => {
  if(flagIndicated){
    characteristic_Write.writeValue(new Uint8Array([
      0x00,
      0x00,
      0x00,
      0x00,
      0x01,
      0x01,
      0x02
    ]));
  }
}

// ステータスバーの点灯 緑
// https://developer.meshprj.com/hc/ja/articles/8286379681945#h_01GBQTH1222DGNRKQ1QCTHST11
const doStatusLED_GREEN = async () => {
  if(flagIndicated){
    characteristic_Write.writeValue(new Uint8Array([
      0x00,
      0x00,
      0x00,
      0x01,
      0x00,
      0x01,
      0x02
    ]));
  }
}

// BLE の切断処理
const setBLEDisconnect = async () => {
  console.log('Disconnecting from Bluetooth Device...');
  currentStatus.value = 'Disconnecting from Bluetooth Device...';
  if (device.gatt.connected) {
      await device.gatt.disconnect();
      console.log('Disconnected');
      currentStatus.value = 'Disconnected!';
  } else {
      console.log('> Bluetooth Device is already disconnected');
  }
}

onMounted(() => {

})

</script>

<template>
  <div class="row">
    <div class="col">
      <h1>MESH Sample Temp&Humd</h1>
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      <button type="button" class="btn btn-primary" @click="getBLEConnect"><BIconBluetooth /> 周辺の MESH デバイスを取得</button>
    </div>
    <div class="p-2">
      <button type="button" class="btn btn-secondary" @click="setBLEDisconnect">MESH デバイス切断</button>
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      {{currentStatus}}
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      <button type="button" class="btn btn-secondary" @click="getTempHumdSensor">温度・湿度取得</button>
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      温度・湿度データ : {{currentTempHumdValue}}
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      <button type="button" class="btn btn-secondary" @click="doStatusLED_RED">ステータス LED ON (赤)</button>
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      <button type="button" class="btn btn-secondary" @click="doStatusLED_BLUE">ステータス LED ON (青)</button>
    </div>
  </div>
  <div class="d-flex">
    <div class="p-2">
      <button type="button" class="btn btn-secondary" @click="doStatusLED_GREEN">ステータス LED ON (緑)</button>
    </div>
  </div>
</template>

動かしてみる

npm run dev コマンドで開発サーバーを起動します。

もともとが Nuxt3 で Basic 認証と Bootstrap の環境をひとまず用意するメモ なので、Chrome ブラウザで表示すると Basic 認証を聞かれるので ID / PASS を入力してページを表示します。

image

うまく表示されました。今回は温度・湿度取得というボタンで MESH 温度湿度ブロックからデータが取得できる仕組みです。

image

MESH のデバイス名の接頭語「MESH-100TH」で絞り込んだものが表示されています。MESH-100TH にして温度湿度ブロックだけ判定できるようにしています。

image

今回のつなぎたいデバイスをクリックしてペア設定をクリックします。

image

接続中でステータスが出ます。接続後のブロック機能の有効化も行って、Indicate まできて操作が可能になりました。

image

温度・湿度取得ボタンをクリックします。

image

このように温度・湿度データが取得できました!

image

別のタイミングで、同じ部屋の別の温度湿度計で計測してみたところ、ちゃんと近い値が来ていたので、バッファからはうまく取得できているようです。