ユーザ用ツール

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
bleキーボードをつなごう_btstack編 [2025/04/22 01:44] – [Descriptor一覧の取得とNotificationの要求] arakibleキーボードをつなごう_btstack編 [2026/05/26 23:45] (現在) – [処理の流れ] araki
行 26: 行 26:
 言語は C++を使用する。 言語は C++を使用する。
  
 +なお、この文書やプログラムの作成に当たっては[[https://vanhunteradams.com/Pico/BLE/GATT_Client.html|Building a Bluetooth GATT Client on the Pi Pico W]]および[[https://github.com/bluekitchen/btstack/tree/501e6d2b86e6c92bfb9c390bcf55709938e25ac1|btstack-1.6.2]]のサンプルプログラムを参考にした。
 +
 +また、Copilot、Gemini、およびLM Studio上の Gemma3 12BなどAIによる支援も利用した。
 +
 +完全なコードは[[https://github.com/wildtree/HHSAdvPico.git|ハイハイスクールアドベンチャー PicoCalc版]]に含まれている。
 ===== 処理の流れ ===== ===== 処理の流れ =====
  
行 40: 行 45:
 この時、デバイスによっては接続を暗号化してセキュアにする必要がある場合がある。 この時、デバイスによっては接続を暗号化してセキュアにする必要がある場合がある。
 キーボードの場合は、その性質上打鍵情報を傍受される危険性があるためか、セキュアであることを要求しているものが少なくない。 キーボードの場合は、その性質上打鍵情報を傍受される危険性があるためか、セキュアであることを要求しているものが少なくない。
-手元にあるキーボードでは、バッファロー製のものと、Capdputerを購入したときに入っているサンプルのBLEキーボード化アプリがセキュア接続を要求しない((前者はセキュアでもOKだが後者はセキュア接続がそもそもできない))が、他はすべてセキュア接続を要求する。+手元にあるキーボードでは、バッファロー製のものと、Cardputerを購入したときに入っているサンプルのBLEキーボード化アプリがセキュア接続を要求しない((前者はセキュアでもOKだが後者はセキュア接続がそもそもできない))が、他はすべてセキュア接続を要求する。
 この要件は、キー入力を通知する Characteristic の属性に記されており、セキュア接続を必須とする場合に、セキュアでない接続上でキー入力のデータを要求しても要求が失敗する。 この要件は、キー入力を通知する Characteristic の属性に記されており、セキュア接続を必須とする場合に、セキュアでない接続上でキー入力のデータを要求しても要求が失敗する。
  
行 518: 行 523:
 それでも globalに staticなリストとして保存するのは、これも例によって、一つの要求を出したらそれが COMPLETEするのを待たねばならないためである。 それでも globalに staticなリストとして保存するのは、これも例によって、一つの要求を出したらそれが COMPLETEするのを待たねばならないためである。
  
 +=== CCCDの検索とNotificationの要求 ===
 +
 +全ての descriptorがわたし終わると、GATT_EVENT_QUERY_COMPLETEが発生する。
 +hid_descriptorsを走査して、UUID16 == 0x2902 の descriptorを探し、そこに Notification要求を書き込む。
 +
 +notification_enableはバイト型の配列で 0x01, 0x00 の順でデータが書き込まれている。
 +GATTのデータは little endian でやり取りされるので、この順で書き込む必要がある。
 +勿論、手元の処理系が little endianである場合には普通に 0x0001の uint16_tデータを渡しても構わないが、汎用化するためにこのようにしてある。
 +
 +gatt_client_write_value_of_characteristic()には handle_gatt_noification_activated()をイベントハンドラとして登録し、そちらで完了の処理を行う。
 +
 +== 処理の続きの部分について ==
 +
 +ここまでは、要求を出したら原則処理はそこで終わりだったが、ここでは続きの部分がある。
 +Descriptorを最後まで捜査して、CCCDが見つからなかった場合には、次のCharacteristicに対して gatt_client_discover_characteristic_descriptors()を要求しなければならない。
 +なので、hid_descriptorsをクリアして、再取得に備える。
 +
 +hid_characteristics_it が hid_characteristeics.end()に到達していたら、もう必要な処理はないので、おしまいである。
 +
 +=== CCCDに対する要求完了 ===
 +
 +CCCDに対する要求が完了したら GATT_EVENT_QUERY_COMPLETEが発生する。
 +中断したところから次の CCCDを探し、終端まで行ったら次の Characteristicに対して、gatt_client_discover_characteristic_descriptors()をかける。
 +
 +そして、characteristicsも終端まで行ったらおしまいである。
 +
 +=== Notificationの処理登録 ===
 +
 +処理が終端に到達したら、BLEセントラル側で Notificationの受け取り準備をする。
 +
 +<code cpp>
 +gatt_client_listen_for_characteristic_value_updates(&notification_listener, &notification_handler, connection_handle, nullptr);
 +</code>
 +
 +Characteristics一つ一つに対して処理を要求することもできるようだが、ここでは一括で行う。
 +最後の nullptrが、全部の Characteristicsに対しての要求を意味している。
 +
 +これであとは notification_handler でGATT_EVENT_NOTIFICATIONのイベントを処理すれば目的達成である。
  
 <code cpp> <code cpp>
行 524: 行 567:
 static std::list<gatt_client_characteristic_t>::iterator hid_characteristic_it; static std::list<gatt_client_characteristic_t>::iterator hid_characteristic_it;
 static std::list<gatt_client_characteristic_descriptor_t>::iterator hid_descriptor_it; static std::list<gatt_client_characteristic_descriptor_t>::iterator hid_descriptor_it;
 +
 +static uint8_t notification_enable[] = {0x01, 0x00}; // 通知を有効化する値
 +static gatt_client_notification_t notification_listener;
  
 // CCCDにNotificationを要求し完了した // CCCDにNotificationを要求し完了した
行 649: 行 695:
               }               }
               ++hid_characteristic_it; // イテレータを保存               ++hid_characteristic_it; // イテレータを保存
 +            }
 +            if (hid_characteristic_it == hid_characteristics.end())
 +            {
 +              gatt_client_listen_for_characteristic_value_updates(&notification_listener, 
 +                &notification_handler, 
 +                connection_handle, 
 +                nullptr);
             }             }
           }           }
