2015/12/05

[nrf51]Long Write

以前、こんな記事を書いた。
hiro99ma blog: [ble]ATT_MTU

S110のGATT ServerではATTのMTUサイズが23byteになっている、というお話だ。
ATTとしては510byteとか512byteとかまでいけるのだけど、MTUは23byte。
Opecodeが1byteで、残りが22byte。
Authentication Signatureというデータがあるときはそれが12byte固定だから、残りが10bytre。
ない場合は22byteが使える。
(あってるかなぁ)

2byteのWriteをしたときのパケットは、こうだった。

image

残り22byteのうち、AttHandleが2byte取っているので、書込みに使えるデータは20byteになる。

 

20byteまでは1回で済むから速いけど、それを過ぎると簡単にいかない。
Readの場合、nRF51822にデータを書き込んでおくと、SoftDeviceとClientがうまいことやってくれるので、あまり気にしなくて良い。
が、Writeはそうは行かないようだ。


Core_V4.2.pdfの、[Vol 3, Part G]"4.9 Characteristic Value Write"が書込む動作なのだが、1回で通信できないものは"4.9.4 Write Long Characteristic Values"か"4.9.5 Reliable Writes"のシーケンスになる。

どちらも、Clientが"Prepare Write Request"でデータを投げ、Serverが"Prepare Write Response"を返すようになっている。
データの終わりには、Execute Write Request/Responseを投げ合っておしまい。

 

これがnRF51822では、Queued Writesというシーケンスになっている。
GATT Serverのシーケンス一覧では、6つのシーケンスがある。

下2つはちょっと毛色が違うので、省こう。
ハンドルするのがStackかAppか、認証があるかないかで分かれている。

ハンドルがStackかAppかというのは、データを持っているのがSoftDeviceかAppかということのようだ。
いつもは値がどう管理されているかを気にしていなかったのだが、Characteristicを追加するときには「vloc」という設定でできるようだ。
設定値はBLE_GATTS_VLOC_STACKとかBLE_GATTS_VLOC_USERとかで、BDSを使っているとデフォルトではStackが選択されている(is_value_userが0になるので)。

ただ、Queued Writsでは、sd_ble_user_mem_reply()というAPIでの返し方を指すみたい。
ここでNULLを渡すシーケンスが、Appハンドルとなっている。
じゃあStackハンドルの場合はどうかというと、メモリをアプリ側で用意してアドレスを渡してやってる。
つまり、ATT用のメモリとは別に、キューに貯めるメモリがいるということなのか。
だったら先ほどのvlocとは関係がないな。

 

以下、Stackハンドルだけ見ていく。

まず、ClientからPrepare Write Requestが来る。
そしたら、イベントとしてBLE_EVT_USER_MEM_REQUESTが通知される。
SoftDeviceとしては、ユーザのメモリが必要なイベントの1つと見なされているのだろう。
ここでsd_ble_user_mem_reply()でメモリを返すと、ClientにPrepare Write Responseを返す。

それ以降の流れがシーケンスを見てもよくわからない。
Responseを返した次にやってくるのが、handle_2のPrepare Write Requestだからだ。
同じhandleであれば考えやすいのだけど。。

Core_V4.2のWrite Long Characteristicシーケンスを見てみよう。

image

たぶん、引数の1番目がハンドル値、2番目がオフセット値、3番目がデータだろう。
となると、普通は同じハンドルに対して何度もReqとResが往復するはず。
でも、nRF51のシーケンスを見ると、オフセットは1回目と2回目で違っているようだ。
となると、Reliabile Writesのシーケンスの方が近いのかもしれない。

image

これはこれで、2番目の引数が0だ。
うーん・・・。

 

image

面倒なのでやってみると、こうなった。
ClientはNexus5で、アプリとしてNordicのnRF Master Controlとかいう名前のを使っている。

Opcodeの0x16がPrepare Request、0x17がPrepare Response、0x18がExecute Request、0x19がExecute Responseだ。
同じハンドルに対してRequestとResponseが繰り返されているし、オフセットも更新されている。
だから、Write Long Characteristicのシーケンスと同じになっているようだ。
(Core_V4.2シーケンスの引数は、パケットデータの順番そのままっぽい。)

まあ、これはこれでいいのかな?


さて、ちょっとした問題があるのだ。

最後のデータを投げたあとにExecute Write RequestをClientが送るのだが、そこにはハンドル値が載ってない。
だから、アプリとしてはどのCharacteristicの書き込みが終わったかわからないのだ。

もちろんNordicのシーケンスでも同じだ。
ClientからExecute Write Requestを受けとると、まずSoftDeviceが管理しているATTテーブルを更新し、それからBLE_GATTS_EVT_WRITEというイベントで通知している。
これは、通常の書き込みを行ったときと同じイベント名だ。

書き込みを行ったときのイベントは、BDSではどのCharacteristicのハンドル値と一致するかで比較して、処理を進めている。
だから、ハンドル値が無しにされると、誰から書き込まれたかがわからない。

ble_app_hrs---LongWrite
これはNordicのサンプルなのだけど、書込まれたときにどうこうする処理がないためか、特に何もしていないように見える。

 

そうやって考えると、Prepare Writeをしている間はどのCharacteristicに書込みをしてもよくて、好きなだけ書込み終わったらExecute Writeする、みたいな考え方なのだろうか。
DBのトランザクション中、みたいな。
まあ、ATTテーブルは更新してしまうからロールバックはできないのだが。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。