-
Notifications
You must be signed in to change notification settings - Fork 1
/
ebiiim.re
605 lines (476 loc) · 23.8 KB
/
ebiiim.re
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
#@# author: ebiiim <[email protected]>
= GoでBluetooth Low Energy
GoでBluetooth Low Energy(以降BLEとします)を触ってみたので、必要な知識をまとめました。
BLEとGATTの概要を確認した後、
Gobotフレームワークを用いて心拍数センサから
取得したデータを表示するプログラムを作成することで
GoでBLEを扱う方法の一例を確認します。
Raspberry Piなど、GoとBluetoothの両方が使用できる端末があればいろいろ遊べそうです。
== Bluetooth Low Energy
BLEとは、
Bluetooth 4.0以降の機能で、少ない消費電力で動作する通信モードです。
Bluetooth Classic(従来のBluetooth)との互換性はありませんが、
次に示す優れたスペックによりIoTを支える技術として広く定着しています。
* ボタン電池で2年間動作可能
* 低レイテンシ(数ms)で通信可能
* 実用上無制限の同時接続台数(Bluetooth Classicは7台)
BLEにおけるデバイス間の通信方法は、ブロードキャスト(1対多)とコネクション(1対1)の2種類があります。
ペアリングして行う一般的な通信はコネクションです。
ペアリング後のアプリケーションデータの送受信方式は、
Generic Attribute Profile(以降GATTとします)で定義されます。
GATTでは、通信を開始する側をクライアントと、クライアントからの要求に応答する側をサーバと呼びます。
たとえば、本稿ではGoのプログラムを用いて心拍数センサからデータを取得しますが、
この場合はGoのプログラムを動作させる機器がクライアントで心拍数センサがサーバです。
=== Generic Attribute Profile
GATTは、次に示す概念により、クライアントによるデータの読み書きとサーバによる通知のためのデータモデルを定義します。
==== キャラクタリスティック(Characteristic)
UUIDで識別される@<fn>{uuid}、データをやりとりするための仕様です。
例を示します。
* Battery Level(0x2A19): バッテリー残量を伝達するためのデータ形式
* Heart Rate Measurement(0x2A37): 心拍数を伝達するためのデータ形式
==== サービス(Service)
単一または複数のキャラクタリスティックで構成される、機能を提供する仕様です。
例を示します。
* BAS(Battery Service): Battery Level(0x2A19)を実装すること
* HRS(Heart Rate Service): Heart Rate Measurement(0x2A37)、Body Sensor Location(0x2A38)(任意)、Heart Rate Control Point(0x2A39)を実装すること
==== プロファイル(Profile)
単一または複数のサービスで構成される、ユースケースを提供する仕様です。
例を示します。
* HRP(Heart Rate Profile)
** クライアントはHRSとDIS@<fn>{dis}を実装すること
** サーバはHRSとDIS(任意)を実装すること
//footnote[dis][Device Information Service]
//footnote[uuid][Bluetooth SIGが策定したキャラクタリスティックは、(通信量の削減を目的とした)短縮表記したUUIDを用いることができる]
前述を含めたいくつかのキャラクタリスティック、サービス、プロファイルをBluetooth SIGが策定しています@<fn>{sig1}@<fn>{sig2}。
これによりBLEデバイス間の互換性を高めています。
//footnote[sig1][サービスおよびプロファイル: @<href>{https://www.bluetooth.com/ja-jp/specifications/gatt/}]
//footnote[sig2][キャラクタリスティック: @<href>{https://www.bluetooth.com/ja-jp/specifications/gatt/characteristics/}]
== GoでBLE
それではGoでBLEに触れてみましょう。
=== GoのBLEライブラリ
Goで書かれたオープンソースのBLEライブラリがいくつかあります。
本稿ではGobotを使用しますが、検討したものを示します。
#@# textlint-disable
==== paypal/gatt
#@# textlint-enable
BLEのpure goによる実装です。
LinuxとmacOSに対応しています。
ググると記事がいくつかヒットしますが、
ライブラリは現在メンテナンスされていないようです。
Linuxでは正常に使用できます。macOSでは、APIの変更に追いついていないため、使うことが困難です。
* Star: 800+
* Last commit: October 2015
* License: BSD-3-Clause
* Repository: @<href>{https://github.com/paypal/gatt}
==== currantlabs/ble
こちらもBLEのpure goによる実装です。
LinuxとmacOSに対応しています。
こちらもLinuxでは正常に使用できます。macOSでも使えるはずですが、私の環境ではデバイスの探索がうまくできませんでした。
* Star: 150+
* Last commit: December 2017
* License: BSD-3-Clause
* Repository: @<href>{https://github.com/currantlabs/ble}
==== muka/go-bluetooth
BlueZ@<fn>{bluez}とD-Busでやりとりするためのライブラリです。
BlueZを前提としているため、今回は検証しませんでした。
//footnote[bluez][Linuxカーネルに採用されているBluetoothプロトコルスタック @<href>{http://www.bluez.org/}]
* Star: 250+
* Last commit: September 2019
* License: Apache-2.0
* Repository: @<href>{https://github.com/muka/go-bluetooth}
==== Gobot
#@# textlint-disable
IoTやロボティクスのためのフレームワークです。
BLEの部分(@<code>{gobot.io/x/gobot/platforms/ble})は@<code>{currantlabs/ble}をフォークしたライブラリ@<fn>{goble}を使用しています。
公式サイト@<fn>{gobot}のドキュメントが豊富なので、本稿で採用しました。
#@# textlint-enable
//footnote[goble][@<href>{https://github.com/go-ble/ble}]
//footnote[gobot][@<href>{https://gobot.io/}]
* Star: 5800+
* Last commit: May 2019
* License: Apache-2.0
* Repository: @<href>{https://github.com/hybridgroup/gobot}
=== Gobotを使う
====[column] 動作確認に用いた環境
本稿の動作確認はすべてRaspberri PiとBLE対応の心拍数センサで行いました。
詳細を示します。
* コンピュータ: Raspberry Pi 3 Model B+
* OS: Ubuntu 18.04.2 LTS
* Go: Go 1.13
* BLEデバイス: Echowell BH20(HRPとBASに対応した心拍数センサ)
====[/column]
まずはGobotをインストールしましょう(@<list>{gobot1})。
//list[gobot1][Gobotのインストール][bash]{
$ go get -d -u gobot.io/x/gobot/...
//}
ディレクトリを眺めてみると、次のことがわかります。
* @<code>{gobot.io/x/gobot/examples/}にサンプルコードがある
* @<code>{gobot.io/x/gobot/platforms/ble/}にBLE関連のコードがある
では、サンプルコードで動作を確認しましょう。
DISの各キャラクタリスティックからデバイス情報を取得する
@<code>{examples/ble_device_info.go}を実行します(@<list>{gobot2})。
サンプルコードでは、引数にフレンドリ名@<fn>{fname}またはBDアドレス@<fn>{bda}を与えることでデバイスの特定を行います。
実行する前に、BLEテストツール@<fn>{lb}などで使用したいBLEデバイスの情報を確認しておくことを推奨します。
//footnote[fname][スマートフォンやPCのBluetooth接続画面に表示される名前]
//footnote[bda][Bluetoothデバイスを識別するためのIEEE 802に準拠したアドレス(MACアドレスと同じ構造)]
//footnote[lb][iOSまたはAndroidで動作するLightBlue Explorerが便利]
//list[gobot2][サンプルコードの実行][bash]{
$ cd ${GOPATH}/src/gobot.io/x/gobot
$ go build examples/ble_device_info.go
$ sudo ./ble_device_info "フレンドリ名またはBDアドレス"
//}
サンプルコードを実行すると、@<list>{gobot3}のような出力が得られるでしょう。
なお、私の環境では@<code>{examples/ble_device_info.go}の33行目にある
@<code>{info.GetPnPId}メソッドでパニックが発生した@<fn>{pnpid}ので、33行目をコメントアウトした状態で実行しています。
//footnote[pnpid][使用したBLEデバイスがPnP ID(0x2A50)キャラクタリスティックに対応していないため]
ログの@<code>{Model number}や@<code>{Firmware rev}は、
それぞれDISで実装することが求められるキャラクタリスティックのModel Number String(0x2A24)とFirmware Revision String(0x2A26)から取得したデータです。
GATTのキャラクタリスティックはこのようにデータの仕様を細かく定義しています。
//list[gobot3][サンプルコードの実行結果][plain]{
$ sudo ./ble_device_info "Echowell BH 123456"
2019/09/18 23:32:58 Initializing connections...
2019/09/18 23:32:58 Initializing connection BLEClient-72B41CA7 ...
2019/09/18 23:32:58 Initializing devices...
2019/09/18 23:32:58 Initializing device DeviceInformation-3E0CE82B ...
2019/09/18 23:32:58 Robot bleBot initialized.
2019/09/18 23:32:58 Starting Robot bleBot ...
2019/09/18 23:32:58 Starting connections...
2019/09/18 23:32:58 Starting connection BLEClient-72B41CA7...
2019/09/18 23:33:53 Starting devices...
2019/09/18 23:33:53 Starting device DeviceInformation-3E0CE82B...
2019/09/18 23:33:53 Starting work...
Model number: BH20
Firmware rev: QLN10
Hardware rev: 01_00_0000
Manufacturer name: Echowell
//}
同様に、@<code>{examples/ble_battery.go}を実行することでBASからバッテリー残量を取得できます。
== 心拍数モニタを作る
サンプルコードの動作を確認したので、次は実際のコードを書きます。
心拍数センサから心拍数を取得するために、HRSを一部実装します。
=== GATTクライアント
@<code>{examples/ble_device_info.go}と@<code>{examples/ble_battery.go}を参考にGATTクライアントを実装します(@<list>{client})。
これは、BASとHRSが使用できることを想定したプログラムです。
バッテリー残量の取得(l.26)を実施した後、センサ位置の取得(l.29)と心拍数通知の受け付け開始(l.32)を行います。
//listnum[client][examples/ble_heart_rate.go][go]{
package main
import (
"fmt"
"os"
"gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/ble"
)
func main() {
bleAdaptor := ble.NewClientAdaptor(os.Args[1])
info := ble.NewDeviceInformationDriver(bleAdaptor)
battery := ble.NewBatteryDriver(bleAdaptor)
heartRate := ble.NewHeartRateDriver(bleAdaptor)
work := func() {
// info
fmt.Println("=== Device Information ===")
fmt.Println("Model number:", info.GetModelNumber())
fmt.Println("Firmware rev:", info.GetFirmwareRevision())
fmt.Println("Hardware rev:", info.GetHardwareRevision())
fmt.Println("Manufacturer name:", info.GetManufacturerName())
// battery
fmt.Println("=== Battery Level ===")
fmt.Println("Battery level:", battery.GetBatteryLevel())
// heartRate
fmt.Println("=== Body Sensor Location ===")
loc, _ := heartRate.GetBodySensorLocation()
fmt.Println("Body sensor location:", loc)
fmt.Println("=== Heart Rate ===")
heartRate.SubscribeHeartRate()
}
robot := gobot.NewRobot("bleBot",
[]gobot.Connection{bleAdaptor},
[]gobot.Device{battery, heartRate},
work,
)
robot.Start()
}
//}
@<code>{examples/ble_battery.go}で確認したように、BASドライバはすでに用意されています。
そのため、このコードを実行するためには、次の項目を実装したHRSドライバを用意する必要があります。
1. @<code>{HeartRateDriver}型
2. @<code>{NewHeartRateDriver}関数
3. @<code>{HeartRateDriver.GetBodySensorLocation}メソッド
4. @<code>{HeartRateDriver.SubscribeHeartRate}メソッド
1と2は既存のコードと同じように実装するだけですが、
3と4はGATTの仕様を読みながら実装していく必要がありそうです。
=== HRSドライバ
@<code>{platforms/ble/ble_device_info.go}のDISドライバと
@<code>{platforms/ble/ble_battery.go}のBASドライバを参考に
HRSドライバを実装します(@<list>{hrs1})。
//listnum[hrs1][platforms/ble/heart_rate_driver.go(ひな型)][go]{
package ble
import (
"encoding/binary"
"fmt"
"os"
"time"
"gobot.io/x/gobot"
)
type HeartRateDriver struct {
name string
connection gobot.Connection
gobot.Eventer
}
func NewHeartRateDriver(a BLEConnector) *HeartRateDriver {
n := &HeartRateDriver{
name: gobot.DefaultName("Heart Rate"),
connection: a,
Eventer: gobot.NewEventer(),
}
return n
}
func (b *HeartRateDriver) Name() string { return b.name }
func (b *HeartRateDriver) SetName(n string) { b.name = n }
func (b *HeartRateDriver) Connection() gobot.Connection {
return b.connection
}
func (b *HeartRateDriver) Start() (err error) { return }
func (b *HeartRateDriver) Halt() (err error) { return }
func (b *HeartRateDriver) adaptor() BLEConnector {
return b.Connection().(BLEConnector)
}
// TODO: HeartRateDriver.GetBodySensorLocation()
// TODO: HeartRateDriver.SubscribeHeartRate()
//}
これで、残りは@<code>{HeartRateDriver.GetBodySensorLocation}メソッド
と@<code>{HeartRateDriver.SubscribeHeartRate}メソッドです。
これらは、それぞれHRSで実装することが求められるキャラクタリスティックの
Body Sensor Location(0x2A38)とHeart Rate Measurement(0x2A37)を使用することを想定します。
HRSは、消費カロリーの値をリセットするためのHeart Rate Control Point(0x2A39)の実装も必要としますが、
本稿では省略します@<fn>{hrcp}。
//footnote[hrcp][実装はとても簡単]
=== GATTキャラクタリスティック
HRPの各キャラクタリスティックのための定数を定義します(@<list>{hrs})。
//listnum[hrs][heart_rate_driver.go(キャラクタリスティックの定義)][go]{
// HRS(Heart Rate Service) characteristics
const (
cUUIDHeartRateMeasurement = "2a37"
cUUIDBodySensorLocation = "2a38"
cUUIDHeartRateControlPoint = "2a39"
)
//}
=== Body Sensor Location
@<code>{HeartRateDriver.GetBodySensorLocation}メソッドのために、
Bluetooth SIGの公式サイトからBody Sensor Location(0x2A38)の仕様を確認します(@<list>{xmlbsl})。
//list[xmlbsl][org.bluetooth.characteristic.body_sensor_location.xml(抜粋)][go]{
<Field name="Body Sensor Location">
<Requirement>Mandatory</Requirement>
<Format>8bit</Format>
<Enumerations>
<Enumeration key="0" value="Other"/>
<Enumeration key="1" value="Chest"/>
<Enumeration key="2" value="Wrist"/>
<Enumeration key="3" value="Finger"/>
<Enumeration key="4" value="Hand"/>
<Enumeration key="5" value="Ear Lobe"/>
<Enumeration key="6" value="Foot"/>
<ReservedForFutureUse start="7" end="255"/>
</Enumerations>
</Field>
//}
8bitのデータが1個あり、その値がセンサの位置を示すようです。
この仕様をGoのコードに書き起こします(@<list>{bsl})。
//listnum[bsl][heart_rate_driver.go(Body Sensor Locationの定義)][go]{
// BodySensorLocation value
var mBodySensorLocation = map[uint8]string{
0: "Other",
1: "Chest",
2: "Wrist",
3: "Finger",
4: "Hand",
5: "Ear Lobe",
6: "Foot",
}
//}
キャラクタリスティックの値を読み、Body Sensor Locationの値をstringで返す
@<code>{HeartRateDriver.GetBodySensorLocation}メソッドを実装します(@<list>{getbsl})。
値の読み出しには@<code>{BLEConnecter.ReadCharacteristic}メソッドを使用します。
//listnum[getbsl][heart_rate_driver.go(Body Sensor Locationの取得)][go]{
func (b *HeartRateDriver) GetBodySensorLocation() (string, error) {
c, err := b.adaptor().ReadCharacteristic(cUUIDBodySensorLocation)
if err != nil {
return "", err
}
val := uint8(c[0])
ret := mBodySensorLocation[val]
if ret == "" {
return "", fmt.Errorf("undefined location %v", val)
}
return ret, nil
}
//}
先ほど用意したクライアントのコード(@<code>{examples/ble_heart_rate.go})を実行してみましょう@<fn>{co}。
@<list>{out1}のような出力が得られるでしょう。
//footnote[co][実装していない部分はコメントアウトすること]
//listnum[out1][examples/ble_heart_rate.goの実行結果(1)][plain]{
$ sudo ./ble_heart_rate "Echowell BH 123456"
2019/09/19 18:29:20 Initializing connections...
2019/09/19 18:29:20 Initializing connection BLEClient-6F2E8C08 ...
2019/09/19 18:29:20 Initializing devices...
2019/09/19 18:29:20 Initializing device Battery-141B8478 ...
2019/09/19 18:29:20 Initializing device Heart Rate-5C9C877 ...
2019/09/19 18:29:20 Robot bleBot initialized.
2019/09/19 18:29:20 Starting Robot bleBot ...
2019/09/19 18:29:20 Starting connections...
2019/09/19 18:29:20 Starting connection BLEClient-6F2E8C08...
2019/09/19 18:29:22 Starting devices...
2019/09/19 18:29:22 Starting device Battery-141B8478...
2019/09/19 18:29:22 Starting device Heart Rate-5C9C877...
2019/09/19 18:29:22 Starting work...
=== Battery Level ===
Battery level: 86
=== Body Sensor Location ===
Body sensor location: Chest
//}
Gobotで用意されたBASドライバと先ほど実装したHRSドライバを使用していることがわかります(l.5-l.6)。
今回用意したセンサは胸につけることを想定しているため、
Body Sensor Locationの値は常に1(Chest)が得られます(l.18)。
これで、Body Sensor Locationキャラクタリスティックの実装が完了しました。
=== Heart Rate Measurement
同様に、キャラクタリスティックの通知の受け付けを開始し、通知を受けたらデータを標準出力に書き出す
@<code>{HeartRateDriver.SubscribeHeartRate}メソッドを実装します(@<list>{gethr_proto})。
通知の受け付けには@<code>{BLEConnecter.Subscribe}メソッドを使用します。
//listnum[gethr_proto][heart_rate_driver.go(心拍数計測データの取得)][go]{
func (b *HeartRateDriver) SubscribeHeartRate() error {
err := b.adaptor().Subscribe(cUUIDHeartRateMeasurement,
func(data []byte, _ error) {
fmt.Println(time.Now().Format("15:04:05"), c)
})
return err
}
//}
クライアントのコード(@<code>{examples/ble_heart_rate.go})を実行すると、
@<list>{out2}のような出力が得られるでしょう。
//list[out2][examples/ble_heart_rate.goの実行結果(2)(抜粋)][go]{
=== Heart Rate ===
18:25:39 [22 74 134 127 131 108]
18:25:40 [22 71 56 131 124 111]
18:25:41 [22 69 221 134 117 114]
//}
#@# textlint-disable
データを解析していないのでただのバイト列ですが、
心拍にあわせて出力されているので、あと一歩ですね。
#@# textlint-enable
それでは、先ほどと同様にBluetooth SIGの公式サイトからHeart Rate Measurement(0x2A37)の仕様を確認します。
長いので、概要のみ掲載します。なお、データのバイトオーダはリトルエンディアンと決められています。
* 最初の1バイトは各種フラグを示した値
** 最下位bitは心拍数のデータサイズを示す
** 下位2bit目と下位3bit目はセンサのステータスを示す
** 下位4bit目は消費カロリーデータの有無を示す
** 下位5bit目はR-R間隔データの有無を示す
* 各種フラグに続いて心拍数の値
** 長さはフラグで指定される
** 1バイトまたは2バイトの符号なし整数
* 心拍数に続いて消費カロリーの値
** 消費カロリーのフラグが1の場合のみ
* 消費カロリーに続いてR-R間隔の値
** R-R間隔のフラグが1の場合のみ
まず、各種フラグをGoのコードに書き起こします@<fn>{spec}(@<list>{hrmflags}@<fn>{binlit})。
//footnote[spec][XMLからコードを生成できたら...]
//footnote[binlit][接頭辞@<code>{0b}または@<code>{0B}から始まる2進数リテラルはGo 1.13で追加された]
//listnum[hrmflags][heart_rate_driver.go(Heart Rate Measurementフラグの定義)][go]{
// HeartRateMeasurement flags
var mHeartRateFormat = map[uint8]string{
0b0: "UINT8",
0b1: "UINT16",
}
var mSensorContactStatus = map[uint8]string{
0b00: "not supported",
0b01: "not supported",
0b10: "supported but contact is not detected",
0b11: "supported and contact is detected",
}
var mEnergyExpandedStatus = map[uint8]string{
0b0: "not present",
0b1: "present",
}
var mRRInterval = map[uint8]string{
0b0: "not present",
0b1: "present (one or more)",
}
//}
続いて、1バイトのデータから各種フラグを取り出す関数を実装します(@<list>{hrmparse})。
バイトから特定ビットの値を取り出すにはシフト演算とAND演算を用います(@<code>{parseHeartRateFlags}関数)。
//list[hrmparse][heart_rate_driver.go(Heart Rate Measurementフラグの解析)][go]{
type HeartRateFlags struct {
heartRateFormat uint8
sensorContactStatus uint8
energyExpendedStatus uint8
rrInterval uint8
}
func parseHeartRateFlags(flags byte) HeartRateFlags {
var hrf HeartRateFlags
hrf.heartRateFormat = flags & 0b1
hrf.sensorContactStatus = flags >> 1 & 0b11
hrf.energyExpendedStatus = flags >> 3 & 0b1
hrf.rrInterval = flags >> 4 & 0b1
return hrf
}
func (hrf HeartRateFlags) String() string {
return fmt.Sprintf("HeartRateFormat: %v\n",
mHeartRateFormat[hrf.heartRateFormat]) +
fmt.Sprintf("SensorContactStatus: %v\n",
mSensorContactStatus[hrf.sensorContactStatus]) +
fmt.Sprintf("EnergyExpandedStatus: %v\n",
mEnergyExpandedStatus[hrf.energyExpendedStatus]) +
fmt.Sprintf("RR-Interval: %v",
mRRInterval[hrf.rrInterval])
}
//}
そして、@<code>{HeartRateDriver.SubscribeHeartRate}メソッドにデータ解析のためのコードを追加します(@<list>{gethr})。
//listnum[gethr][heart_rate_driver.go(心拍数の取得)][go]{
func (b *HeartRateDriver) SubscribeHeartRate() error {
err := b.adaptor().Subscribe(cUUIDHeartRateMeasurement,
func(data []byte, e error) {
if e != nil {
fmt.Fprintf(os.Stderr, "err: %v", e)
return
}
hr, hrf, e := ParseHeartRate(data)
if e != nil {
fmt.Fprintf(os.Stderr, "err: %v", e)
return
}
fmt.Println(hrf)
fmt.Printf("HeartRate: %v\n", hr)
fmt.Println("--------------------")
})
return err
}
//}
最後に、クライアントのコード(@<code>{examples/ble_heart_rate.go})を実行します。
@<list>{out3}のように、解析されたデータが得られるでしょう。
//list[out3][examples/ble_heart_rate.goの実行結果(3)(抜粋)][go]{
=== Heart Rate ===
18:29:22
HeartRateFormat: UINT8
SensorContactStatus: supported and contact is detected
EnergyExpandedStatus: not present
RR-Interval: present (one or more)
HeartRate: 66
--------------------
18:29:23
HeartRateFormat: UINT8
SensorContactStatus: supported and contact is detected
EnergyExpandedStatus: not present
RR-Interval: present (one or more)
HeartRate: 67
--------------------
//}
心拍数が正しく取得できています。
これで、Heart Rate Measurementキャラクタリスティックの実装が完了しました。
== 結び
#@# textlint-disable
本稿ではBLEの概要、GoのBLEライブラリ、GobotでGATTクライアントを作る方法を確認しました。
加えて、キャラクタリスティックから取得したバイト列を仕様に従い解析する方法を確認しました。
これらにより、BLEを用いた簡単なアプリケーションを一通り作ることができると考えます。
外部デバイスと連携するアプリケーションは非同期処理を多用しますが、
Goはゴルーチンとチャネルでこれをわかりやすく実装できます。
さらに、GobotフレームワークはBLE以外にもさまざまなプラットフォームに対応しています。
結構遊べそうなので、皆さんもぜひお試しください。
#@# textlint-enable