MicroPythonのBluetoothについて調べてみた

# はじめに
MicroPythonのBluetooth通信について気になったのでまとめてみます

## Bluetoothとは
Bluetoothは近距離　無線通信規格の一つです

Bluetoothの利点
- 省電力
- アップグレードが容易
- 赤外線通信よりも広範囲
- 音声とデータの転送ができる
- 使用できるデバイスが安価


Bluetoothの欠点
- Wi-Fiに比べて帯域幅が狭い
- デバイス間の短距離通信のみ可能
- 屋内環境において電波の減衰が起こる

### BLE(Bluetooth Low Energy)とは

Bluetoothの一部で，バージョン4.0から追加された低消費電力の通信規格です．電池寿命の延長を目的として，無線部分の消費電力削減がされています．このおかげでボタン電池で複数年稼働するため，IoTデバイスの通信方式で注目されています．


## MicroPythonのBluetooth


Bluetoothモジュールはボード上のBluetoothコントローラーへのインターフェースを提供していて，Bluetooth Low Energy(BLE)をサポートしています．セントラル(Central)，ペリフェラル(Peripheral)，ブロードキャスター(Broadcaster)，オブザーバー(Observer)のロールがあり，GATTサーバ/クライアントロール，L2CAP connection-oriented-channelsもあります．

- セントラルとペリフェラルの関係![qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650453/fb984269-fbbd-4a5c-f9aa-f6d73ee86d3c.png)



### クラスBLE

##### コンストラクタ
### `class bluetooth.BLE`
- BLEオブジェクトを返す

#### 設定

### ` BLE.active( [active,] /) `
- オプションでBLE無線のアクティブ状態を変更し，現在の状態を返す

### `BLE.config('param'./)`
### `BLE.config(*,param=value,...)`
- BLEインターフェイスの設定値を取得or設定
- パラメーター名を文字列として一度に１つだけ指定する
- 値を設定する場合には，キーワード構文を使って，一度に1つ以上のパラメーターを設定できる

| 使える値 | `'addr_mode'` | `'mac'`|  `'gap_name'`|
|:-:|:-:|:-:|:-:|
|出来る事 | アドレスモードを設定 |  現在利用中のアドレスを取得 | サービス0x1800，キャラクタリスティック0x2a00で利用するGAPデバイス名を取得/設定|
|戻り値   | |(addr_type, addr)  |


### ブロードキャスターロール(アドバタイザー)：送信側
#### `BLE.gap_advertise(interval_us,adv_data=None,*,resp_data=None,connectable=True)`
- 指定された間隔(マイクロ秒単位)でアドバタイジング(Advertising)を開始する
- アドバタイジングを停止するには`interval_us`をNoneに設定する
- `adv_data`,`resp_data`はバッファプロトコルを実装する任意のタイプにできる(`bytes`,`bytearray`,`str`)
- `adv_data`はすべてのブロードキャストに含まれ，`resp_data`はアクティブスキャンへの返信として送信される

### オブザーバ―ロール(スキャナー)：受信側
#### `BLE.gap_scan(duration_ms,interval_us=1280000,window_us=11250,active=False,/`)
- 指定された期間(ミリ　秒単位)持続するスキャン操作を実行する
- 時間していないしでスキャンするときは`duration_ms`を0に設定する
- スキャンを停止するに`duration_ms`をNoneに設定する
- スキャン結果毎に`_IRQ_SCAN_RESULT`イベントが発生する，イベントデータは`(addr_type, addr, adv_type, rssi, adv_data)`
- `addr_type`の値はPUBLICまたはRANDOMアドレスであるかを示す
  - 0x00 - PUBLIC
  - 0x01 - RANDOM(RANDOM,RPA,NRPAのいずれかで，どれであるかはアドレス自体にエンコードされている) 
  
- `'adv_type'`  の値は次のBluetooth仕様に対応している
  - 0x00 - ADV_IND - 接続/スキャン可能な無向アドバタイジング
  - 0x01 - ADV_DIRECT_IND - 接続可能な有向アドバタイジング
  - 0x02 - ADV_SCAN_IND - スキャン可能な無向アドバタイジング
  - 0x03 - ADV_NONCONN_IND - 接続不可能な無向アドバタイジング
  - 0x04 - SCAN_RSP - スキャン応答
- スキャンの応答を結果として受け取りたい場合は，`active`をTrueにする

### セントラルロール
セントラルデバイスは，オブザーバーロールを使うか，既知のアドレスで検出したペリフェラルに接続できる
#### `BLE.gap_connect(addr_type,addr,scan_duration_ms=2000,min_conn_interval_us=None,max_conn_interval_us=None,/)`

