2025年1月28日

023-1.SPIKEプライム Bluetooth入門-第2回「サンプルプログラムのバグを取る」

この連載ではBluetooth Low Energy(ブルートゥース・ロー・エナジー。以下、Bluetooth LE)という機能を使って、「レゴ エデュケーションSPIKEプライム(以下、SPIKE)」のラージハブと通信する方法について紹介します。この技術を応用すると、パソコンからラージハブにプログラムを送ったり、パソコンでセンサーの測定値を受け取ったり、いろいろなことができるようになります。なお、この記事ではWindows10以上のパソコンを推奨しています。(文/松原拓也)

◆ ラージハブとの接続

前回はレゴ グループの公式資料「LEGO Education SPIKE Prime protocol documentation」に付いてくるサンプルプログラム(examples/python/~)を実行しました。
https://github.com/LEGO/spike-prime-docs/
今回も同じサンプルプログラムを使います。プログラムの動かし方については、前回の記事を参考にしてください。

まずはパソコンからラージハブへ接続するコードを紹介します。
サンプルプログラムでは、bleakモジュール(ライブラリ)の「BleakScanner」というクラスを使って接続を行います。
ソースファイル「app.py」をThonnyのエディタで開いて確認します。 「find_device_by_filter」という関数に接続したいラージハブのUUIDを指定しています。UUIDは「Universally Unique IDentifier」の略で、デバイスの種類ごとに割り当てているIDのことです。

UUIDは「Service」「RX」「TX」がありますが、ここでは「Service」を探します。ラージハブのServiceのUUIDは「0000FD02-0000-1000-8000-00805F9B34FB」です。
接続に成功すると、ラージハブのMACアドレス(12桁の識別番号)を取得することができます。MACアドレスはラージハブごとに違うので、他人のラージハブと見分けることができます。

Bluetooth LEで通信するデータのことを「メッセージ」といいます。SPIKEのメッセージはレゴ グループの公式資料「SPIKEプライム・プロトコル」で仕様が決められています。
https://lego.github.io/spike-prime-docs/messages.html
この資料は重要です。

◆ バグを突き止める

「LEGO Education SPIKE Prime protocol documentation」は、2024年3月に公開されましたが、サンプルプログラムにバグがあります。2024年10月現在まで、8か月以上も更新されていません。そこで、自分でバグ取り(デバッグ)をしてみましょう。単に修正する方法を書くだけでは勉強にならないので、解決策を見つけるまでの考え方を紹介します。
将来、プログラムが更新されて、バグがなくなると思いますが、その場合はココに書いてある作業は不要になります。

まずはどういう不具合があるのか? チェックしてみましょう。
ラージハブにカラーセンサーだけを接続した状態で、サンプルプログラム(app.py)を実行します。 本来はカラーセンサーの測定値が表示されるはずですが、 測定値が表示されません。このようなエラーが表示されてしまいます。

エラーの内容をよく読んでみましょう。
エラーが発生しているファイルと行数が表示されています。
一番下に「struct」と書かれています。structモジュールで発生しているエラーだとわかります。
「unpackには2バイトのバッファが必要です」と英語で書かれています。何かのバッファ(データ)が2バイト必要だったところ、それが足りずにエラーが出ていることがわかります。

突然ですが、以前に連載していた「ロボコン ヒント集」の内容を思い出してみましょう。あの連載では「見える化することの大切さ」を何度も紹介していました。今回も「見える化」で解決します。
ソースファイル「app.py」を開いて、 「data = cobs.unpack(data)」という一文を探します。 「unpack」はデータを解読可能な状態に変換するための関数です。ここで受信したメッセージを「data」という変数に格納しています。
この一行下に「print(data.hex())」を付け足します。メッセージを監視して、エラーの原因を特定します。

