ハイハイスクールアドベンチャー_m5stack_m5cardputer版
差分
このページの2つのバージョン間の差分を表示します。
両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
ハイハイスクールアドベンチャー_m5stack_m5cardputer版 [2024/10/29 02:20] – [ビルド] araki | ハイハイスクールアドベンチャー_m5stack_m5cardputer版 [2025/04/25 01:56] (現在) – [BTキーボードをつなごう] araki | ||
---|---|---|---|
行 47: | 行 47: | ||
ソースは、GitHubから取得して、Platform IOのプロジェクトとしてビルドする。 | ソースは、GitHubから取得して、Platform IOのプロジェクトとしてビルドする。 | ||
- | < | + | < |
$ git clone https:// | $ git clone https:// | ||
</ | </ | ||
行 86: | 行 86: | ||
Core/ | Core/ | ||
- | < | + | < |
cfg.clear_display = true; | cfg.clear_display = true; | ||
M5.begin(cfg); | M5.begin(cfg); | ||
行 119: | 行 119: | ||
CoreはWireでINTR=5だが、Core2はWire1でINTR=33だ。 | CoreはWireでINTR=5だが、Core2はWire1でINTR=33だ。 | ||
- | < | + | < |
class M5StackKeyBoard : public KeyBoard | class M5StackKeyBoard : public KeyBoard | ||
{ | { | ||
行 233: | 行 233: | ||
JIS配列に対応させたい場合はこれを書き換えることになる。 | JIS配列に対応させたい場合はこれを書き換えることになる。 | ||
- | < | + | < |
const uint8_t BTKeyBoard:: | const uint8_t BTKeyBoard:: | ||
{ 0, | { 0, | ||
行 266: | 行 266: | ||
手抜きの極みのキー処理である。 | 手抜きの極みのキー処理である。 | ||
キーボードからの通知が来るたびに、これが呼び出される。 | キーボードからの通知が来るたびに、これが呼び出される。 | ||
+ | Handle == 41は modifiers + 10文字分のキーバッファだが Handle == 29は modifiers + res + 6文字分のキーバッファとなっている。 | ||
+ | < | ||
+ | 実はキーボードからのキー入力の通知についてはハンドルで見分けることができる。 | ||
- | データは keyboardReport | + | btstack でBLEキーボードをつないだ時に知ったのだけれど、Notification |
+ | 一つが attribute handle で、もう一つが value handleだ。 | ||
+ | 私が調べたところ、attribute handle == 0x0040 のものがキーボードからのキー入力の通知になる。 | ||
+ | なので、btstack ではそのようにしてふるっているし、NimBLEでもそうするべきだろうと思ったのだが、NimBLERemoteCharacteristic クラスは、value_handleしか取得できない。 | ||
- | < | + | value_handleの方はキーボードによってまちまちの値を返すので、使い物にならない。 |
- | typedef struct __attribute__((__packed__)) | + | |
- | { | + | どうにかして attribute handle を取り出せないものかと NimBLEのコードを掘っていったら、あろうことか、attribute_handleの方は中で捨ててた。 |
- | uint8_t modifiers; | + | |
- | uint8_t keys[10]; | + | よって、NimBLEでは Notification Event Handlerの中でハンドルで処理を分けるやり方はできない、というのが結論である。 |
+ | |||
+ | 動作は単純で、前回渡されたデータと、今回渡ってきたデータとでキーバッファ部分を比べて、新しいのがあったらそれが今回入力されたキーだとみなして、keybufに積んでいく。 | ||
+ | これは、例えば、A, | ||
+ | |||
+ | データは keybufに格納され、あとで、BTKeyBoard:: | ||
+ | |||
+ | |||
+ | < | ||
+ | typedef | ||
+ | | ||
+ | { | ||
+ | uint8_t modifiers; | ||
+ | uint8_t keys[10]; | ||
+ | } k1; | ||
+ | struct __attribute__((__packed__)) | ||
+ | { | ||
+ | uint8_t modifiers; | ||
+ | uint8_t reserved; | ||
+ | uint8_t keys[6]; | ||
+ | uint8_t padding[3]; | ||
+ | } k2; | ||
+ | uint8_t raw[11]; | ||
} keyboard_t; | } keyboard_t; | ||
+ | |||
static keyboard_t keyboardReport; | static keyboard_t keyboardReport; | ||
- | static | + | static |
static void | static void | ||
行 284: | 行 313: | ||
{ | { | ||
// handle: 41 -- key / 51 -- media key | // handle: 41 -- key / 51 -- media key | ||
- | | + | |
{ | { | ||
- | keyboard_t *newKeyReport = (keyboard_t*)pData; | + | |
- | for (int i = 0 ; i < 10 ; i++) | + | case 29: |
+ | case 41: | ||
+ | if (length != 8 && length != 11) return; // not key data interface (maybe) | ||
+ | | ||
+ | int buflen = 6; | ||
+ | uint8_t *buf = keyboardReport.k2.keys; | ||
+ | uint8_t *input = newKeyReport-> | ||
+ | uint8_t mod = newKeyReport-> | ||
+ | if (length == 11) | ||
+ | { | ||
+ | buflen = 10; | ||
+ | buf = keyboardReport.k1.keys; | ||
+ | input = newKeyReport-> | ||
+ | mod = newKeyReport-> | ||
+ | } | ||
+ | | ||
+ | { | ||
+ | uint8_t c = input[i]; | ||
+ | if (c == 0) continue; | ||
+ | if (memchr(buf, | ||
+ | } | ||
+ | memcpy(& | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | == つながるキーボードつながらないキーボード == | ||
+ | |||
+ | BLEキーボードっていっているのにつながるやつとつながらないやつがありまして、なんでだろうと思っていたのです。 | ||
+ | 思えば、最初にテストに使った、バッファローのBSKBB335がつながる方のやつだったので、深く考えないで、かつさっさと実装を終えられたのですが。 | ||
+ | |||
+ | それでもつながらないのがあるというのが気になります。 | ||
+ | BLEの実装に関しては、ペリフェラル側((サーバー側))に多く、セントラル側((クライアント側))については、ほぼ、NimBLEのサンプルとその亜種しかないのです。 | ||
+ | |||
+ | ハイハイスクールアドベンチャーについても、もちろん、亜種の一つです。 | ||
+ | |||
+ | 通知可能のCharacteristicは端から notfy()をかけてみても、まったく一つも callbackを返してきません。 | ||
+ | 悶々とします。 | ||
+ | |||
+ | で、gatttool を使ってもう少し掘ってみることにしました。 | ||
+ | |||
+ | <code bash> | ||
+ | $ gatttool -I -b xx: | ||
+ | ... | ||
+ | [xx: | ||
+ | Error: Read characteristics by UUID failed: Encryption required before read/ | ||
+ | [xx: | ||
+ | </ | ||
+ | |||
+ | え、これって、セキュアな接続をはらないとダメってことですか? | ||
+ | |||
+ | なんで、コードにごそごそと追加を行います。 | ||
+ | |||
+ | <code cpp> | ||
+ | pClient = NimBLEDevice:: | ||
+ | Serial.println(" | ||
+ | pClient-> | ||
+ | pClient-> | ||
+ | pClient-> | ||
+ | if (!pClient-> | ||
{ | { | ||
- | | + | |
- | | + | |
- | | + | |
} | } | ||
- | | + | |
+ | { | ||
+ | Serial.println(" | ||
+ | return false; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | そうそう。 | ||
+ | 初期化もちょいと変えてやります。 | ||
+ | |||
+ | <code cpp> | ||
+ | NimBLEDevice:: | ||
+ | NimBLEDevice:: | ||
+ | NimBLEDevice:: | ||
+ | NimBLEScan *pScan = NimBLEDevice:: | ||
+ | pScan-> | ||
+ | pScan-> | ||
+ | pScan-> | ||
+ | pScan-> | ||
+ | pScan-> | ||
+ | while (pScan-> | ||
+ | { | ||
+ | sleep(100); | ||
} | } | ||
+ | </ | ||
+ | |||
+ | NimBLEDevice:: | ||
+ | これで無事につながらなかったキーボードがつながるようになりました。 | ||
+ | |||
+ | == キーボードが再接続できない == | ||
+ | |||
+ | BLEキーボードを接続した状態でしばらく何もしないで放置していると、キーボード側がパワーセーブモードに入って接続が切れます。 | ||
+ | クライアント側には onDisconnect() の通知が送られて、接続が切れたことが認識されます。 | ||
+ | |||
+ | ここで、デバイスの再検索を始めて、打鍵するなどして復帰したキーボードから Advertiseがやってくればめでたく再接続になります。 | ||
+ | |||
+ | NimBLE のサンプルやそれを参考にした多くのコードが、次のような実装をしていたので私もそうしていました。 | ||
+ | |||
+ | <code cpp> | ||
+ | void | ||
+ | ClientCallbacks:: | ||
+ | { | ||
+ | Serial.print(pClient-> | ||
+ | Serial.println(" | ||
+ | NimBLEDevice:: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | 一見すると何の問題もありません。 | ||
+ | 実際、シリアルコンソールにも | ||
+ | 一向にデバイスが再接続されないのです。 | ||
+ | 困った。 | ||
+ | これでは、一気通貫にハイハイスクールアドベンチャーを最後までプレイするのでなければ、ゲームの途中でにっちもさっちもいかなくなってしまいます。 | ||
+ | |||
+ | さて、原因はなんでしょう? | ||
+ | もしかすると、一度接続したデバイスからの Advertiseを無視しているのかもしれません。 | ||
+ | ならば <code cpp> | ||
+ | 再接続はうまくいかないし、そもそも、最初の接続が完了するまで何度も Advertiseのコールバックがかかってきてしまいました。 | ||
+ | 筋違いのようです。 | ||
+ | |||
+ | じゃあ、なんなんだろう? | ||
+ | |||
+ | ふと、callbackの中から NimBLEDevice:: | ||
+ | これ、つまりコールバック処理が完了しなくなって、おかしなことになっている可能性があるんじゃないかと。 | ||
+ | |||
+ | で、次のようにしました。 | ||
+ | |||
+ | <code cpp> | ||
+ | static volatile bool connected = false; | ||
+ | |||
+ | void | ||
+ | ClientCallbacks:: | ||
+ | { | ||
+ | Serial.print(pClient-> | ||
+ | Serial.println(" | ||
+ | connected = false; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | で、メイン処理の中で、 | ||
+ | |||
+ | <code cpp> | ||
+ | if (!connected) | ||
+ | { | ||
+ | Serial.println(" | ||
+ | NimBLEDevice:: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | という処理を足してやりました。 | ||
+ | |||
+ | 結論。 | ||
+ | |||
+ | あっさり再接続しました_ノ乙(、ン、)_ | ||
+ | |||
+ | < | ||
+ | Connect to BLE Keybord. | ||
+ | Advertised HID Device found: Name: Notepad8, Address: XX: | ||
+ | Start connecting to server... | ||
+ | A new client created. | ||
+ | BLE Device connected. | ||
+ | Connected to XX: | ||
+ | RSSI: -60 | ||
+ | Done. | ||
+ | XX: | ||
+ | Start scan. | ||
+ | Advertised HID Device found: Name: Notepad8, Address: XX: | ||
+ | Start connecting to server... | ||
+ | BLE Device connected. | ||
+ | Reconnected. | ||
+ | Connected to XX: | ||
+ | RSSI: -62 | ||
+ | Done. | ||
+ | </ | ||
+ | |||
+ | == キーバッファ == | ||
+ | |||
+ | キーバッファの仕様は何種類か存在しているようである。 | ||
+ | |||
+ | 最も多い実装は8bytesで、modifiers + padding + key buffer (6bytes) という構成で、手元ではバッファローのBSKBB335だけが11bytes で modifiers + key buffer (10bytes)というものである。 | ||
+ | |||
+ | キーバッファのサイズだとか、多分アラインメントを考慮したであろうパディングの有無や全体長などの差異はあれど、渡ってくる情報(modifiersやキーコード)に違いはない。 | ||
+ | |||
+ | が、キーバッファーの部分の扱いには、解釈違いのものがあるようである。 | ||
+ | |||
+ | 当初、試したキーボード((BSKBB335/ | ||
+ | なので、常にキーが押されればキーバッファの先頭にはキーコードがあるという想定だった。 | ||
+ | |||
+ | 他のキーボードでもそれで問題は特にはなかった。 | ||
+ | |||
+ | が、ちょっと色気(?)を出して、シフトキーを押したりコントロールキーを押したりしたときにそれは起きた。 | ||
+ | |||
+ | 文字が入力されない。 | ||
+ | |||
+ | 仕方がないので、デバッグです。 | ||
+ | 調べてみたら、キーは渡ってきてます。 | ||
+ | 但し、なぜか二文字目で先頭は0です。 | ||
+ | どうやら、キーボードによっては、modifiers が押されたのもキーバッファを消費する((ただしよりにもよってキーコードは0))ようです。 | ||
+ | なので、0ならそこで打ち切りとしていた処理を、0なら飛ばすように変更しました。 | ||
+ | |||
+ | == NimBLE 2.2.3 == | ||
+ | |||
+ | NimBLEが1.x系から2.x系になりました。 | ||
+ | メモリの使用量などが改善しているという触れ込みなので、メモリが余裕というわけではないESP32環境では乗り換えないという選択肢はないでしょう。 | ||
+ | |||
+ | 移行の大筋は[[https:// | ||
+ | |||
+ | 大きな変更としては、ポインタ渡しだったものが参照渡しや値渡しになっていたり、Advertiseの Callbackを受けるクラスが NimBLEScanCallbacksになっていたりするのと、NimBLEClientCallbacsのイベントハンドラの名前や引数がちょいちょい変わっているのと、あとは時間に関する指定が、sec だったり msだったりしてたのが msに統一されたというあたりでしょうか。 | ||
+ | |||
+ | まあ、intellisenseが効いている VSCode で編集していれば、変更はほぼ機械的に、赤くなっているエラー行を入れ替えていって、あとは時間に関する値の見直しを入れていくくらいでしょうか? | ||
+ | |||
+ | わたしは、NimBLEClient:: | ||
+ | |||
+ | ただ、本当に厄介なのはそういう部分じゃなくて、同じな態でいるのに、実は挙動が違うという奴です。 | ||
+ | 具体的には **NimBLEDevice:: | ||
+ | |||
+ | 1.xでは、この関数を呼び出すと、Advertiseを拾って、stopがかかるまでここでブロックされていました。((だったはずです。キーボードが接続待ちになるまでここで止まっていたので。)) | ||
+ | |||
+ | ところが、2.xではこの関数は即座に戻ってきて処理を続行し、裏で待ち受け処理を行うようになっていました。 | ||
+ | |||
+ | 何が問題なのかというと、Scan Stopがかかって、接続可能な状態になっている前提で組まれているコードが、Scan中なのにやってきて、しかもまだデバイスが見つかっていないので、管理用のステータスが「未接続」となっているものだから、別のところから再び scan をかけようとするのです。 | ||
+ | |||
+ | 1.xでどうだったかは知りませんが、2.xではこの状況になるとデフォルトでは先行するスキャンでやりかかっていた処理やデータはクリアされて最初からやり直しをするようになっています。 | ||
+ | |||
+ | この時、先行するスキャンがデバイスを見つけて、データに触り始めている状態だと、触っているデータがクリアされてしまい例外を吐いて落ちてしまうのです。 | ||
+ | |||
+ | 当然、この時どこを触っているのかはその時々で違うので、backtrace を手繰ってもその都度違うところで例外が出るので最初原因が何なのかわからずに大変困惑しました。 | ||
+ | |||
+ | 対処として、start()をかけうるコードの前に、NimBLEDevice:: | ||
+ | |||
+ | また、初期化部分では、スキャンが終わるまでループするようにも修正しました。 | ||
+ | |||
+ | |||
==== 画面のスケーリング ==== | ==== 画面のスケーリング ==== | ||
行 350: | 行 608: | ||
その場合自力でビルドして置き換えておく。 | その場合自力でビルドして置き換えておく。 | ||
- | < | + | < |
$ git clone --recursive https:// | $ git clone --recursive https:// | ||
$ cd mkspiffs | $ cd mkspiffs |
ハイハイスクールアドベンチャー_m5stack_m5cardputer版.1730168417.txt.gz · 最終更新: 2024/10/29 02:20 by araki