- ペリフェラルに接続する
- 未処理の接続試行を早期にキャンセルするには`gap_connect(None)`を呼び出す
- 接続に成功すると`_IRQ_PERIPHERAL_CONNECT`イベントが発生する．接続の試行をキャンセルすると`_IRQ_PERIPHERAL_DISCONNECT`イベントが発生する
- デバイスからのアドバタイジングペイロードを受信するまでscan_duration_msまで待機する
- 接続間隔はmin_conn_interval_usおよびmax_conn_interval_usのいずれかまたは両方を使ってマイクロ秒単位で設定できる．それ以外の場合は，通常30000~50000マイクロ秒の間でデフォルトの間隔が選択される．間隔を短くするとスループットが向上するが消費電力は増加する

### ペリフェラルロール
ペリフェラルデバイスは，接続可能なアドバタイズを送信することできる．通常は`gatts_register_serveces`を用いて最初に登録されたサービスとキャラクタリスティックを持つGATTサーバとして動作する．
セントラルが接続すると`_IRQ_CENTRAL_CONNECT'イベントが発生する

### セントラル&ペリフェラルロール
#### `BLE.gap_disconnect(conn_handle,/)`
- 指定した接続ハンドル(接続の識別)を切断する
- (ペリフェラルとして動作している)このデバイスに接続したセントラル，または(セントラルとして動作している)このデバイスに以前に接続されていたペリフェラルのいずれかになる
- 切断に成功すると`_IRQ_PERIPHERAL_DISCONNECT`または`_IRQ_CENTRAL_DISCONNECT`イベントが発生する
- 接続ハンドルが接続されていない場合は`False`を，そうでない場合は`True`を返す

### GATTサーバ
- GATTサーバには登録されたサービスのセットがある
- 各サービスにはキャラクタリスティックが含まれていることがあり，各キャラクタリスティックは値を持つ
- キャラクタリスティックには，それ自体に値を持つ**ディスクリプタ**を含めることもできる
- 値はローカルに保存され，サービス登録中に精製される「値ハンドル(value handle)」によってアクセスされる．また，リモートのクライアントデバイスから読み書きすることもできる．さらに，ペリフェラルは接続ハンドルを介して接続されたクライアントにキャラクタリスティックを「通知」できる．
- セントラルまたはペリフェラルのいずれかのロールを持つデバイスがGATTサーバとして機能するが，ペリフェラルがサーバとして機能するのが一般的
- キャラクタリスティックとディスクリプタのデフォルト最大サイズは20バイト

##### キャラクタリスティックとは
- ペリフェラル機器がセントラル機器に公開して共有するデータ構造
- ペリフェラル機器とセントラル機器はこのキャラクタリスティックを介してデータのやり取りを行う

#### `BLE.gatts_register_services(services_definition,/)`
- 指定したサービスでサーバを構成し，既存サービスを置換する
- services_definitionはサービスのリストであり，各サービスはUUIDとキャラクタリスティックのリストを含む2項目のタプル
- 各キャラクタリスティックは，UUID，フラグ値，およびオプションのディスクリプタのリストを含む2または3項目のタプル
- 各ディスクリプタはUUIDとフラグ値を含む2項目のタプル

#### `BLE.gatts_read(value_handle,/)`
- このハンドルのためのローカル値(`gatts_write`かリモートクライアントで書き込まれたもの)を読み取る

#### `BLE.gatts_write(value_handle,data,send_update=False,/)`
- このハンドルのローカル値を書き込む．クライアントによって読み取ることができる
- send_updateが`True`の場合，サブスクライブしているクライアントにこの書き込みいついて通知される

#### `BLE.gatts_indicate(conn_handle,value_handle,/)`
- キャラクタリスティックの現在の値を含む指示(indication)要求を接続中のクライアントに送信する
- acknowledgment(あるいはタイムアウトなどの失敗)があった場合，`_IRQ_GATTS_INDICATE_DONE`イベントが発生する

#### `BLE.gatts_set_buffer(value_handle,len,append=False,/)`
- 値の内部バッファサイズをバイト数で設定．受信可能な最大の書き込みが制限される．デフォルトは20
- appendを`True`に設定すると，すべてのリモート書き込みが現在の値に置き換えられるのではなく，追加される
- `gatts_read`を使用すると，読み取り後に値がクリアされる．

### GATTクライアント
GATTクライアントは，リモートのGATTサーバ上のキャラクタリスティックを発見し，読み書きできる
セントラルロールを持つデバイスがGATTクライアントとして動作するのが一般的ですが，接続したセントラルについての情報を発見するためにペリフェラルがクライアントとして動作することも可能

#### `BLE.gattc_discover_servives(conn_handle,uuid=None,/)`
- サービスについて接続されたサーバを問い合わせる
- オプションで照会するサービスのUUIDを指定できる
- 検出された各サービスについて`_IRQ_GATTC_SERVICE_RESULT`イベントが発生し，検索が完了すると`_IRQ_GATTC_SERVICE_DONE`になる

#### `BLE.gattc_discover_characteristics(conn_handle,start_handle,end_handle,uuid=None,/)`
- 指定した範囲のキャラクタリスティックについて接続されたサーバを問い合わせる
- オプションで照会するキャラクタリスティックのUUIDを指定できる
- 任意のサービスのキャラクタリスティックを検索するには`start_handle_=1`,`end_handle=0xffff`とする
- 検出された各キャラクタリスティックについて`_IRQ_GATTC_CHARACTERISTIC_RESULT`イベントが発生し，検索が完了すると`_IRQ_GATTC_CHARCTERISIC_DONE`になる

#### `BLE.gattc_discover_descriptors(conn_handle,start_handle,end_handle,/)`
- 指定した範囲のディスクリプタについて接続されたサーバを問い合わせる
- 検出された各ディスクリプタについて`_IRQ_GATTC_DESCRIPTOR_RESULT`イベントが発生し，検索が完了すると`_IRQ_GATTC_DESCRIPTOR_DONE`になる

#### `BLE.gattc_write(conn_handle,value_handle,data,mode=0,/)`
- 指定したキャラクタリスティックあるいはディスクリプタについて接続されたサーバにリモート書き込みを発行する
- 引数`mode`は書き込み動作を指定する．現在サポートされているのは以下
  - `mode=0`(デフォルト)は応答なしの書き込み．書き込みはリモートサーバに送信されるが，確認は返されず，イベントは発生しない
  - `mode=1`は応答付きの書き込み．リモートサーバはデータを受信したという応答/承認を送信するように要求される

#### `BLE.gattc_exchange_mtu(conn_handle,/)`
- `BLE.config(mtu=value)`で設定した優先MTUを使って，接続されているサーバとのMTU交換を開始する
- MTU交換が完了すると`_IRQ_MTU_EXCHANGED`イベント発生する

### L2CAP connection-oriented-channels
2つのBLEデバイス間でのソケットのようなデータ交換が可能になる．デバイスがGAPを介して接続されると，どちらかのデバイスは他方のデバイスがPSM(Protoclo/Service Multiplexer)番号でリスンできる．

アクティブなL2CAPチャネルは，確率された接続ハンドルとCID(チャネルID)によって識別される．

#### `BLE.l2cap_listen(psm,mtu,/)`
- 指定の`psm`と`mtu`に指定のローカルMTUで，L2CAPチャネル要求の着信のためのリスンを開始する
- リモートデバイスが接続を開始すると`_IRQ_L2CAP_ACCEPT`イベントが発生し，リスン中のサーバに着信接続を(0以外の整数を返すことで)拒否する機会が与えられる
- 接続が受け入れられると`_IRQ_L2CAP_CONNECT`イベントが発生し，サーバがチャネルID(CID)とローカルおよびリモートのMTUを取得できるようになる

#### `BLE.l2cap_connect(conn_handle,psm,mtu,/)`
- `mtu`に設定したローカルMTUで，指定した`psm`上のリスニングピアに接続する
- 接続に成功すると`_IRQ_L2CAP_CONNECT`イベントが発生し，クライアントはCID
- CIDとローカルおよびリモートの(ピア)MTUを取得できるようになる
- 接続に失敗すると`_IRQ_L2CAP_DISCONNECT`イベントが発生し，ステータスは0以外になる

#### `BLE.l2cap_disconnect(conn_handle,cid,/)`
- 指定した`conn_handle`と`cid`を持つアクティブなL2CAPチャネルを切断する

#### `BLE.l2cap_send(conn_handle,cid,buf,/)`
- `conn_handle`と`cid`で識別されるL2CAPチャネル上で，指定した`buf`(バッファプロトコルをサポートしている必要アリ)を送信する
- 指定されたバッファは，リモート(ピア)MTUよりも大きく，ローカルMTUの2倍以下のサイズにすることはできない
- これはチャネルがストールしている場合に，`False`を返す．この状態では`_IRQ_L2CAP_SEND_READY`イベントを受信するまで(リモートデバイスがより多くのクレジットを付与したとき，通常はデータ受信して処理した後に発生する)，`l2cap_send`を再度呼び出してはならない

#### `BLE.l2cap_recvinto(conn_handle,cid,buf,/)`
- 指定した`conn_handle`および`cid`から，指定した`buf`にデータを受信する
- チャネルから読み込んだバイト数を返す
- `buf`がNoneの場合，利用可能なバイト数を返す

# まとめ
MicropythonでBluetoothやろうとするといろいろできそうです．Bluetooth通信に関する用語が全然わかりませんでしたが，少しだけ理解することが出来ました．

参考にしたサイト
- [L2CAPとは - 意味をわかりやすく](https://e-words.jp/w/L2CAP.html)
- [ubluetooth --- 低レベル Bluetooth - Read the Docs](https://micropython-docs-ja.readthedocs.io/ja/latest/library/bluetooth.html?highlight=bluetooth)
- [【サルでもわかるBLE入門】（２） アドバタイズとGATT通信](https://www.musen-connect.co.jp/blog/course/trial-production/ble-beginner-2/)
- [接続ハンドル - IBM](https://docs.microsoft.com/ja-jp/sql/odbc/reference/develop-app/connection-handles?view=sql-server-ver16)