プログラム(app.py)を実行すると、ラージハブからのメッセージが16進数で表示されるようになります。
「SPIKEプライム・プロトコル」を読みながら、メッセージを解読してみましょう。 たとえば、「20」で始まるメッセージは「ProgramFlowNotification」です。「21」で始まるメッセージは「ConsoleNotification」です。これらは正常に動作しているので、バグとは無関係のようです。

エラー発生直前のメッセージは「3c」で始まっています。
調べてみると、「DeviceNotification」のメッセージだとわかります。 「DeviceNotification」はバッテリー残量やセンサーやモーターに関するデータです。 データの構造が2重になっていて、センサーやモーターの接続数によってサイズが変わります。
「DeviceNotification」を受信するには、事前に「DeviceNotificationRequest」というメッセージをラージハブに送ってから、「DeviceNotificationResponse」というメッセージを受信して、データを送る周期を設定する必要があります。サンプルプログラムでは周期は5秒に設定されています。

カラーセンサーだけを接続している時の「DeviceNotification」は次のとおりです。
・Device5x5MatrixDisplay(ライトマトリクスの情報。26バイト)
・DeviceImuValues(IMUの情報。21バイト)
・DeviceBattery(バッテリー残量の情報。2バイト)
・DeviceColorSensor(カラーセンサーの情報。9バイト)
、、、最後に「余分な1バイト」があります。これは一体、何でしょうか?この1バイトのせいで「バッファが必要(足りない)」のエラーが出ているのではないでしょうか?
あと、RGBの値の範囲は0~1023だと書かれていますが、実際は0~65535だと思います。

再度、条件を変えて実行してみましょう。カラーセンサーとモーターを接続している時の「DeviceNotification」がこちらです。
今度はエラーが出なくはなりましたが、おかしなところだらけです。モーター情報が表示されず、バッテリー情報が2つ表示されています。これは、片方はニセモノ。DeviceColorSensorのあとに「余分な1バイト」があって、モーター情報がバッテリー情報に化けてしまっています。
バグの正体は「余分な1バイトが発生していること」と結論付けました。

◆ プログラムを修正する

原因がわかったので、対策してみましょう。 ソースファイル「messages.py」の「DEVICE_MESSAGE_MAP」という配列にDeviceColorSensorのデータ構造が書かれています。structモジュールのフォーマット文字という書式です。これによると、DeviceColorSensorのデータ構造は次のとおりです。
・ID、0x0c(1バイト)。
・接続しているポート番号(1バイト)。
・認識した色の番号。-1の場合は認識失敗(1バイト)。
・赤色の値(2バイト)。
・緑色の値(2バイト)。
・青色の値(2バイト)。
、、、このデータ構造自体には間違いはありません。しかし、ここで「余分な1バイト」を受け取ることで、データ化けを防ぐことができるのではないでしょうか?

ソースファイル「messages.py」を修正して、DeviceColorSensorの最後に1バイトを追加します。データのサイズを9バイト→10バイトに変更します。
0x0C: ("Color", "<BBbHHH"),

0x0C: ("Color", "<BBbHHHB"),
、、、「B」の一文字を付け足すだけです。

「app.py」を実行してみましょう。5秒おきに測定値が更新されます。
見事、カラーセンサーの測定値が表示されるようになりました。そして、エラーが出なくなりました。 測定値は「Color:(デバイス番号, 接続したポート, 認識した色, 赤色, 緑色, 青色, 0)」の形式で表示されます。
正しい対策方法かどうかはわかりませんが、これでバグ取りは完了とします。

なお、この連載の記事ですが、あまりにも覚えることが多すぎて、文字を読んでいるだけだと、頭に入りません。自分の手を動かして、実際に通信をしてみると、面白さが伝わってきて、理解できるようになると思います。
SPIKEの基本セットとBluetoothを搭載したパソコンがあれば、すぐに実験できますので、試してみましょう。

当ブログの内容は、弊社製品の活用に関する参考情報として提供しております。
記載されている情報は、正確性や動作を保証するものではありません。皆さまの創意工夫やアイデアの一助となれば幸いです。