アルプスのセンサネットワークモジュールをラズパイ3と3GPIで使ってみる（其の2 C言語によるBLEソフト）

[前回](http://qiita.com/TomoSoft/items/33e39cb3beaa2ea043db)に引き続き、[メカトラックス株式会社様](http://www.mechatrax.com/)よりお借りした[アルプスのセンサネットワークモジュール](http://www.alps.com/j/iotsmart-network/index.html)用のプログラムを、Bluetoothプロトコルスタック「Bluez」をベースにC言語で作成しました。アルプスのセンサネットワークモジュールは、Bluetooth Low Energy(BLE)を用いて、モーションデータと共に、気圧、温度・湿度などの環境データを取得でき、センサネットワークモジュールを、BLEを用いて、次のようにRaspberry Pi 3と接続しました。<br>
<div align="center" ><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/49af0ca6-31c5-9913-70d4-1c8352b20c20.jpeg"></div>

>写真ではRaspberry Pi 3の上に、メカトラックス株式会社様の[3GPI(Raspberry Pi向け3G通信モジュール)](http://www.mechatrax.com/products/3gpi)を載せています。次回其の3では、センサネットワークモジュールで取得した環境データを3GPI経由でネットワーク上にアップロードします。

前回は、Bluezのコマンド「gatttool」を使って、アルプスのセンサネットワークモジュールとRaspberry Pi 3とBLE接続しましたが、このgatttoolコマンドを使った操作をC言語を用いてプログラム化します。

なお、今回はC言語でアルプスのセンサネットワークモジュールから環境データを取得しましたが、Python言語を使ってモーションデータを取得したプログラムを[「Pythonを使ってアルプスのセンサネットワークモジュールでモーションデータの取得」](http://tomosoft.jp/design/?p=8104)に示します。

# Bluetoothプロトコルスタック「Bluez」のインストール
Raspberry Pi 3では、Bluetoothインタフェースが標準で実装され、BLEでの接続も可能になっていますが、今回はセンサネットワークモジュール用プログラムをC言語で開発するため、最新バージョン「Bluez 5.40」をインストールし、ソースコード、ヘッダファイル、Bluetoothライブラリが利用できる作業環境を構築します。

Rasbian 上で Bluez をインストールする際に apt-get を使うと 最新バージョン以外がインストールされる可能性があるので、Bluez 5.40を取得してソースからコンパイルしてインストールします。

```
$ sudo apt-get install libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libical-dev libreadline-dev libudev-dev libusb-dev make
$ mkdir -p work/bluetooth
$ cd work/bluetooth
$ wget https://www.kernel.org/pub/linux/bluetooth/bluez-5.40.tar.xz
$ tar xvf bluez-5.40.tar.xz
$ cd bluez-5.40
$ ./configure --disable-systemd --enable-library
$ make
$ sudo make install
```
続けて make でインストールされないものを手動でインストールします。

```
$ sudo cp attrib/gatttool /usr/local/bin/
```

あとは、libbluetooth-dev の各種ヘッダファイルをビルドしたもので置き換えます。既存のincludeを残したい場合はバックアップし、コンパイルしたlib/配下をbluetoothとしてリンクします。

```
$ sudo cp -ipr lib/ /usr/include/bluetooth.5.40
$ cd /usr/include
$ sudo ln -s bluetooth.5.40/ bluetooth 
```

# Bluetoothプロトコルスタック「Bluez」からgatttoolコマンド部分の抽出
gatttoolコマンドの実行ファイル「gatttool」が存在するフォルダを調べると「work/bluetooth/bluez-5.40/attrib」でした。このフォルダの内容は次のようになっています。<br>
<div align="center" ><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/38a5098a-ee3f-c17f-526a-1aa161c1a0b6.jpeg"></div>

main関数は「gatttool.c」に記述され、gatttoolコマンドのinteractiveモードの場合は、「interactive.c」のinteractive関数にmain関数から制御が移されます。関連するファイルの関数間の制御をまとめた図を次に示します。
今回作成するセンサネットワークモジュール用プログラムは、「gatttoolコマンド入力」の部分が、「char-write-reqのデータテーブル」となり、アルプスのセンサネットワークモジュールから受け取るコールバック関数「connect_cb」と「char_write_req_cb」が呼ばれると、次に出力するデータをchar-write-reqのデータテーブルからを取得して、このデータをパラメータにして入力コマンド解析関数「parse_line」を呼び出すように修正することで、gatttoolコマンドのキーボード入力と同等の機能を持たせます。<br>
<div align="center" ><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/3dd8f2b5-f6bd-3908-317d-9fc9248df820.jpeg"></div>

このフォルダのソースコードと呼び出されている関数のソースコードと、あとBluetoothのライブラリ「libbluetooth-internal.a」、「libshared-glib.a」をリンクすれば、センサネットワークモジュール用プログラムができます。
呼び出されている関数とBluetoothのライブラリはmakefileを参考にします。gatttoolコマンドのmakefileは「work/bluetooth/bluez-5.40/Makefile.tools」で、「attrib_gatttool_SOURCES」によりコンパイルするソースコードが定義されており、この部分のコードを切り取ってmakefileを作成します。

次にプログラムの作成とコンパイル環境の構築を行います。ソースコードを修正し、コンパイルし、コンパイル/リンクエラーが出ると、ソースコードの修正やMakefileにより必要なファイルの追加を繰り返すことにより、センサネットワークモジュール用プログラムを作成します。

# センサネットワークモジュール用プログラムの作成
アルプスのセンサネットワークモジュールのアドレスとinteractiveのパラメータを与えて起動し、「connect」をキー入力した部分までのプログラムコードは、「[C言語によりRaspberry Pi 3とSensorTag間をBLEで接続](http://tomosoft.jp/design/?p=7344)」を参照してください。

## 対話的に入力するchar-write-reqのデータテーブル
[前回](http://qiita.com/TomoSoft/items/33e39cb3beaa2ea043db)確認したchar-write-reqのデータを、テーブル化して参照テーブル「charWriteReq」に設定します。

```c
char charWriteReq[10][32] = {
	{"char-write-req 0x0018 200300"},
	{"char-write-req 0x0018 230301"},
	
		{"char-write-req 0x0013 0100"},
		{"char-write-req 0x0016 0100"},
	
		{"char-write-req 0x0018 2F0303"},
		{"char-write-req 0x0018 01031C"},
		{"char-write-req 0x0018 040300"},
		{"char-write-req 0x0018 05043C00"},
	
		{"char-write-req 0x0018 200301"},
		{"char-write-req 0x0018 230300"},
	};
```

## Notify とIndicate のデータ受信イベントハンドラ
Notification とIndicate のデータを受信したときに、当該イベントハンドラ「events_handler_main」に制御が移ります。ここでは受け取ったデータの内容を表示します。
受信したデータのOpcodeが「0xf3」の場合、センサネットワークモジュールのデータパケット2を示し、データに設定されている温湿度、気圧の情報を取得して表示します。

```c
static void events_handler_main(const uint8_t *pdu, uint16_t len, gpointer user_data, uint16_t *pHandle)
{
	uint8_t *opdu;
	size_t plen;
	uint16_t olen;
	uint16_t i;
	GString *s;
	float Pressure,Humidity,Temperature;
	char ascPressure[16],ascHumidity[16],ascTemperature[16];
	*pHandle = get_le16(&pdu[1]);
	switch (pdu[0]) {
	case ATT_OP_HANDLE_NOTIFY:
		s = g_string_new(NULL);
		g_string_printf(s, "Notification handle = 0x%04x value: ", *pHandle);
		break;
	case ATT_OP_HANDLE_IND:
		s = g_string_new(NULL);
		g_string_printf(s, "Indication   handle = 0x%04x value: ", *pHandle);
		break;
	default:
		error("Invalid opcode\n");
		return;
	}
	for (i = 3; i < len; i++)
		g_string_append_printf(s, "%02x ", pdu[i]);
	rl_printf("%s\n", s->str);
	g_string_free(s, TRUE);
	if(pdu[3] == 0xf3) {
		Pressure = ((float)pdu[5] + (float)pdu[6]*256)*860 / 65535 +250;
		Humidity = (((float)pdu[7] + (float)pdu[8]*256) - 896) / 64;
		Temperature = ((float)pdu[9] + ((float)pdu[10]*256) - 2096) /50;
		printf("Pressure:%7.2f Humidity:%5.2f Temperature:%5.2f\n ",Pressure,Humidity,Temperature);
	}
	if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
		return;
	opdu = g_attrib_get_buffer(attrib, &plen);
	olen = enc_confirmation(opdu, plen);
	if (olen > 0)
		g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL);
}
```

## 「connect」後の次のコマンドの取得
「connect」により接続が完了したときに接続完了のコールバック関数「connect_cb_main」が呼び出され、charWriteReqテーブルの「char-write-req 0x0018 200300」の実行を準備します。

```c
static void connect_cb_main(GIOChannel *io, GError *err, gpointer user_data)
{
	uint16_t mtu;
	uint16_t cid;

	if (err) {
		set_state(STATE_DISCONNECTED);
		error("%s\n", err->message);
		return;
	}

	bt_io_get(io, &err, BT_IO_OPT_IMTU, &mtu,
	          BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID);

	if (err) {
		g_printerr("Can't detect MTU, using default: %s", err->message);
		g_error_free(err);
		mtu = ATT_DEFAULT_LE_MTU;
	}

	if (cid == ATT_CID)
		mtu = ATT_DEFAULT_LE_MTU;

	attrib = g_attrib_new(iochannel, mtu);
	g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES,
	                  events_handler, attrib, NULL);
	g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES,
	                  events_handler, attrib, NULL);
	set_state(STATE_CONNECTED);
	rl_printf("Connection successful\n");
	
	btComm = g_strdup(charWriteReq[commanNo]);
	printf("charWriteReq[%d][]=%s\n", commanNo, charWriteReq[commanNo]);
	commanNo ++;
	parse_line(btComm);
}
```

## コマンド実行後の次のコマンドの取得
charWriteReqテーブルに従ってchar-write-reqが実行され、前回のchar-write-reqの実行が完了すると、当該コールバック関数「char_write_req_cb」に制御が移り、charWriteReqテーブルから次のchar-write-reqを取得して、parse_line関数のパラメータに設定して、実行の準備を行います。char-write-reqは、char-write-reqのデータテーブルに設定されているデータ数、10回実行されます。、

```c
static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data)
{
	printf("char_write_req_cb\n");

	if (status != 0) {
		error("Characteristic Write Request failed: "
		      "%s\n", att_ecode2str(status));
		set_st_state(ST_STATE_ERROR);
		return;
	}

	if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) {
		error("Protocol error\n");
		set_st_state(ST_STATE_ERROR);
		return;
	}

	rl_printf("Characteristic value was written successfully\n");
	set_st_state(ST_STATE_WRITE_RES);
	
	if(commanNo != 10) {
		btComm = g_strdup(charWriteReq[commanNo]);
		printf("charWriteReq[%d][]=%s\n", commanNo, charWriteReq[commanNo]);
		commanNo ++;
		parse_line(btComm);
	}
}
```

## char-write-reqの処理関数
コマンドの参照テーブルを参照して、「char-write-req」が入力されると、char-write-reqの処理関数「cmd_char_write」が呼び出され、char-write-reqの処理を行います。

```c
static void cmd_char_write(int argcp, char **argvp)
{
	uint8_t *value;
	size_t plen;
	int handle;

	if (conn_state != STATE_CONNECTED) {
		failed("Disconnected\n");
		return;
	}

	if (argcp < 3) {
		rl_printf("Usage: %s <handle> <new value>\n", argvp[0]);
		return;
	}

	handle = strtohandle(argvp[1]);
	if (handle <= 0) {
		error("A valid handle is required\n");
		return;
	}

	plen = gatt_attr_data_from_string(argvp[2], &value);
	if (plen == 0) {
		error("Invalid value\n");
		return;
	}

	if (g_strcmp0("char-write-req", argvp[0]) == 0)
		gatt_write_char(attrib, handle, value, plen,
		                char_write_req_cb, NULL);
	else
		gatt_write_cmd(attrib, handle, value, plen, NULL, NULL);

	g_free(value);
}
```

## コマンドの参照テーブル
gatttoolコマンドの「connect」、「char-write-req」、「exit」の各コマンド名とそのコマンドを処理するための関数を、コマンドの参照テーブル「commands」で定義します。

```c
static struct {
	const char *cmd;
	void (*func)(int argcp, char **argvp);
	const char *params;
	const char *desc;
} commands[] = {
	{
		"connect",		cmd_connect,	"[address [address type]]",
		"Connect to a remote device"
	},
	{
		"char-write-req",	cmd_char_write,	"<handle> <new value>",
		"Characteristic Value Write (Write Request)"
	},
	{
		"exit",		cmd_exit,	"",
		"Exit interactive mode"
	},
	{ NULL, NULL, NULL}
};
```

# センサネットワークモジュール用プログラムのコンパイル環境の構築
センサネットワークモジュール用プログラムのために作成したフォルダに、フォルダ「work/bluetooth/bluez-5.40/attrib」に含まれるCコードファイルとヘッダファイルをコピーして、コンパイルした結果のフォルダ内の内容を次に示します。「alpsble」がセンサネットワークモジュール用プログラムの実行ファイル、フォルダ「_build」にはコンパイルしたオブジェクトファイルが保存されます。<br>
<div align="center" ><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/e91a8671-4690-e16c-9589-24ed889e91d4.jpeg"></div>

makeファイルを次に示します。フォルダ「_build」にオブジェクトが作成され、実行ファイル「alpsble」がカレントディレクトに作成されます。リンクするライブラリは、Bluetoothのライブラリ「libbluetooth-internal.a」と「libshared-glib.a」、Readline library「readline」、クロスプラットフォームのユーティリティライブラリ「glib-2.0」を使用しています。また、含まれていたソースコード「interactive.c」と「utils.c」は、「gatttool.c」にマージしました。

```
ROJECT_NAME := alpsble
BLUEZ_PATH = /home/pi/work/bluetooth/bluez-5.40/
PRJ_PATH = .
OBJECT_DIRECTORY = _build
OUTPUT_BINARY_DIRECTORY = .
OUTPUT_FILENAME := $(PROJECT_NAME)

　　　　　　・
　　　　　　・
#sources project
C_SOURCE_FILES += $(PRJ_PATH)/gatttool.c
C_SOURCE_FILES += $(PRJ_PATH)/att.c
C_SOURCE_FILES += $(PRJ_PATH)/gatt.c
C_SOURCE_FILES += $(PRJ_PATH)/gattrib.c

C_SOURCE_FILES += $(BLUEZ_PATH)/src/log.c
C_SOURCE_FILES += $(BLUEZ_PATH)/client/display.c
C_SOURCE_FILES += $(BLUEZ_PATH)/btio/btio.c

　　　　　　・
　　　　　　・
#includes common to all targets
INC_PATHS += -I$(BLUEZ_PATH)
INC_PATHS += -I$(BLUEZ_PATH)/btio
　　　　　　・
　　　　　　・
#Link Library
LIBS += $(BLUEZ_PATH)/lib/.libs/libbluetooth-internal.a
LIBS += $(BLUEZ_PATH)/src/.libs/libshared-glib.a
LIBS += -lreadline
LIBS += `pkg-config --libs glib-2.0`
　　　　　　・
　　　　　　・
```

# センサネットワークモジュール用プログラムの実行
作成したセンサネットワークモジュール用プログラム「alpsble」を、Raspberry Pi 3で実行した結果を次に示します。センサネットワークモジュールをアドバタイズすると、Raspberry Pi 3がセンサネットワークモジュールから通知を受信し、char-write-reqのデータテーブルの設定内容に従って、センサネットワークモジュールにWrite Requestメッセージでデータを送信します。Raspberry Pi 3はセンサネットワークモジュールからのWrite Responseメッセージを受け取り、センサネットワークモジュールがデータの受け取りに成功すると、次のデータを送信します。また、センサネットワークモジュールからはNotificationメッセージを、データ受信イベントハンドラで受け取ります。
「```charWriteReq[x][]=char-write-req 0x0018 200300```」はセンサネットワークモジュールに送信したデータを示します。「```Characteristic value was written successfully```」は、センサネットワークモジュールに送信したデータが正常に書き込まれたことを示します。「```Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00```」は、データ受信イベントハンドラで受け取ったデータを示します。

```
$ ./alpsble
Connection successful
charWriteReq[0][]=char-write-req 0x0018 200300
char_write_req_cb
Characteristic value was written successfully
st_state = 5
charWriteReq[1][]=char-write-req 0x0018 230301
char_write_req_cb
Characteristic value was written successfully
charWriteReq[2][]=char-write-req 0x0013 0100
char_write_req_cb
Characteristic value was written successfully
charWriteReq[3][]=char-write-req 0x0016 0100
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
charWriteReq[4][]=char-write-req 0x0018 2F0303
char_write_req_cb
Characteristic value was written successfully
charWriteReq[5][]=char-write-req 0x0018 01031C
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
charWriteReq[6][]=char-write-req 0x0018 040300
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
charWriteReq[7][]=char-write-req 0x0018 05043C00
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
charWriteReq[8][]=char-write-req 0x0018 200301
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
charWriteReq[9][]=char-write-req 0x0018 230300
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
char_write_req_cb
Characteristic value was written successfully
Notification handle = 0x0012 value: e0 14 00 00 00 00 00 c9 0b 00 01 00 00 00 00 00 00 00 00 00
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 29 01 00 00
Notification handle = 0x0012 value: f3 14 18 e0 44 16 eb 0d 00 80 00 80 00 00 00 00 01 0a 0f 00
Pressure:1002.83 Humidity:75.06 Temperature:29.34
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 29 02 00 01
Notification handle = 0x0012 value: f3 14 12 e0 24 16 e3 0d 00 80 00 80 00 00 00 00 01 0a 0f 01
Pressure:1002.75 Humidity:74.56 Temperature:29.18
```

#　関連URL

1. [アルプスのセンサネットワークモジュールをラズパイ3と3GPIで使ってみる（其の1 BLEパケット構造解析）](http://qiita.com/TomoSoft/items/33e39cb3beaa2ea043db)
2. [アルプスのセンサネットワークモジュールをラズパイ3と3GPIで使ってみる（其の3 Googleスプレッドシートへのセンサ情報の保存）](http://qiita.com/TomoSoft/items/4ac6c411f1cfdd128a4c)