何年も前から、運動時の記録が残せるような心拍計が欲しかった。Androidが登場してからはAndroid端末と連動する心拍計が欲しくなった。今ならBluetooth Low Energy (BLE)準拠のものを選ぶのが妥当な判断だろうが、残念ながら自分の使いたい端末で対応していない。悩んだ結果ANT+準拠のものを注文した。

CooSpo ANT+無線心拍計
それが中国から6/28に届いた。6/11にebay.comで注文したことになっているから、2週間強かかったことになる。まぁ、早い方ではないだろうか。
商品写真では”CooSpo”というブランド名らしきものが印字されている。が、自分のところに配達されたものは無印。ちなみに、文字のスタイリィングによって、”p”がなかなか認識できなかった。が、”Coo(l)” + “Spo(rt)”ということなんだろう。わかってしまえばなぜわからなかったかの方が不思議なくらい。
だが、”CooSpo”がわかったからといっても特に意味がない。どこの会社のブランドであるか、とか、この心拍計のモデル名/番号があるか、などは全くの謎だ。まぁ中華グッズにはよくある話。
説明書によると、チェストストラップの内側に電極部がある(黄色線で囲んだ部分)ということだが、これは知らなかった。てっきり、計測器の裏側部分だけがそういう役目を果たすものと思っていた。この製品だけの話か、他の同様の製品にも当てはまるのか、自分にはわからない。
ただ、心拍計ユニットをストラップに固定するスナップの間隔が4.5cm、というのは標準サイズのようで(写真は、今回購入したCooSpoのものではなく、eBayの一リスティングから)、以前買った中国Kyto社のHRM-2830のものとサイズ的には互換性がある。だが、パッと試した範囲では、CooSpoの計測ユニットをHRM-2830のベルトにつけて見ても、スマホから心拍計に接続できなかった。これがひょっとするとHRM-2830が使えなかったことと関係しているのかもしれない。
ちなみに、中国Kyto社のHRM-2830と今回のCooSpoの比較というと、計測ユニットはどちらも横幅はほぼ同じだが、CooSpoのものの方が縦に若干長く、かつ厚い。そうはいっても、着用しているときには全く気にならない程度。

