前回に引き続き、メカトラックス株式会社様よりお借りしたアルプスのセンサネットワークモジュール用のプログラムを、Bluetoothプロトコルスタック「Bluez」をベースにC言語で作成しました。アルプスのセンサネットワークモジュールは、Bluetooth Low Energy(BLE)を用いて、モーションデータと共に、気圧、温度・湿度などの環境データを取得でき、センサネットワークモジュールを、BLEを用いて、次のようにRaspberry Pi 3と接続しました。
写真ではRaspberry Pi 3の上に、メカトラックス株式会社様の3GPI(Raspberry Pi向け3G通信モジュール)を載せています。次回其の3では、センサネットワークモジュールで取得した環境データを3GPI経由でネットワーク上にアップロードします。
前回は、Bluezのコマンド「gatttool」を使って、アルプスのセンサネットワークモジュールとRaspberry Pi 3とBLE接続しましたが、このgatttoolコマンドを使った操作をC言語を用いてプログラム化します。
なお、今回はC言語でアルプスのセンサネットワークモジュールから環境データを取得しましたが、Python言語を使ってモーションデータを取得したプログラムを「Pythonを使ってアルプスのセンサネットワークモジュールでモーションデータの取得」に示します。
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」でした。このフォルダの内容は次のようになっています。
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コマンドのキーボード入力と同等の機能を持たせます。
このフォルダのソースコードと呼び出されている関数のソースコードと、あと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で接続」を参照してください。
対話的に入力するchar-write-reqのデータテーブル
前回確認したchar-write-reqのデータを、テーブル化して参照テーブル「charWriteReq」に設定します。
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を示し、データに設定されている温湿度、気圧の情報を取得して表示します。
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」の実行を準備します。
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回実行されます。、
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の処理を行います。
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」で定義します。
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」にはコンパイルしたオブジェクトファイルが保存されます。
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