KKP(くるくるピ)
KKP(くるくるピ)とは?
自動車部品メーカーのデンソーが作ったBLEのリモコン。車のハンドルに取り付けることでスマホをタッチせずに親指を使うことでスマホを遠隔操作できる。BLEなので通信距離は、10mぐらい、従来のBluetooth2~3よりも接続に必要な時間が短く、瞬時に繋がる。BLEで接続するため、消費電力が少ないので、電池交換は1年に1回ぐらい。くるくるピの名前の由来は、おそらくダイヤル(ロータリー)スイッチがクルクル回るから?Yahoo!カーナビも対応予定があるそうです。
利用シーン
スマホを手に持って操作できないシーンでくるくるピを使おう。
- 車のハンドルに取り付けて、スマホを触らずに音楽再生やカーナビアプリを操作。
- タブレットを見ながら、プレゼン資料をスライド操作
- ランニング中にスマホを操作する
注意点
BLEは比較的新しい技術のため、OSや端末の相性がある。またKKPに対応したアプリでないと操作することができない。
アプリ開発者目線
面倒くさいBLEの通信処理や、ハードウェアやBLEの知識(ペリフェラルやアドバタイズ)がないアプリ開発者でも、UIEvolutionと共同開発して作られたUIE MultiAccessを使うことで、簡単にBluetoothの通信処理や、フォーカス、音声アシストをアプリに組み込むことが出来る。
iOSエミュレータ
iOSで動くエミュレータアプリがあるので、手元にKKPがなくてもUIE MultiAccessを使ったアプリのデバッグが可能かも?。iOSの端末がないので未確認です。
KKP(くるくるピ)は、どこで購入できるの?
楽天(下記URL)から購入することが出来ます。2016年2月2日現在は、税込 5,400 円(送料別)
#Spin n' Click(スピン アンド クリック)
https://play.google.com/store/apps/details?id=com.denso.viper.launcher&hl=ja
Spin n' Click(スピン アンド クリック)とは?
スマホにインストールされているアプリ一覧から、KKPでコントロールすることが出来る、KKP対応アプリ(サードパーティー製アプリ)だけをフィルタリングして、ランチャー形式で表示してくれるアプリ。もちろん、Spin n' Clickは、KKPで操作することが可能。Spin n' Clickからサードパーティのアプリを起動する際は、BLEの接続を維持したまま、シームレスに起動することが出来るのがすごい。
#注意点
- AndroidのOSが4.4以上
- Bluetooth Low Energyに対応している端末
- Nexus5, Nexus6, GalaxyS4, XperiaZ3, XperiaZ4
BLEは比較的新しい機能なのでOSが4.4でもBLEに未対応の機種があります。KKPを購入される際は、端末側のスペックなどをチェックして購入すること。必要であれば、メーカーに一度、確認することをオススメします。
#KKP対応のアプリを作るための準備
##会員登録が必要
デンソーが運営する、DENSO Connectivity Portalにて、
https://developer.navicon.com/regist/prov
##ドキュメントやライブラリをダウンロード
下記リンクにアクセスして、ページの一番下にある、「Android版ライブラリ」をクリックしてダウンロードする。
https://developer.navicon.com/service/viper
#デモアプリを動かす
SampleLauncher-src_denso.tbz(デモアプリ)を解凍して、Android StudioのFile→Open->SampleLauncherを選択してインポート、ビルドする。KKPとアプリが接続出来ない場合は、BluetoothがONになっているか確認すること。それでも接続できない場合は、OS側のBluetoothを一度、OFFにしてからONにすると直ります。
##UIE MultiAccess
先ほどビルドしたSampleLauncherのプロジェクトファイルには、KKPと連携するのに必要なUIE MultiAccessが含まれている。SampleLauncher -> libs -> UIEMultiAccess.aar。aarは、Android Studioのライブラリ拡張子。
#Android Studioの新規プロジェクトにUIE MultiAccessを組み込む。
##適当にEmpty projectを作成
注意点としてUIE MultiAccessはAndroid4.4から対応しているので、Minimum SDKを4.4に合わせる。
UIEMultiAccess.aarをコピ
先ほど動かしたサンプルアプリSampleLauncherのlibsフォルダにあるUIEMultiAccess.aarをコピーする。
##新規で作ったプロジェクトのlibsフォルダに貼り付ける。
libsを選択して、右クリックしてペースト。libsがない人はProjectになっていることを確認する。
##build.gradle編集
##compile(name:'UIEMultiAccess', ext:'aar')を追加
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile(name:'UIEMultiAccess', ext:'aar') <-----(*´ω`*)
}
##libsにあるファイルを読み込む設定
repositories {
jcenter()
flatDir {
dirs 'libs'
}
}
##AndroidManifest.xmlにBluetoothの権限を追加
- uses-permission android:name="android.permission.READ_PHONE_STATE"
- uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="araiyusuke.kkpsample">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
##MainActivity
##UIE MultiAccessテスト
package araiyusuke.kkpsample;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import uie.multiaccess.app.UMAApplication;
public class MainActivity extends AppCompatActivity {
private UMAApplication umaApplication = UMAApplication.INSTANCE;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
umaApplication.create(this);
}
}
cannot resolve symbolエラーが。。。
Android Studio -> File -> invalidate Caches / Restartを実行すると動くよ!
##MainActivityにイベントリスナーを追加
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import uie.multiaccess.app.UMAApplication;
import uie.multiaccess.input.UMAHIDConstants;
import uie.multiaccess.input.UMAHIDInputEventListener;
import uie.multiaccess.input.UMASensorEvent;
public class MainActivity extends AppCompatActivity implements UMAHIDInputEventListener {
private UMAApplication umaApplication = UMAApplication.INSTANCE;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
umaApplication.create(this);
}
@Override
protected void onStart() {
super.onStart();
umaApplication.start(this);
}
@Override
protected void onResume() {
super.onResume();
umaApplication.resume(this);
}
@Override
protected void onPause() {
super.onPause();
umaApplication.pause(this);
}
@Override
protected void onStop() {
super.onStop();
umaApplication.stop(this);
}
@Override
protected void onDestroy() {
umaApplication.destroy(this);
super.onDestroy();
}
//ダイヤルが回転
@Override
public boolean onRotate(int distance, int direction) {
Log.d("ダイヤル回転", "onRotate" + "distance" + ":" + distance + ":" + "direction" + ":" + direction);
switch (direction) {
case UMAHIDConstants.ROTATE_CLOCKWISE:
Log.d("ダイヤル回転","時計回り");
break;
case UMAHIDConstants.ROTATE_ANTI_CLOCKWISE:
Log.d("ダイヤル回転","反時計回り");
break;
default:
}
return false;
}
//方向キーが押された
@Override
public boolean onTranslate(int distanceX, int distanceY) {
Log.d("方向キー", "onTranslate" + ":" + distanceX + ":" + distanceY);
if (distanceX == 0 && distanceY == -1) {
Log.d("方向キー","↑");
} else if (distanceX == 0 && distanceY == 1) {
Log.d("方向キー","↓");
} else if (distanceX == -1 && distanceY == 0) {
Log.d("方向キー","←");
} else if (distanceX == 1 && distanceY == 0) {
Log.d("方向キー", "→");
}
return false;
}
//ボタンを離した(ボタンを押して ~ 離すまで時間が開くと呼ばれない)
@Override
public boolean onPressUpButton(int button) {
Log.d("ボタン離された", ":" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ボタン離された","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ボタン離された","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ボタン離された","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ボタン離された","ホームボタン");
break;
default:
}
return false;
}
//ボタンが押された(方向キーは除く)
@Override
public boolean onPressDownButton(int button) {
Log.d("ボタン押す", "opPressDownButton" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ボタン押す","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ボタン押す","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ボタン押す","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ボタン押す","ホームボタン");
break;
default:
}
return false;
}
//ボタンのダブルクリック(方向キーは除く)
@Override
public void onDoubleClickButton(int button) {
Log.d("ダブルクリック", ":" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ダブルクリック","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ダブルクリック","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ダブルクリック","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ダブルクリック","ホームボタン");
break;
default:
}
}
@Override
public void onLongPressButton(int button) {
}
//ボタン長押し
@Override
public void onLongPressButton(int button, int state) {
if (state == UMAHIDConstants.GESTURE_STATE_ENDED ) {
Log.d("長押し", "長押し終了");
} else if (state == UMAHIDConstants.GESTURE_STATE_BEGAN) {
Log.d("長押し", "長押し開始");
}
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("長押し", "エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("長押し","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("長押し","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("長押し","ホームボタン");
break;
case UMAHIDConstants.BUTTON_LEFT:
Log.d("長押し","←");
break;
case UMAHIDConstants.BUTTON_UP:
Log.d("長押し","↑");
break;
case UMAHIDConstants.BUTTON_DOWN:
Log.d("長押し","↓");
break;
case UMAHIDConstants.BUTTON_RIGHT:
Log.d("長押し","→");
break;
}
}
//動かない
@Override
public void onAccelerometerUpdate(UMASensorEvent sensor) {
Log.d("加速度センサー", sensor.x + ":" + sensor.y + ":" + sensor.z);
}
}
イベントリスナーの詳細
KKPのボタン画像を見ながら、イベントリスナーの処理を解説していく
onRotate(int distance, int direction)
ロータリースイッチをくるくる回すことで呼び出されるイベントリスナー。引数のdirectionには、どちらの方向に回したのか向き情報が代入されている。
- UMAHIDConstants.ROTATE_CLOCKWISE(時計回り)
- UMAHIDConstants.ROTATE_ANTI_CLOCKWISE(反時計回り)
//ダイヤルが回転
@Override
public boolean onRotate(int distance, int direction) {
Log.d("ダイヤル回転", "onRotate" + "distance" + ":" + distance + ":" + "direction" + ":" + direction);
switch (direction) {
case UMAHIDConstants.ROTATE_CLOCKWISE:
Log.d("ダイヤル回転","時計回り");
break;
case UMAHIDConstants.ROTATE_ANTI_CLOCKWISE:
Log.d("ダイヤル回転","反時計回り");
break;
default:
}
return false;
}
onPressDownButton(int button)
ボタン(方向キー除く)が押された時に呼び出されるイベントリスナ。
//ボタンが押された(方向キーは除く)
@Override
public boolean onPressDownButton(int button) {
Log.d("ボタン押す", "opPressDownButton" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ボタン押す","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ボタン押す","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ボタン押す","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ボタン押す","ホームボタン");
break;
default:
}
return false;
}
onPressUpButton(int button)
エンターボタン、戻るボタン、ホームボタン、マルチキーボタンがプッシュダウン、プッシュアップされると呼び出されるイベントリスナー。方向キーは対応していない。
- UMAHIDConstants.BUTTON_MAIN(エンター)
- UMAHIDConstants.BUTTON_VR(マルチキー)
- UMAHIDConstants.BUTTON_BACK(戻る)
- UMAHIDConstants.BUTTON_HOME(ホーム)
//ボタンが押された(方向キーは除く)
@Override
public boolean onPressDownButton(int button) {
Log.d("ボタン押す", "opPressDownButton" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ボタン押す","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ボタン押す","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ボタン押す","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ボタン押す","ホームボタン");
break;
default:
}
return false;
}
onTranslate(int distanceX, int distanceY)
方向キーが押された時に呼び出されるイベントリスナー。
//方向キーが押された
@Override
public boolean onTranslate(int distanceX, int distanceY) {
Log.d("方向キー", "onTranslate" + ":" + distanceX + ":" + distanceY);
if (distanceX == 0 && distanceY == -1) {
Log.d("方向キー","↑");
} else if (distanceX == 0 && distanceY == 1) {
Log.d("方向キー","↓");
} else if (distanceX == -1 && distanceY == 0) {
Log.d("方向キー","←");
} else if (distanceX == 1 && distanceY == 0) {
Log.d("方向キー", "→");
}
return false;
}
onDoubleClickButton(int button)
ボタン(方向キーを除く)をダブルクリックした時に呼び出されるイベントリスナー。
//ボタンのダブルクリック(方向キーは除く)
@Override
public void onDoubleClickButton(int button) {
Log.d("ダブルクリック", ":" + button);
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("ダブルクリック","エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("ダブルクリック","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("ダブルクリック","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("ダブルクリック","ホームボタン");
break;
default:
}
}
onLongPressButton(int button, int state)
ボタンが長押しされた時に呼び出されるイベントリスナー。すべてのボタン(方向キー、エンターボタン、戻るボタン、ホームボタン、マルチキー)に対応している。引数のbuttonには、どの場所が押されたのか判別するための値が代入されている。stateには、押した時(0)、離した時(1)の情報が代入されている。
//ボタン長押し
@Override
public void onLongPressButton(int button, int state) {
if (state == UMAHIDConstants.GESTURE_STATE_ENDED ) {
Log.d("長押し", "長押し終了");
} else if (state == UMAHIDConstants.GESTURE_STATE_BEGAN) {
Log.d("長押し", "長押し開始");
}
switch (button) {
case UMAHIDConstants.BUTTON_MAIN:
Log.d("長押し", "エンターボタン");
break;
case UMAHIDConstants.BUTTON_VR:
Log.d("長押し","マルチキー");
break;
case UMAHIDConstants.BUTTON_BACK:
Log.d("長押し","戻るボタン");
break;
case UMAHIDConstants.BUTTON_HOME:
Log.d("長押し","ホームボタン");
break;
case UMAHIDConstants.BUTTON_LEFT:
Log.d("長押し","←");
break;
case UMAHIDConstants.BUTTON_UP:
Log.d("長押し","↑");
break;
case UMAHIDConstants.BUTTON_DOWN:
Log.d("長押し","↓");
break;
case UMAHIDConstants.BUTTON_RIGHT:
Log.d("長押し","→");
break;
}
}
onAccelerometerUpdate(UMASensorEvent sensor)
動かない。
//動かない
@Override
public void onAccelerometerUpdate(UMASensorEvent sensor) {
Log.d("加速度センサー", sensor.x + ":" + sensor.y + ":" + sensor.z);
}
#BLE通信処理
HIDManager.isBLESupported()
AndroidのOSの設定をBluetooth Offの状態だとfalseが帰ってきた。BLE未対応の端末がないのでわかりませんが、Bluetooth4.0に対応していない場合もfalseが帰ってくるのかも。
if (HIDManager.isBLESupported()) {
Log.d("デバッグ","BLEサポート");
BluetoothをONにしてくださいアラートを表示させたり、
BLEに対応していないのでアプリが使えないなどメッセージを表示する。
} else {
Log.d("デバッグ","BLE NOT サポート");
}
UMAHIDManager
接続処理は自動と手動をサポートしている。
Automatic connection モード
デバイスを発見すると自動で接続される。処理もシンプルです。
Manual connection モード
接続デバイスは、KKPだけとは限らない。他社からUIE MultiAccessに対応したデバイスが発売する可能性もあるのかもしれない。対応していないデバイスに自動で接続されるのは問題があるので、
接続先のデバイスを指定できるモードも用意されている。
private UMAApplication umaApplication = UMAApplication.INSTANCE;
private UMAHIDManager mHIDManager;
private static final int CONNECTION_TIMEOUT_MS = 5000; /* 5秒 */
private static final int DISCOVERY_TIMEOUT_MS = 10000; /* 10秒 */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
umaApplication.create(this);
mHIDManager = (UMAHIDManager)umaApplication.getUMAService(UMAApplication.UMA_HID_SERVICE);
mHIDManager.addHIDManagerCallback(new SampleHIDManagerCallback(new Handler(getMainLooper())));
//手動で接続
mHIDManager.enableManualConnection(DISCOVERY_TIMEOUT_MS);
mHIDManager.startDiscoverDevice();
}
private class SampleHIDManagerCallback extends UMAHIDManagerCallback {
private Handler mHandler;
SampleHIDManagerCallback(Handler handler) {
mHandler = handler;
}
public void onInputDeviceDiscovered(UMAInputDevice device){
Log.d("デバッグ","onInputDeviceDiscovered" + ":" + device.getDevice().getName());
mHIDManager.connectDevice(device, CONNECTION_TIMEOUT_MS);
}
public void onInputDeviceDiscoveryStarted() {
Log.d("デバッグ","onInputDeviceDiscoveryStarted");
}
public void onInputDeviceDiscoveryStopped(final int stopReason) {
Log.d("デバッグ","onInputDeviceDiscoveryStopped");
}
public void onInputDeviceConnected(final UMAInputDevice device) {
Log.d("デバッグ","onInputDeviceConnected");
}
public void onInputDeviceFailToConnect(int error) {
Log.d("デバッグ","onInputDeviceFailToConnect");
}
public void onInputDeviceDisconnected(UMAInputDevice device) {
Log.d("デバッグ","onInputDeviceDisconnected");
}
}
##再接続
UMAHIDManagerには、一度切断されたデバイスに再接続する機能も用意されている。前回接続したデバイスを記録しているので、discoveryせずに再接続が可能。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
umaApplication.create(this);
UMAHIDManager HIDManager = (UMAHIDManager) UMAApplication.INSTANCE.getUMAService(UMAApplication.UMA_HID_SERVICE);
if (HIDManager.isBLESupported()) {
HIDManager.enableAutoConnection(5000, 5000, 5000);
if (!HIDManager.connectLastConnectedDevice(5000)) {
HIDManager.startDiscoverDevice();
}
}
}
まとめ
KKP(くるくるピ)の簡単な説明、開発環境、接続について、まとめてみました。
#次回
次回はデモアプリを作りながら、UIE MultiAccessのフォーカス、音声読み上げ機能を紹介します。