若くない何かの悩み

何かをdisっているときは、たいていツンデレですので大目に見てやってください。

あまりに BLE マクロを Nature Remo 上でデバッグするのが苦行だったので Swift 用の開発環境を整えた

TL;DR

Android nRF Connect の BLE マクロのサブセットを macOS/iOS/... 上で開発する環境を用意しました。

github.com

import Foundation
import BLEMacroEasy

// You can find your iPhone's UUID by running the following command in Terminal:
// $ git clone https://github.com/Kuniwak/swift-ble-macro
// $ cd swift-ble-macro
// $ swift run ble discover
let myIPhoneUUID = UUID(uuidString: "********-****-****-****-************")!
let myMacro = try String(contentsOf: URL(string: "https://ble-macro.kuniwak.com/iphone/battery-level.xml")!)

try await run(macroXMLString: myMacro, on: myIPhoneUUID) { data in
    // This handler is called when every value read from the peripheral.
    let batteryLevel = Int(data[0])
    print("\(batteryLevel)%")
}

macOS/iOS/... 上で BLE マクロを実行する機能のほかマクロの開発を助ける CLI を提供しています。BLE マクロ実行機能については README を見てください。CLI は BLE デバイスのスキャン、マクロのバリデーション、マクロの実行、対話的なマクロ実行をサポートしています:

$ # BLE デバイスをスキャンする
$ ble discover
00000000-0000-0000-0000-000000000000    Example Device 1    -78
11111111-1111-1111-1111-111111111111    Example Device 2    -47
22222222-2222-2222-2222-222222222222    Example Device 3    -54
...

$ # Ctrl+C でスキャンを中断する

$ # BLE マクロを実行する
$ ble run path/to/your/ble-macro.xml --uuid 00000000-0000-0000-0000-000000000000

$ # BLE マクロを対話的に実行する
$ ble repl --uuid 00000000-0000-0000-0000-000000000000
connecting...
connected

(ble) ?
write-command, w, wc    Write to a characteristic without a response
write-descriptor, wd    Write to a descriptor
write-request, req      Write to a characteristic with a response
read, r Read from a characteristic
discovery-service, ds   Discover services
discovery-characteristics, dc   Discover characteristics
discovery-descriptor, dd        Discover descriptors
q, quit Quit the REPL

(ble) dc
180A 2A29 read
180A 2A24 read
D0611E78-BBB4-4591-A5F8-487910AE4366 8667556C-9A37-4C91-84ED-54EE27D90049 write/write/notify/extendedProperties
9FA480E0-4967-4542-9390-D343DC5D04AE AF0BADB1-5B99-43CD-917A-A77BC549E3CC write/write/notify/extendedProperties
180F 2A19 read/notify
1805 2A2B read/notify
1805 2A0F readk

(ble) r 180F 2A19
58

なお開発した BLE マクロは Kuniwak/ble-macro で公開しています。

背景

Nature Remo に BLE(Bluetooth Low Energy)マクロ機能が搭載されました(公式アナウンス)。対応機種(Nature Remo3、Nature Remo mini2 ファミリー)の Remo をお使いなら BLE デバイスを Remo から操作できます。そこで BLE で操作できる我が家の Philips Hue のフルカラー電球x3を Remo から操作すべく BLE マクロの開発を始めました(無駄にフルカラーなのはこの動画を見て淡い憧れがあったため)。

我が家の Philips Hue

Remo のサポートしている BLE マクロは Android nRF Connect のサブセットです。マクロは XML で記述します。例えば Hue を白昼色で点灯させるには次のように記述します:

<macro name="hue-daylight-white" icon="BRIGHTNESS_HIGH">
    <assert-service description="Hue" uuid="932c32bd-0000-47a2-835a-a8d455b859dd">
        <assert-characteristic description="Combined" uuid="932c32bd-0007-47a2-835a-a8d455b859dd">
            <property name="WRITE" requirement="MANDATORY"/>
        </assert-characteristic>
    </assert-service>
    <write description="Set to Daylight White" characteristic-uuid="932c32bd-0007-47a2-835a-a8d455b859dd" service-uuid="932c32bd-0000-47a2-835a-a8d455b859dd" value="0101010201fe0302fa0005020100" type="WRITE_REQUEST" />
</macro>

BLE マクロでは service や characteristic という操作窓口で値の読み書きを指示できます。有名なデバイスならばこれらは 第三者による仕様の推測 である程度詳細がわかります。ただマクロを記述して Remo で動作する状態に持っていくまでにはそれなりに試行錯誤が必要になります。この試行錯誤がとにかく苦行でした

