Bluetooth 4.0(BLE)対応のリストバンド型活動量計を、自作の Android アプリから操作して遊んでみようという試みです。
手頃な値段の腕輪を探す
今回は、 Amazon で ¥2.5k ほどで売っていた、こちらをターゲットとしました。
Excelvan スマートブレスレット Bluetooth 4.0
購入先:その1
購入先:その2
腕輪について調べてみる
使えそうな Android 用のアプリケーションが2つほどありました。
Navig8r Move(Google Play)
ZERONER(Google Play)
Move は活動量(歩数、距離、カロリー)履歴の取得のみ、ZERONER は履歴の取得に加えて、Android の端末の通知をリストバンドに送ることができます(ただし、あらかじめ決められたアプリ(Twitter, Facebook, Skype, Whatsapp)のみで、好きなアプリの通知はできない)。
取扱説明書的なものは、こちらが使えそうです
Viddon X5(PDFへの直接リンク)
サービスの一覧を取得してみる
どんな UUID が使えるのか調べるため、 BluetoothGatt の discoverServices を呼んでみます。
サービス(Service)
以下はBLE標準のものです。
Service Name | UUID |
---|---|
Generic Access | 00001800-0000-1000-8000-00805f9b34fb |
Generic Attribute | 00001801-0000-1000-8000-00805f9b34fb |
Device Information Service | 0000180a-0000-1000-8000-00805f9b34fb |
Battery Service | 0000180f-0000-1000-8000-00805f9b34fb |
以下は独自のものです。
Service Name | UUID |
---|---|
PHONE ALERT | f000ff10-0451-4000-b000-000000000000 |
MAIN | f000ff00-0451-4000-b000-000000000000 |
キャラクタリスティック(Characteristic)
以下はBLE標準のものです。
Characteristic Name | UUID |
---|---|
Device Name | 00002a00-0000-1000-8000-00805f9b34fb |
Appearance | 00002a01-0000-1000-8000-00805f9b34fb |
Peripheral Privacy Flag | 00002a02-0000-1000-8000-00805f9b34fb |
Reconnection Address | 00002a03-0000-1000-8000-00805f9b34fb |
Peripheral Preferred Connection Parameters | 00002a04-0000-1000-8000-00805f9b34fb |
Service Changed | 00002a05-0000-1000-8000-00805f9b34fb |
System ID | 00002a23-0000-1000-8000-00805f9b34fb |
Model Number String | 00002a24-0000-1000-8000-00805f9b34fb |
Serial Number String | 00002a25-0000-1000-8000-00805f9b34fb |
Firmware Revision String | 00002a26-0000-1000-8000-00805f9b34fb |
Hardware Revision String | 00002a27-0000-1000-8000-00805f9b34fb |
Software Revision String | 00002a28-0000-1000-8000-00805f9b34fb |
Manufacturer Name String | 00002a29-0000-1000-8000-00805f9b34fb |
IEEE 11073-20601 Regulatory Certification Data List | 00002a2a-0000-1000-8000-00805f9b34fb |
Battery Level | 00002a19-0000-1000-8000-00805f9b34fb |
以下は独自のものです。
Characteristic Name | UUID |
---|---|
PHONE ALERT | f000ff11-0451-4000-b000-000000000000 |
ALARM | f000ff01-0451-4000-b000-000000000000 |
USER | f000ff02-0451-4000-b000-000000000000 |
SPORT | f000ff03-0451-4000-b000-000000000000 |
LED | f000ff04-0451-4000-b000-000000000000 |
DATE | f000ff05-0451-4000-b000-000000000000 |
PAIR | f000ff06-0451-4000-b000-000000000000 |
DAILY | f000ff07-0451-4000-b000-000000000000 |
SEDENTARY | f000ff08-0451-4000-b000-000000000000 |
POWER SAVING | f000ff09-0451-4000-b000-000000000000 |
ディスクリプタ(Descriptor)
Descriptor Name | UUID |
---|---|
- | 00002902-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002902-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
- | 00002901-0000-1000-8000-00805f9b34fb |
Service MAIN(f000ff00-0451-4000-b000-000000000000)
Characteristic PHONE ALERT(f000ff11-0451-4000-b000-000000000000)
が、今回お目当ての UUID となりそうです。
通知を送るための電文を解析してみる
前章で探し当てた UUID に、どのようなデータを writeCharacteristic すると通知が送られるのか、 ZERONER のアプリケーションで実際に通知を送ってみました。
実際に、送られたデータをダンプしたものです。
02C0010100003FE0200020002000200020003FC0
02C001023FC02000200020002000200020000000
02C0010300000000000000001C003E0063004100
02C001041F0079006300430047007F0039000000
02C0010500000000000000000C003E0063004100
02C00106400040004000410063003E0000000000
02C0010700000000000000000C003E0063006100
02C0010841007F004000610063003E0000000000
02C0010900004000400040005C007E0063006100
02C0010A410041004100610063007E0000000000
02C0010B000000000000000018003E0066004300
02C0010C430043004300620076003C0000000000
02C00110
横96dot x 縦16dotのモノクロビットマップが送れるようです。
1〜12行目はビットマップデータが含まれています。
1行目のデータを例にすると、
data | description |
---|---|
02 | start mark |
C0 | bitmap data size (16x12=192) |
01 | icon type (1=mail, 0=phone) |
01 | sequence (1, 2, 3 ... 12) |
0000 3FE0 2000 2000 2000 2000 2000 3FC0 | bitmap data (16x8 big endian。データの並び順については後述) |
ビットマップデータは、横16dot x 縦8dot x 12個に分割して、以下の順に転送します。
①③⑤⑦⑨⑪
②④⑥⑧⑩⑫
13行目はフッターです。
解析結果を元に通知を送ってみる
実際に Android アプリを作ってみましょう。
以下の説明は、見やすくするため、エラー処理は省略しています。
接続
今回は実験なので、デバイスの検索などは行わず直接接続します。
BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBtAdapter = mBluetoothManager.getAdapter();
BluetoothDevice mDevice = mBtAdapter.getRemoteDevice(DEVICE_ADDRESS);
BluetoothGatt mBluetoothGatt = mDevice.connectGatt(this, false, new BluetoothGattCallback() {
...(省略)...
});
サービス一覧の取得
connectGatt が成功すると、 BluetoothGattCallback の onConnectionStateChange が呼ばれます。
discoverServices を呼んで、 BluetoothGattCallback の onServicesDiscovered が呼ばれれば準備完了です。
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
mBluetoothGatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 準備OK
}
}
通知の送信
文字列から、モノクロビットマップを作成して、腕輪に送信します。
腕輪にメッセージが表示されて、振動しました!!成功です!!
private void sendNotification(String text) {
byte[][] r = makeBitmapPacket(text);
for (int i = 0; i < r.length; i++) {
BluetoothGattCharacteristic c = characteristic(BAND_SERVICE_PHONE_ALERT_UUID, BAND_CHARACTERISTIC_PHONE_ALERT_UUID);
c.setValue(r[i]);
mBluetoothGatt.writeCharacteristic(c);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
}
}
BluetoothGattCharacteristic c = characteristic(BAND_SERVICE_PHONE_ALERT_UUID, BAND_CHARACTERISTIC_PHONE_ALERT_UUID);
c.setValue(new byte[]{2, (byte) 192, 1, 16});
mBluetoothGatt.writeCharacteristic(c);
}
private BluetoothGattCharacteristic characteristic(UUID sid, UUID cid) {
if (mBluetoothGatt == null) return null;
BluetoothGattService s = mBluetoothGatt.getService(sid);
if (s == null) {
Log.w(TAG, "Service NOT found :" + sid.toString());
return null;
}
BluetoothGattCharacteristic c = s.getCharacteristic(cid);
if (c == null) {
Log.w(TAG, "Characteristic NOT found :" + cid.toString());
return null;
}
return c;
}
private byte[][] makeBitmapPacket(String c) {
int len = 6;
byte size = (byte) (len * 16 * 2);
Paint w_paint = new Paint();
w_paint.setAntiAlias(false);
w_paint.setColor(Color.BLACK);
w_paint.setTextSize(14f);
w_paint.setTextScaleX(10f / 14f);
Paint.FontMetrics fm = w_paint.getFontMetrics();
Bitmap bmp = Bitmap.createBitmap(16 * len, 16, Bitmap.Config.ARGB_8888);
Canvas cv = new Canvas(bmp);
cv.drawText(c, 0, 8 - (fm.ascent + fm.descent) / 2, w_paint);
byte[][] r = new byte[len * 2][4 + 32];
for (int d = 0; d < len * 2; d++) {
for (int y = 0; y < 8; y++) {
int d1 = 0, d2 = 0;
for (int x = 0; x < 8; x++) {
d1 |= bmp.getPixel((d / 2) * 16 + x, (d & 1) * 8 + y) == Color.BLACK ? 1 : 0;
d2 |= bmp.getPixel((d / 2) * 16 + x + 8, (d & 1) * 8 + y) == Color.BLACK ? 1 : 0;
if (x < 7) {
d1 <<= 1;
d2 <<= 1;
}
}
r[d][4 + y * 2] = (byte) d1;
r[d][4 + y * 2 + 1] = (byte) d2;
}
r[d][0] = 2;
r[d][1] = size;
r[d][2] = 1;
r[d][3] = (byte) (d + 1);
}
bmp.recycle();
return r;
}
切断
実験が終わったら、切断処理を行います。
mBluetoothGatt.disconnect();
今回のサンプルソースのプロジェクト一式はここからダウンロードできます。
(自分で試す際は、 MainActivity.java の DEVICE_ADDRESS を自分の腕輪の Mac アドレスに書き換えてください)
他にも、
- バッテリー残量の取得
- 活動履歴(歩数、距離、カロリー)の取得
- 時間の設定、取得
- アラームの設定、取得
などが行えるので、解説していきたいと思います。