Xperia X10 Mini, Galaxy Note 3とも無事接続(両端)。だが、Xperia Acro IS11S(真ん中)はダメな模様。
早速、ANT+をサポートしているはずのXperia X10 Mini E10i, Xperia Acro IS11S、Galaxy Note 3 N-9005でテスト。GN3はストックROMのまま(ただしルート化済み)だが、X10 MiniにはGinger BreadベースのカスタムROM MiniCM 7をインストールしてあるし、AcroにはIce Cream SandwitchベースのカスタムROMがインストールしてある。それぞれ、ANT Radio ServiceとANT+ Pluginsの2つのアプリが最初からプリインストールされているか(GN3)か、このために自分でインストールした(他の2機種)。後日他の連携Androidアプリについても調べてみた。
その上でANT+ Heart Rate Grapherで心拍数をモニタしてみた。簡単なペアリングの後(Bluetoothのようにパスキーの入力などはない)、X10 Mini, GN3では写真のように無事計測し始めたが、Acroではうまくいかない。
「ハートレートセンサー購入しました」、「Xperia acro IS11S + GARMIN Hart rate monitor」、などを見るとIS11Sにハードウェア的にANT+機能があることは間違いないと考えてよさそうだ。なぜうまくいかないのかわからないが、実際に心拍計と併用するのはX10 Miniのつもりだったので深入りしないようにする。一応接続に失敗した時のログは取ってみたので、この記事の末尾に付けておく。
これでわかるように、ANT+では1対多の通信が可能な模様。というか、既にペアリング済みの受信機が複数あるときに、特定の1台だけに送信する、ということはできないんじゃないか。もっとも実装を考えればこちらの方が簡単なんだろうが。
WikipediaのANT+のエントリ内の “ANT+ Plugins on Android”のセクションには以下の記述がある:
The ANT+ Plugins also feature multi-app concurrency, where any number of apps may have access to the same data coming from the same sensor simultaneously, and any number of phones/devices may read data from the same sensor simultaneously. This opens additional use cases such as always-on background monitoring for multiple apps or group training, and further reduces the burden on developers by helping to ensure them access to device data no matter which apps may be running at the moment.
しかし自分が試した範囲では、同じAndroid端末内では、受信を受けられるのは1アプリに限られるようだった。つまり、何かのアプリが心拍数データを受信している状態で、同じ端末上の他のアプリに心拍数データを受信させようとすると、「ANT+チャネルが使えない」といったエラーが表示され、場合によっては「無理やりこちらに奪うか?」というようなメッセージが表示された。これはGBベースのX10 Miniでの話なので、それが関係しているのかもしれない。その他のバージョンのAndroidの端末では試していない。
以下、「ほなみん化」したXperia Acro IS11Sで、心拍計に接続できなかったときのログ。
---- Jun 28, 2014 2:02:57 PM ---- 06-28 13:46:36.257 349 657 I ActivityManager: START {cmp=com.dsi.ant.plugins.antplus/.utility.db.Activity_PluginMgrDashboard} from pid 4322 06-28 13:46:38.577 349 618 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.antplus.pluginsampler/.Activity_Dashboard} from pid 690 06-28 13:46:38.657 349 349 I ActivityManager: Start proc com.dsi.ant.antplus.pluginsampler for activity com.dsi.ant.antplus.pluginsampler/.heartrate.Activity_SearchUiHeartRateSampler: pid=4355 uid=10142 gids={} 06-28 13:46:39.907 349 657 I ActivityManager: Start proc com.dsi.ant.service.socket for service com.dsi.ant.service.socket/com.dsi.ant.service.AntRadioService: pid=4371 uid=10137 gids={3003} 06-28 13:47:03.047 4355 4355 E ActivityThread: Activity com.dsi.ant.antplus.pluginsampler.heartrate.Activity_SearchUiHeartRateSampler has leaked ServiceConnection com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc$2@2bef2db8 that was originally bound here 06-28 13:47:03.047 4355 4355 E ActivityThread: android.app.ServiceConnectionLeaked: Activity com.dsi.ant.antplus.pluginsampler.heartrate.Activity_SearchUiHeartRateSampler has leaked ServiceConnection com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc$2@2bef2db8 that was originally bound here 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.bindAndRequest(AntPluginPcc.java:638) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.requestAccess_Helper_SubMain(AntPluginPcc.java:549) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.requestAccess_Helper_Main(AntPluginPcc.java:522) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.requestAccess_Helper_SearchActivity(AntPluginPcc.java:367) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.requestAccess(AntPlusHeartRatePcc.java:197) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.requestAccess(AntPlusHeartRatePcc.java:246) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.antplus.pluginsampler.heartrate.Activity_SearchUiHeartRateSampler.requestAccessToPcc(Activity_SearchUiHeartRateSampler.java:31) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.antplus.pluginsampler.heartrate.Activity_HeartRateDisplayBase.handleReset(Activity_HeartRateDisplayBase.java:90) 06-28 13:47:03.047 4355 4355 E ActivityThread: at com.dsi.ant.antplus.pluginsampler.heartrate.Activity_HeartRateDisplayBase.onOptionsItemSelected(Activity_HeartRateDisplayBase.java:389) 06-28 13:48:12.634 349 613 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.antplus.grapher.heartrate/.HrGrapherActivity} from pid 690 06-28 13:48:12.934 349 619 I ActivityManager: Start proc com.dsi.ant.antplus.grapher.heartrate for activity com.dsi.ant.antplus.grapher.heartrate/.HrGrapherActivity: pid=4427 uid=10141 gids={} 06-28 13:49:38.824 349 360 I ActivityManager: Process com.dsi.ant.plugins.antplus.managerlauncher (pid 4322) has died. 06-28 13:49:40.824 349 574 I ActivityManager: Process com.dsi.ant.antplus.pluginsampler (pid 4355) has died. 06-28 13:49:40.824 349 574 I WindowManager: WIN DEATH: Window{2bfc9468 com.dsi.ant.antplus.pluginsampler/com.dsi.ant.antplus.pluginsampler.Activity_Dashboard paused=false} 06-28 13:49:51.124 349 574 I ActivityManager: Process com.dsi.ant.plugins.antplus (pid 4334) has died. 06-28 13:50:08.654 349 657 I ActivityManager: Process com.dsi.ant.service.socket (pid 4371) has died. 06-28 13:53:20.285 349 574 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.dsi.ant.plugins.antplus.managerlauncher cmp=com.dsi.ant.plugins.antplus.managerlauncher/.PluginManagerLauncher} from pid 690 06-28 13:53:20.485 349 619 I ActivityManager: Start proc com.dsi.ant.plugins.antplus.managerlauncher for activity com.dsi.ant.plugins.antplus.managerlauncher/.PluginManagerLauncher: pid=5401 uid=10139 gids={} 06-28 13:53:20.705 349 359 I ActivityManager: START {cmp=com.dsi.ant.plugins.antplus/.utility.db.Activity_PluginMgrDashboard} from pid 5401 06-28 13:53:20.825 349 657 I ActivityManager: Start proc com.dsi.ant.plugins.antplus for activity com.dsi.ant.plugins.antplus/.utility.db.Activity_PluginMgrDashboard: pid=5413 uid=10138 gids={} 06-28 13:53:26.185 349 613 I WindowManager: WIN DEATH: Window{2c69d9a0 com.dsi.ant.antplus.grapher.heartrate/com.dsi.ant.antplus.grapher.heartrate.HrGrapherActivity paused=false} 06-28 13:53:26.185 349 614 I ActivityManager: Process com.dsi.ant.antplus.grapher.heartrate (pid 4427) has died. 06-28 13:53:26.195 349 614 W ActivityManager: Scheduling restart of crashed service com.dsi.ant.antplus.grapher.heartrate/.HrCollectorService in 5000ms 06-28 13:53:31.315 349 371 I ActivityManager: Start proc com.dsi.ant.antplus.grapher.heartrate for service com.dsi.ant.antplus.grapher.heartrate/.HrCollectorService: pid=5430 uid=10141 gids={} 06-28 13:53:33.065 349 359 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.antplus.grapher.heartrate/.HrGrapherActivity} from pid 690 06-28 13:53:33.905 349 618 I ActivityManager: Start proc com.dsi.ant.service.socket for service com.dsi.ant.service.socket/com.dsi.ant.service.AntRadioService: pid=5450 uid=10137 gids={3003} 06-28 13:53:46.155 349 619 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.plugins.antplus.managerlauncher/.PluginManagerLauncher} from pid 690 06-28 13:53:46.465 349 349 I ActivityManager: START {cmp=com.dsi.ant.plugins.antplus/.utility.db.Activity_PluginMgrDashboard} from pid 5401 06-28 13:53:48.275 349 574 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.antplusdemo/.ANTPlusDemo} from pid 690 06-28 13:53:48.525 349 618 I ActivityManager: Start proc com.dsi.ant.antplusdemo for activity com.dsi.ant.antplusdemo/.ANTPlusDemo: pid=5472 uid=10140 gids={} 06-28 13:54:10.155 349 657 I ActivityManager: START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.dsi.ant.antplus.pluginsampler/.Activity_Dashboard} from pid 690 06-28 13:54:10.645 349 561 I ActivityManager: Start proc com.dsi.ant.antplus.pluginsampler for activity com.dsi.ant.antplus.pluginsampler/.Activity_Dashboard: pid=5495 uid=10142 gids={} 06-28 13:54:18.145 349 561 I ActivityManager: START {cmp=com.dsi.ant.antplus.pluginsampler/.heartrate.Activity_SearchUiHeartRateSampler} from pid 5495 06-28 13:56:09.195 349 359 I ActivityManager: Process com.dsi.ant.antplus.grapher.heartrate (pid 5430) has died. 06-28 13:56:09.195 349 359 W ActivityManager: Scheduling restart of crashed service com.dsi.ant.antplus.grapher.heartrate/.HrCollectorService in 5000ms 06-28 13:56:09.805 349 561 I ActivityManager: Process com.dsi.ant.plugins.antplus.managerlauncher (pid 5401) has died. 06-28 13:56:10.915 349 349 I ActivityManager: Process com.dsi.ant.antplus.pluginsampler (pid 5495) has died. 06-28 13:56:10.915 349 349 I WindowManager: WIN DEATH: Window{2c0355a8 com.dsi.ant.antplus.pluginsampler/com.dsi.ant.antplus.pluginsampler.Activity_Dashboard paused=false} 06-28 13:56:10.925 349 587 I WindowManager: WIN DEATH: Window{2c26dbf8 com.dsi.ant.antplus.pluginsampler/com.dsi.ant.antplus.pluginsampler.heartrate.Activity_SearchUiHeartRateSampler paused=false} 06-28 13:56:10.925 349 390 I WindowManager: WINDOW DIED Window{2c26dbf8 com.dsi.ant.antplus.pluginsampler/com.dsi.ant.antplus.pluginsampler.heartrate.Activity_SearchUiHeartRateSampler paused=false} 06-28 13:56:14.225 349 371 I ActivityManager: Start proc com.dsi.ant.antplus.grapher.heartrate for service com.dsi.ant.antplus.grapher.heartrate/.HrCollectorService: pid=5628 uid=10141 gids={} 06-28 13:56:39.805 349 613 I ActivityManager: Process com.dsi.ant.antplusdemo (pid 5472) has died. 06-28 13:56:42.885 349 587 I ActivityManager: Process com.dsi.ant.plugins.antplus (pid 5413) has died. 06-28 13:57:42.015 349 613 I ActivityManager: Process com.dsi.ant.service.socket (pid 5450) has died. 06-28 13:58:55.283 437 437 E wpa_supplicant: Unsupported command: SETSUSPENDOPT 0 06-28 14:00:06.783 349 619 I ActivityManager: Start proc com.dsi.ant.antplus.pluginsampler for activity com.dsi.ant.antplus.pluginsampler/.heartrate.Activity_SearchUiHeartRateSampler: pid=6062 uid=10142 gids={} 06-28 14:00:07.733 349 359 I ActivityManager: Start proc com.dsi.ant.plugins.antplus for service com.dsi.ant.plugins.antplus/.heartrate.HeartRateService: pid=6078 uid=10138 gids={} 06-28 14:00:08.333 349 360 I ActivityManager: Start proc com.dsi.ant.service.socket for service com.dsi.ant.service.socket/com.dsi.ant.service.AntRadioService: pid=6094 uid=10137 gids={3003} 06-28 14:00:08.593 349 384 I ActivityManager: Displayed com.dsi.ant.antplus.pluginsampler/.heartrate.Activity_SearchUiHeartRateSampler: +1s847ms 06-28 14:00:20.333 437 437 E wpa_supplicant: Unsupported command: SETSUSPENDOPT 0 06-28 14:00:20.663 437 437 E wpa_supplicant: Unsupported command: SETSUSPENDOPT 0 ---- Jun 28, 2014 2:02:57 PM ----