行 659: 行 712:
 } }
 </code> </code>
-==== Notificationの処理準備 ==== 
  
 ==== Notification handler ==== ==== Notification handler ====
  
 +ほぼ蛇足であるが、Notification handlerについても少しだけ記しておく。
 +Notificationについては、キーボードからの入力は 0x0040 のハンドルで渡されるようである。
 +メディアキーなどの特殊キーはさらに別のハンドルとなるがここでは割愛する。
 +
 +ほとんどのキーボードの場合はデータ長は 8bytes で先頭が modifierで、1byteのパディングがあって、6bytesのキーコードがわたされる。
 +
 +但し、バッファローのキーボードで11bytes (modifier + 10bytesのキーコード)というものがあったのでどちらでも扱えるようにデザインしてある。
 +
 +キーコードについては、キーボードごとに違いはないので、キーコードを文字にマッピングしてやって、keybuf というキューにデータを登録している。
 +
 +キーの状態に変化がないと Notificationは来ない。
 +キーが押しっぱなしでは状態が変化したわけではないので、押しっぱなしの通知は来ない。
 +通知がなければ、押しっぱなしとみることで、キーリピートやゲームでのキー処理にも対応できるが、ここではそのあたりの処理はしない。
 +
 +キーを押し続けているときに新しくキーが押されたらその分が新しく押されたキーコードとして渡されるが、その時、押しっぱなしになっているキーも渡されるので、差分を見なければ、新しく押されたキーを見つけることができない。
 +
 +そのため前回渡されたキーバッファの内容を保存している。
 +
 +押しっぱなしのキーが離されたときにも、通知が来るが、この時他に押しっぱなしのキーがあればそれも渡されるため、同様に、新しく押されたキーがあるかどうか、離されただけなのか、というチェックを前回のデータと比較して行う。
 +
 +キーボードについては概ねこのような処理を行っている。
 +
 +
 +<code cpp>
 +typedef union {
 +  struct __attribute__((__packed__))
 +  {
 +      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;
 +
 +static keyboard_t keyboardReport;
 +static std::queue<uint8_t> keybuf;
 +static const int MAX_KEYCODE = 96;
 +const uint8_t keymap[][MAX_KEYCODE] = {
 +  {    0,   0,   0,   0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
 +     'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
 +     '3', '4', '5', '6', '7', '8', '9', '0',  13,  27,   8,   9, ' ', '-', '=', '[',
 +     ']','\\',   0, ';','\'', '`', ',', '.', '/',   0,   0,   0,   0,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 127,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +  },
 +  {
 +       0,   0,   0,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,
 +      13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +  },
 +  {
 +       0,   0,   0,   0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
 +     'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
 +     '#', '$', '%', '^', '&', '*', '(', ')',  13,  27,   8,   9, ' ', '_', '+', '{',
 +     '}', '|',   0, ':', '"', '~', '<', '>', '?',   0,   0,   0,   0,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 127,   0,   0,   0,
 +       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +  },
 +};
 +// 通知を受け取るハンドラ
 +static void 
 +notification_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) 
 +{
 +  if (packet_type != HCI_EVENT_PACKET) return;
 +  switch (hci_event_packet_get_type(packet)) 
 +  {
 +    case GATT_EVENT_NOTIFICATION:
 +      {
 +        // 通知を受信した場合の処理
 +        uint16_t attribute_handle = gatt_event_notification_get_handle(packet);
 +        uint16_t value_length = gatt_event_notification_get_value_length(packet);
 +        const uint8_t *value = gatt_event_notification_get_value(packet);
 +        uint16_t value_handle = gatt_event_notification_get_value_handle(packet);
 +        uint16_t service_id = gatt_event_notification_get_service_id(packet);
 +        if (attribute_handle == 0x0040)
 +        {
 +          if (value_length == 0) return; // データがない場合は無視
 +          if (value_length == 8 || value_length == 11)
 +          {
 +            keyboard_t *newKeyReport = (keyboard_t*)value;
 +            int buflen = 6;
 +            uint8_t *buf = keyboardReport.k2.keys;
 +            uint8_t *input = newKeyReport->k2.keys;
 +            uint8_t mod = newKeyReport->k2.modifiers;
 +            if (value_length == 11)
 +            {
 +              buflen = 10;
 +              buf = keyboardReport.k1.keys;
 +              input = newKeyReport->k1.keys;
 +              mod = newKeyReport->k1.modifiers;     
 +            }
 +            for (int i = 0 ; i < buflen ; i++)
 +            {
 +              uint8_t c = input[i];
 +              if (c == 0) continue;
 +              if (mod == 3) mod = 1;
 +              uint8_t ch = keymap[mod][c];
 +              if (ch == 0) continue;
 +              if (memchr(buf, c, buflen) == NULL) keybuf.push(ch);
 +            }
 +            memcpy(&keyboardReport, value, value_length);
 +          }
 +        }
 +      }
 +      break;
 +    default:
 +      break;
 +  }
 +}
 +</code>
  
  

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information