hiro99ma blog

何か技術的なこと

ncs: Service のテンプレート

2024/11/04

ncs の Service はある程度までテンプレートで作れるんじゃないか、とふと思った。

config.json に設定を書いて node generator.js と走らせれば generated/ の下にヘッダファイルとソースファイルを作る。

まだ対応している Permission などが少ない。

Write without Response はないし、 Indication と Notification の両方設定することもできてしまうし、 Authenticate 関係はまったくない。

データ長やデータのチェックはほぼ呼び元にコールバックして丸投げだが、 DevAcademy BLE のサンプルをちょっと変更すれば使えるようになった。

無駄は多いしエラーチェックも中途半端だが、何もないところから作り始めるよりは楽になるんじゃなかろうか。


今回のテンプレート作成メモ

UUID

BT_UUID_128_ENCODE() を使った変数と、それを BT_UUID_DECLARE_128() に通した恐らく struct bt_uuid* 型の変数を作る。
Advertising で BT_UUID_128_ENCODE() の方を使うことがあった(正しくは接続時のSCAN_RSP PDU)のでヘッダファイルに、BT_UUID_DECLARE_128() はサービス定義の引数で使ったのでソースファイルに置いた。
オリジナルの LBS では両方ともヘッダファイルにあったので参照することがあるのかもしれない。

Read

Characteristic の読込は、要求が来たときに値を読み込んでから値を返すか、イベントや定期更新などで取得済みの変数を作っておいて要求が来たときにその値を返すかというパターンがあると思う。
どちらがよいのかは状況次第だろう。

LBS だとこうなっている(コード)。

static ssize_t read_button(struct bt_conn *conn,
			  const struct bt_gatt_attr *attr,
			  void *buf,
			  uint16_t len,
			  uint16_t offset)
{
	const char *value = attr->user_data;

	LOG_DBG("Attribute read, handle: %u, conn: %p", attr->handle,
		(void *)conn);

	if (lbs_cb.button_cb) {
		button_state = lbs_cb.button_cb();
		return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
					 sizeof(*value));
	}

	return 0;
}

コールバック関数が登録されているなら値を更新して bt_gatt_attr_read() を呼ぶ。
引数の valueattr->user_data である。

BT_GATT_CHARACTERISTIC()はこんな感じで Attribute が 2つ並んでいる。
Core v5.1, Vol.3, Part G の “3.3.1 Characteristic declaration” と “3.3.2 Characteristic Value declaration” だろう。
“3.3.3 CCCD” はオプションみたいなものなので、必要があればBT_GATT_CCC()で追加する。

#define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _user_data) \
	BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ,                 \
			  bt_gatt_attr_read_chrc, NULL,                         \
			  ((struct bt_gatt_chrc[]) {                            \
				BT_GATT_CHRC_INIT(_uuid, 0U, _props),           \
						   })),                         \
	BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data)

user_datastruct bt_gatt_attrvoid *user_data に代入される。
なので attr->user_data は LBS でいえば button_state のアドレスだと思われる。

bt_gatt_attr_read()で渡す value は引数なので別のアドレスでもよさそうだし、Notification や Indication も同様である。
サイズを管理しているわけでもないので SoftDevice(ではないけど)が勝手に値を返したくてもできないと思うのだ。
何のために指定しているのだろう?
名前が “attr_data” ではなく “user_data” なので、ユーザが好きに扱うものと考えて良い気もする。
nRF ではなく Zephyr のコードなのだが、コメントが「Attribute user data」だとわからんのだよ。。。

そういう使い方をするのであれば、Service のコードに紐付けるよりアプリ側に提供した方が使い道がありそうだ。
使い道は保留とした。

svc.attrs[]

bt_gatt_notify() などは引数に const struct bt_gatt_attr* を取る。
コメントが “Characteristic or Characteristic Value attribute” となっているが、値の更新を通知するのであれば Value の方だろう。
先ほど書いたように BT_GATT_CHARACTERISTIC() でサービスに Characteristic を追加する 2 つの Attribute定義(BT_GATT_ATTRIBUTE) が追加される。
Value は 2 つめの方なので、attrs[] の添字にはそれを考慮するとよい。

< Top page