記事の内容
ソニーが販売している IoT タグ MESH を利用して、温度・湿度を測定する。
動機
感染症対策・熱中症対策として、部屋の温度・湿度を測定したい
「感染症対策・熱中症対策には温度と湿度の管理が大切」 ということはわかっていたが、実際の温度・湿度は把握していなかった。
単純に温度計・湿度計を購入して測定すればいいが、自動的に一定間隔で測定データを記録して、そのデータを可視化したり機械学習で活用できるのではないか。
また以前、BLE通信を利用してセンサと接続できる iOS アプリを開発するプロジェクトに参画していた。その際の内容を思い出すためにも、温度・湿度を測定できる iOS アプリの開発に挑戦した。
開発環境
PC
MacBook Air(2020,M1) メモリ:16GB ストレージ:1TB
言語
Swift
フレームワーク
SwiftUI
MESH
温度・湿度ブロック
- 測定できる温度の範囲: -10℃ 〜 50℃
- 測定できる湿度の範囲: 0% 〜 100%
- 充電: Micro-USB
アプリの要件
- MESH 温度・湿度ブロック 1台と MacBook Air を BLE 通信を利用して接続する
- MESH 温度・湿度ブロックは接続後、0.5秒ごとに温度・湿度の測定データを PC に送信する
- iOS アプリで現在の温度と湿度を確認する
- 今回は MacBook Air をセントラル MESH 温度・湿度ブロックをペリフェラル とする(※セントラル・ペリフェルについては後述)
BLE 通信
BLEとは
- 近距離無線通信の規格 Bluetooth を省電力化した通信形式
- PC、スマートフォンなど通信の親機に相当する機器のことを セントラル と呼ぶ
- MESH や忘れ物防止タグなど通信の子機に相当する機器のことを ペリフェラル と呼ぶ
BLE通信の流れ
※ 説明をわかりやすくするため、簡略化しています
- ペリフェラルが通信待ちの状態では、周囲の不特定多数の機器を対象に無線信号を送信(アドバタイズ)
- セントラルがスキャンを実行することで、アドバタイズを受信
- セントラルが1対1で通信したいペリフェラルに対して、接続を要求
- ペリフェラルがセントラルからの接続要求を受信し、1対1の通信(GATT通信)に切り替える
- ペリフェラルからセントラルへ通信データを送信する ※
- セントラルがペリフェラルに対して、切断を要求
※ GATT 通信の特徴
サービス と キャラクタリスティック の形で測定データをやり取りする
コード
ポイント
CoreBluetooth を利用する
つまづいたところ
センサと接続が完了した際に、ペリフェラルのデリゲートを設定する
-
peripheral.delegate = self
を記載が必要であることを気づかず、時間を浪費した
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
guard let peripheral = devices.first?.peripheral else { return }
peripheral.delegate = self
peripheral.discoverServices(services)
print("centralManager did connect peripheral: \(peripheral.name?.description ?? "unnnamed")")
}
ペリフェラルにブロック機能の有効化・温度や湿度、測定頻度の設定コマンドを送信する
- 必要な知識: リトルエンディアン・16進数の足し算(チェックサムの値を算出するために必要)
- 16進数の足し算: MacBook Air にプリインストールされている 「計算機」 アプリで対応
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
let byteArray: [UInt8] = [0x00, 0x02, 0x01, 0x03]
let data = Data(byteArray)
let batteryLevelArray: [UInt8] = [0x00,0x03,0x00,0x03]
let batteryLevelData = Data(batteryLevelArray)
let modeSettingArray: [UInt8] = [0x01,0x00,0x01,0xF4,0x01,0xFF,0x9C,0x64,0x00,0x00,0x00,0x11,0x11,0x20,0x38]
let modeSettingData = Data(modeSettingArray)
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
peripheral.setNotifyValue(true, for: characteristic)
peripheral.writeValue(data, for: characteristic, type: .withResponse)
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
}
print("did Discover Characteristics for executed")
for characteristic in characteristics {
peripheral.setNotifyValue(true, for: characteristic)
peripheral.writeValue(batteryLevelData, for: characteristic, type: .withResponse)
peripheral.writeValue(batteryLevelData, for: characteristic, type: .withoutResponse)
}
for characteristic in characteristics {
peripheral.setNotifyValue(true, for: characteristic)
peripheral.writeValue(modeSettingData, for: characteristic, type: .withResponse)
peripheral.writeValue(modeSettingData, for: characteristic, type: .withoutResponse)
}
}
実際にアプリを動かす
iPhone SE (2nd Generation) を使って、MESH センサと接続する様子を収録した。
1日中動かすと、室内の湿度が1日で10〜20%前後変動することがわかった。特に、朝は感染症のリスクが高くなる湿度40%以下になる傾向があるため、部屋で洗濯物を干す・湯を沸かす等の対策が取れた。
(部屋のサイズや人数にもよるが、もしかしたら加湿器をわざわざ購入しなくても良いかも ... )
このアプリの使い所
- 感染症対策
- 熱中症対策
- ペットがいる家庭での温度管理
- 職場の温度・湿度管理
今後したいこと
- WidgetKit を利用して、アプリを立ち上げなくても温度・湿度を確認する。
App Store での配信開始
2023/01/09 配信開始 🎉
2023/01/10 ver 1.2.0 で測定したデータを保存できるようにした
・ 当初 CoreData を利用する予定だったが、Xcode でのアーカイブ時にエラーが発生
・ 上記エラーを解決するのに時間がかかりそうだったため Realm を採用した
参考資料
MESH 技術ドキュメント
MESH の操作方法・通信仕様がまとめられている。
Qiita
下記記事がとてもわかりやすい。
Speacker Deck (2023/04/04 追記)
この記事の取り組みを スライド にまとめました。