公式曰く Android なら nRF Connect for Mobile アプリで BLE マクロの記録から実行までをサポートしているらしく楽にマクロを開発できるようです。しかし手元には Android 端末がありませんでした。iOS 用に同じ開発者の 同名のアプリ がありますがこれにはマクロ機能がついていません。そのため最初の頃は Remo に BLE マクロを食べさせてみて理由のほとんどわからないエラーに直面して、BLE マクロを勘で書き換えて…というサイクルを回していました。

エラーに詳細な情報がない様子

このサイクルがあまりに不毛であったため Android の nRF Connect for Mobile と同様の機能をもつ環境をなんとか整えられないかと思い Kuniwak/swift-ble-macro を開発しました。nRF Connect for Mobile よりも大幅に機能は少ないですが、Remo で動作する BLE マクロを開発するには十分な機能を持っています。ぜひご活用ください。

なおBLE マクロを開発するには既存のアプリがどんな通信を BLE デバイスとしているかをキャプチャすることも重要です。詳しくは次のブログを読むとやり方がわかるでしょう:

harumi.sakura.ne.jp

BLE マクロを開発した際の苦労

Hue や Switchbot でさまざまな苦労をしたのでここで供養しておきます。

Hue から insufficient encryption エラーが返ってくる

Hue はペアリングしないと操作できないようです。ペアリングしていない central から read/write しようとすると insufficient encryption エラーが発生します。なおペアリングされるのは最初にペアリングされたデバイスのみです(後述する方法でこれを回避できる)。例えば初回に Hue のアプリで接続してしまうとそれ以降のデバイス(e.g. Remo、Google Nest mini)からは操作を受け付けてくれなくなってしまいます。これを回避するにペアリングしてある Hue の公式アプリ > 設定 > 音声アシスタント > Google Home > 検出可能にする、の操作が必要です。この操作によってごく短時間(1minほど?)ペアリングを受け付けてくれるようになります。これは service 0000fe0f-0000-1000-8000-00805f9b34fb の characteristic 97fe6561-2004-4f62-86e9-b71ee2da3d220x01 を書き込むことで再現できます(検出可能にする BLE マクロ)。

ペアリングし損ねた Hue のファクトリリセット方法がわからない

公式アプリや Mac などにペアリングする前に Google Nest mini などにペアリングされてしまうと前述の insufficient encryption 問題によって公式アプリや Mac などから操作ができなくなります。ファクトリリセットは BLE による操作で実現されているため公式アプリがペアリングされていない状況ではファクトリリセットが必要になります。

Hue をファクトリリセットする方法として点灯と消灯のサイクルを回す方法が紹介されています。しかし私の Hue(モデル LCA009、ファームウェアバージョン v1.116.3)ではうまく動きませんでした。代わりに、Hue の電源を壁面のスイッチ等から落としてから再び電源を入れると、ごく短時間 & かなりの近距離であれば Hue の公式アプリで検出可能になることを発見しました。そこからファクトリリセットができました。

Switchbot を操作しようとしても反応しない

Switchbot も BLE によって操作ができるデバイスです。ただこいつもなかなかの曲者です。v6.6 なら Discord の開発者コミュニティに紹介されているマクロ で操作ができるのですが v6.3 では操作を受け付けてくれません。試行錯誤しているうち v6.3 でも characteristic cba20002-224d-11e6-9fb8-0002a5d5c51b に 0x01 を書き込む前に cba20003-224d-11e6-9fb8-0002a5d5c51b で通知の購読をすると爪が動くことを発見しました。ということで v6.3 で動くマクロは以下になります:

<macro name="switchbot-push" icon="PLAY">
    <assert-service description="Switchbot" uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b">
        <assert-characteristic description="Push" uuid="cba20002-224d-11e6-9fb8-0002a5d5c51b">
            <property name="WRITE" requirement="MANDATORY"/>
        </assert-characteristic>
        <assert-characteristic description="Configuration" uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b">
            <property name="NOTIFY" requirement="MANDATORY"/>
            <assert-cccd />
        </assert-characteristic>
    </assert-service>
    <write-descriptor description="Enable notifications" characteristic-uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b" service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b" uuid="00002902-0000-1000-8000-00805f9b34fb" value="0100" />
    <write description="Write 0x570100" characteristic-uuid="cba20002-224d-11e6-9fb8-0002a5d5c51b" service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b" value="570100" type="WRITE_REQUEST" />
    <wait-for-notification description="Wait for notification" characteristic-uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b" service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b" timeout="5000" />
</macro>

お世話になった製品・文献

最後にお世話になった製品・文献を紹介します:

github.com

github.com