この記事は新歓ブログリレー2025 5日目の記事です。
こんにちは。24BのSyntaxErrorです。突然ですが、SwitchBotを試したいけどSwiwtchBotのハブを買うのはちょっと…と思ったことはありませんか?僕はあります。そこで、どの家にもあるRaspberryPiからSwitchBot(ボット) (以下Bot)を操作してみました!
今回のコードの一部はGitHubにありますので、もしよろしければ参考にしてみてください。
なお、十分にスキルがあるわけではないのでクオリティはお察しください…
使ったもの
- Raspberry Pi 4 model B/4GB
- SwitchBot ボット
やったこと
APIドキュメントを見る
SwitchBotはBLE(Bluetooth Low Energy)のドキュメントをGitHubで公開しています。
https://github.com/OpenWonderLabs/SwitchBotAPI-BLE
これを読むとBotを操作できます。
言語選択
Raspberry Piで動けばなんでもいいので、なんとなくGoを選択。自分のGoの練習も兼ねています。Goでなければならない理由は特にないです。
Goでbluetoothを扱うためにはgo-bleを使いました。
がんばる
SwitchBotについても、BLEについても十分に知識があるわけではないのでひたすら試行錯誤をしました。
ボットのドキュメントにはすべてが書いてあるので、調べながら実装しました。
以下は試行錯誤してなんとか動くようになったやり方です。
initialize
go-bleをlinuxで利用するときは、go-ble/linux内のNewDevice()で得られるdeviceをble.SetDefaultDevice(device)で設定します。
Scan
Scanを行い、Bot Broadcast Messageを受け取ります。
func Scan(ctx context.Context, allowDup bool, h AdvHandler, f AdvFilter) errorでは、範囲内のBluetoothデバイスからのアドバタイズを受信し、AdvFilterでフィルタリングして、それぞれのアドバタイズに対してAdvHandlerを実行します。
Botのみに絞るためには、アドバタイズにServiceUUIDが0xfd3dのServiceが存在し、Service Dataの最初のbyteが0x48または0xc8であることを確認するのがいいと思います。(0xfd3dはWoanTechnology(参考)、0x48はSwitchBot Bot(WoHand)、0xc8は0x48の暗号化された状態です。)
さて、ScanによってアドバタイズからはMACアドレスが、Service DataからはSwitchBotのモードや状態、バッテリーの残量などが得られます。操作するときにはMACアドレスとBotのモードや状態が必要なので、適切に保存しておきます。
Botへの接続
ble.Connectかble.Dialを用いて接続します。少なくとも自分の環境においてDialはScan中にしか動かなかったので、Connectのほうが使い勝手がいい気がします。
DialはMACアドレスを、Connectはアドバタイズが条件を満たすかを返す関数を引数に与えれば動きます。
Characteristicの取得
ドキュメントを読むと、サービス(UUID:cba20d00-224d-11e6-9fb8-0002a5d5c51b)のキャラクタリスティック(UUID:cba20002-224d-11e6-9fb8-0002a5d5c51b、書き込み用)にREQ messageを書き込むといいよと書いてあるので、それを実装します。
client.DiscoverServicesを用いてサービスを取得し、client.DiscoverCharacteristicsを用いてキャラクタリスティックを取得します。
同時にもう一つのキャラクタリスティック(UUID:cba20003-224d-11e6-9fb8-0002a5d5c51b、Notify用)も取得しておくと、実行結果を取得できます。このキャラクタリスティックはNotifyを受け取るときに使います。
Characteristicへの書き込み
ClientのWriteCharacteristic(c *Characteristic, value []byte, noRsp bool) errorを用いてREQ messageを書き込みます。
cには先ほど取得した書き込み用のキャラクタリスティックを指定します。
valueにはドキュメントの通りにバイト列を設定します。
- ボットが"押す"モードの時
value := []byte{0x57, 0x01, 0x00}
- ボットが"スイッチ"モードの時
- オンにする
value := []byte{0x57, 0x01, 0x01}
- オフにする
value := []byte{0x57, 0x01, 0x02}
- オンにする
とすればよいです。
noRspはfalseにしておくとerrorがちゃんと返ってくるのでfalseのほうがいいと思います。
これが正常に実行されれば、SwitchBotがボタンを押すなり引くなりしてくれます。
自分はここまでに3日間かかりました。
暗号化について
Botにはパスワードを設定する機能が備わっています。パスワードが設定されている場合、残念ながら上の方法では操作できません。そこで、この記事を参考にしつつ、Bluetoothのログをとってみることにしました。
なお、自分のボットには2449というパスワードをかけていました(現在変更済み)。
ここから先は自己責任でお願いします。
頑張ってログを読む
CTFをします。
Androidスマホから取り出したログをWiresharkに入れ、それっぽいログを探します。
すると、次のようなログがありました。

Value: 5711bc2bd98501の部分が怪しいです。
元々のREQ messageのことを考えると、0x57、0x11、0xbc2bd985、0x01のように分解できると予想できます。
ここで、0xbc2bd985がなにかしらの方法で暗号化されたパスワードであると予想し、様々な暗号化やエンコードを試せるサイトにパスワードである2449を入れてみると、CRC32による変換がちょうど同じになっています。
実際に、他のパスワードに変換しても同様にCRC32で変換されていることが確かめられます。
これを適切に実装するとパスワードが設定された状態でもスイッチを押すことができます。
エンディアンに注意しないと失敗します(1敗)。
いかがでしたか?
今回はSwitchBotのボットをRaspberryPiから動かしてみました!
12月に試したまま放置していたので、ここで記事にできてよかったです。
GitHubにはスイッチを動かす前に情報を取得してみているので、よかったらそちらもご覧ください!
明日は@kitsneさんによる記事です!お楽しみに~