このページの本文へ

車とスマホがつながるSDLの世界第9回

SDLコンテスト締切直前、コード&解説一挙公開!!

車両情報+ニュース読み上げアプリを作ってみた!

2019年01月31日 15時00分更新

文● 柴田文彦 アプリ制作●KDDIテクノロジー 編集●ASCII

  • この記事をはてなブックマークに追加
  • 本文印刷

 この記事では、SDLに対応したオリジナルAndroidアプリの作成を通して、SDL車載機と通信して車両情報を取得しながら動作するSDLアプリ開発のツボを、ソースコードを含む実例によって解説する。

図1:今回作成したアプリの車載機側の待機画面のサンプル。実際には細かい文字は、停車時のみ表示される

 実際にSDL対応Androidアプリを開発したのは、株式会社KDDIテクノロジーのチームだ。その際のノウハウを惜しげもなく公開してくれただけでなく、開発の過程に沿って3通りのアプリの全ソースコードを、GitHubで共有してくれている。これらの情報は、SDLアプリ開発のための即戦力となるはずだ。

開発するのは車両情報を表示しつつ、ニュースも読み上げるアプリ

 今回解説するアプリは、SDL車載機とAndroidアプリの組み合わせによって、どのような機能を実現できそうかという可能性を提示するためのものだ。そして、望む機能をアプリとして実装するためには、何をどのようにすればよいのか、その際にどのようなことが問題となりそうで、その解決策は何かといった、すぐに役立つノウハウを提供することを目指している。

 アプリの完成形としては、車載機経由で車両データを取得し、車の状態や環境の変化に応じて音声でガイドして、運転をサポートしてくれるものとなる(図1)。さらに、安全を確保した状態で最新ニュースを読み上げるなど、インターネットに常時接続したスマホならでは機能も盛り込んでいる。

今回のアプリ開発の3つのフェーズ

 アプリのソースコードは、3段階のステップでご紹介する。開発チームでは、それらの段階を、フェーズ1、フェーズ2、そしてフェーズ2.5と名付けた。

 フェーズ1は、3つの車両データを扱うアプリで、まだ機能的にも限られたものとなっている。それを機能的に拡張し、1つのアプリとしての機能を一通り実現したのがフェーズ2だ。その後、SDLのAndroid用SDKのバージョンアップにともなって、APIが大幅に変更されたことに対応したのがフェーズ2.5となっている。つまり、フェーズ1→フェーズ2は、アプリとしての機能向上のためのステップアップ、フェーズ2→フェーズ2.5は、SDL側のAPI変更に対応するためのものと位置づけられている。

 以下、この3つのフェーズに沿って、それぞれのアプリが実現した機能とその実装方法について、できるだけ詳しく見ていこう。なお、各フェーズのアプリのソースコードは、GitHubに公開されている。それぞれ以下のリンクを参照していただきたい。

■フェーズ1(https://github.com/KDDI-tech/SdlSamplePh1
■フェーズ2(https://github.com/KDDI-tech/SdlSamplePh2
■フェーズ2.5(https://github.com/KDDI-tech/SdlSamplePh2_5

フェーズ1:車両データを読み取る基礎アプリ

 この「フェーズ1」アプリは、SDLのAndroid用SDK(sdl_android)のバージョン4.6.3に対応している。アプリのビルド条件などについて、詳しくは、上記GitHubのSdlSamplePh1にあるREADME.mdを参照していただきたい(図2)。

図2:GitHubに公開されているフェーズ1のアプリの全ソースコード

 このアプリは、SDL車載機を通して、大きく3種類の車両情報を取得し、それぞれ状況に応じて車載機画面へメッセージを表示する。また、同じメッセージを、音声によってユーザー(運転者)に通知する機能を実現している。それらの3種類の車両情報とは、燃料残量、タイヤ空気圧、ヘッドライトの点灯状態と車の周囲の環境光の明暗との組み合わせの3つだ。

 車載機経由で取得したい車両データの種類によらず、目的のデータの変化を通知によって知るためには、あらかじめ取得したいデータの種類を車載機に登録しておく必要がある。そにより、登録済の車両データが変化すると、以下に述べるように、onOnVehicleData()メソッドが呼ばれるようになるので、その中で変化したデータを識別して、対応する処理を実行する。

 なお、このプログラムの動作確認には、クラウド上の車載機エミュレータ、Manticoreを利用している。従って、車両データの変化は実際の車のものではなく、Manticoreのコンソールを使って擬似的に設定し、変化させている。

■燃料残量

 燃料残量は、満タンに対するパーセンテージで、車載機から読み取ることができる。このプログラムでは、残量を10%刻みで表示し、同時に音声でも読み上げる。また残量が30%以下になると「そろそろガソリンスタンドを探しましょう」というメッセージを表示するように設定している(図3)。

図3:車の燃料残量を10%刻みで車載機画面に表示し、30%以下になると給油するよう注意をうながす

 燃料残量の変化に対応するソースコードの主要部分だけをかいつまんで見ておこう。まず、車両データが変化すると、onOnVehicleData()メソッドが呼ばれる。

@Override
public void onOnVehicleData(OnVehicleData notification) {
....
    if (usableVehicleData.get(VD_FUEL_LEVEL) && notification.getFuelLevel() != null) {
        _changeDisplayByFuelLevel(notification.getFuelLevel());
    }
....
}

 このメソッドの中で、受け取ったデータに燃料残量が含まれている場合には、_changeDisplayByFuelLevel()メソッドを呼び出している。そのメソッドは、以下のように定義している。

private void _changeDisplayByFuelLevel(Double fuelLevel) {
    int fuel = fuelLevel.intValue();
    // 50%から10%刻みで通知を行う
    if(fuel <= 50 && fuel > 0){
        if (fuel % 10 == 0) {
            String str = (fuel <= 30) ? getResources().getString(R.string.fuel_under30) : "";
            _showTextField(getResources().getString(R.string.fuel_notif1) + fuel + getResources().getString(R.string.fuel_notif2), str, null, null);
            _showImage(ICON_FUEL);
        }
    }
}

 このメソッドでは、燃料残量の数字の変化に対して、10刻みで残量を表示するようにし、さらに30以下の場合にだけ、「ガソリンスタンド...」のメッセージを表示している。実際の表示は_showTextField()メソッドを呼び出して実行しているが、そのメソッドの中には、表示した文字列を読み上げる機能も実装されている。

■タイヤの空気圧

 タイヤの空気圧は、圧力が標準よりも低いということが検出できる。また、空気圧以外に、何らかの異常が発生した場合にも、異常であることを通知してくれる(図4)。タイヤは、一般的な乗用車では前後/左右の4本だが、SDLでは、それに加えて、後輪の内側の左右2本を加えた6本をサポートしている。これは、後輪がダブルタイヤになっている運搬用の車両を意識してのものだろう。

図4:全部で6輪の空気圧の低下と、異常状態を検出し、車載機画面、および音声で通知する

 タイヤの空気圧の変化に対する応答処理の主要部分のソースコードを確認しておこう。基本的には燃料残量の場合と同様だ。まず、車両データの変化に対して呼び出されるonOnVehicleData()メソッドでは、データに空気圧情報が含まれていれば、_changeDisplayByTirePressure()メソッドを呼び出すようにしている。

@Override
public void onOnVehicleData(OnVehicleData notification) {
....
    if (usableVehicleData.get(VD_TIRE_PRESSURE) && notification.getTirePressure() != null) {
        _changeDisplayByTirePressure(notification.getTirePressure());
    }
....
}

 そして、その_changeDisplayByTirePressure()メソッドでは、とりあえず6輪すべてのタイヤの状態を調べている。ここでは、そのうち左右の前輪、2本の状態を調べるコードを見ておこう。

ComponentVolumeStatus frontLeft = tire.getLeftFront().getStatus();
ComponentVolumeStatus frontRight = tire.getRightFront().getStatus();

 タイヤの空気圧の確認によって低圧や異常が検出された場合には、それに対応するメッセージを表示し、音声でも読み上げることになる。その部分のコードは割愛する。

■ライト点灯状態

 ライトの点灯状態は、ロービームとハイビームのどちらがオンになっているかを独立して検出できる。また、車載機からは車両の周辺の照度状態も取得できるので、夜なのにライトが付いていないという危険な状態はもちろん、昼なのにライトが付けっぱなしになっているという状態も確認できる(図5)。

図5:車の周囲が暗くなってもライトが付いていない、または明るいのにライトが付いてるという状態を検出して車載機画面、および音声で通知する

 ライト点灯状態の変化に対する応答処理についても、ソースコードの主要な部分を確認しておこう。基本的にはこれまでの燃料残量や空気圧の場合と同様の処理となる。車両データの変化に対して呼び出されるonOnVehicleData()メソッドでは、データにヘッドライトの点灯状態の変化が含まれていれば、_changeDisplayByHeadLampStatus()メソッドを呼び出す。

@Override
public void onOnVehicleData(OnVehicleData notification) {
....
    if (usableVehicleData.get(VD_HEAD_LAMP_STATUS) && notification.getHeadLampStatus() != null) {
        _changeDisplayByHeadLampStatus(notification.getHeadLampStatus());
    }
....
}

 このonOnVehicleData()メソッドから呼ばれる_changeDisplayByHeadLampStatus()メソッドでは、まず、ヘッドライトの点灯状態のデータに含まれている車の周囲の照度(AmbientLightStatus)を調べ、それと実際の点灯状態との組み合わせによって、昼夜に応じて的確なメッセージを表示するようにしている。

private void _changeDisplayByHeadLampStatus(HeadLampStatus lampStatus) {
    AmbientLightStatus lightStatus = lampStatus.getAmbientLightStatus();
    if (_checkAmbientStatusIsNight(lightStatus)) {
        if (! _checkAnyHeadLightIsOn(lampStatus)){
            _showHeadLightTurnOnMsg();
        }
    } else if (lightStatus.equals(AmbientLightStatus.DAY)) {
        if(_checkAnyHeadLightIsOn(lampStatus)) {
            _showHeadLightTurnOffMsg();
        }
    }
}

 なお、このフェーズ1で作成したアプリの動作を記録したデモムービーが、上記GitHubのDemoMovieフォルダにあるSDL_Demo_Phase1.mp4として公開されている。このアプリの機能や操作の流れについては、そちらを参照していただきたい。

フェーズ2:機能アップ

 「フェーズ2」アプリも、開発および動作環境はフェーズ1のアプリとまったく同様となっている。つまり、sdl_androidのバージョン4.6.3に対応したものだ。

 このフェーズ2のアプリのもっとも大きな機能強化は、フェーズ1のアプリの機能に加えて、RSSによる通知機能を備えたことにある。RSS情報を読み込むニュースサイトなどのURLは、事前にスマホ側で登録しておく。あとは、走行中に車が停止したタイミングで、RSSから読み取ったニュースのヘッドラインなどを車載機のディスプレイに表示し、同時に音声で読み上げる。

 それに加えて、従来の車両データの通知機能のオプションを設定するユーザーインターフェースも追加している。さらに、搭乗した車の車両データの概要を、SDLとして接続して機能を開始する直前にスマホ画面に表示する機能も加えた。以下、フェーズ2で追加した機能の概要とともに、それを実現するためにプロジェクトに追加したソースコードについても簡単に解説しよう。

■車両データ一覧表示

 フェーズ2では、ユーザーがSDL車載機を搭載した車に乗り込んで車載機と接続した際に、そこから得られるIDを調べ、以前にも接続したことがある車載機と判断された場合には、アプリ内に蓄積していた車両データを即座に表示する(図6)。

図6:アプリが既知の車載機を認識すると、保存してあった車載機、および車両本体の情報をスマホ画面に表示する

 一方、初めて接続する車載機の場合には、その旨を表示し、次回以降は情報が表示できるように、データをプリファレンスとして記憶する。

 上の図6の画面を表示するアクティビティは、SdlSamplePh2プロジェクトに含まれるJavaソースコード、InformationActivity.javaとして記述されている。この内容は、一般的なAndroidアプリの画面を構成するアクティビティと同様だ。

 そこに表示される情報を提供するモデルクラスとなるのが、Vehicle.javaだ。ただし、実際に車載機と通信して車両データを取得する部分は、フェーズ1のアプリと同様、SdlService.java内に記述されている。また、フェーズ2では、その中に、取得した車両データをSharedPreferencesに保存するコードも追加している。

■通知機能の設定画面

 このアプリは、フェーズ1で実現した各種の通知機能について、実際の通知機能を有効にするかどうか、また燃料残量の通知に関しては、何%になった時点で通知するのかという設定を、Androidアプリとしてのユーザーインターフェースによって可能にしている(図7)。

図7:走行中の車両データの通知機能の有効/無効、通知の条件を設定する画面

 この画面を表示するためのアクティビティは、SettingsActivity.javaとして実装されている。この内容も、一般的なAndroidの設定画面のアクティビティと同様のものなので、説明は省略する。

 なお、取得した車両情報をプリファレンスとして保存したり、そこから読み出したりするための、SharedPreferencesのマネージャクラスは、PrefManager.javaとして記述した。

■RSSのURL設定

 運転中に車が停止したことを検出して車載機画面に表示し、音声で読み上げるニュースは、そのソースのURLを、あらかじめスマホ側に登録しておく。そのために、URLの入力欄と、そこから取得したフィード情報を確認するための設定画面を用意した(図8)。

図8:RSSのURLを入力し、そこから取得したフィードを表示して確認する画面

 この画面のアクティビティは、RssActivity.javaとして実装した。ただし、実際に指定されたURLから非同期でフィード情報を取得する機能は、別途RssAsyncReader.javaとして記述している。これは、一般的なRSSリーダーアプリで使われている同様の機能と大差ないものとなっている。

 このアプリでは、運転中に車が停止したということを、車両のスピードがゼロで、かつ運転者がブレーキを踏んでいるという状態として認識している。そこで、このアプリが車載機上で動作中に、ManticoreのコンソールでDriver BreakingをYESに設定すると、設定したRSSのURLから取得したニュースの表示と読み上げを開始する(図9)。

図9:スピードがゼロで、ブレーキを踏んでいる状態を「停止した」と解釈し、安全とみなしてニュースの表示と読み上げを開始する

 この、スピードとブレーキ状態の検出は、フェーズ1のアプリでも見たように、SdlService.java内のonOnVehicleData()メソッドの中で、車載機からの通知の中にスピードとブレーキングに関する情報が含まれていれば、_checkVehicleDriveState()メソッドを呼び出して確認している。その部分のソースコードを抜き出して見ておこう。

@Override
public void onOnVehicleData(OnVehicleData notification) {
....
    if(notification.getSpeed() != null){
        latestSpeed = notification.getSpeed().intValue();
        if(latestSpeed == 0) {
            _checkVehicleDriveState();
        }
    }
    if(notification.getDriverBraking() != null){
        latestBreakState = notification.getDriverBraking();
        if(latestBreakState.equals(VehicleDataEventStatus.YES)) {
            _checkVehicleDriveState();
        }
    }
....
}

 そして、_checkVehicleDriveState()メソッドの中では、検出された「停止」状態が3秒以上続くようであれば、RSS情報の表示と読み上げを開始するようにしている。

private void _checkVehicleDriveState() {
    try {
        Thread.sleep(3 * 1000);
        if(latestSpeed == 0 && latestBreakState.equals(VehicleDataEventStatus.YES)) {
            String url = prefManager.getPrefByStr(R.id.rssText,"");
            if(!url.isEmpty()) {
                String lastMod = prefManager.read(RssActivity.RSS_LAST_MODIFIED_KEY, "");
                new RssAsyncReader(this).execute(url, lastMod);
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

 なお、このフェーズ2で作成したアプリのデモムービーも、上記GitHubのDemoMovieフォルダ内に、SDL_Demo_Phase2_1.mp4、およびSDL_Demo_Phase2_2.mp4として公開されている。このアプリの実際の動作の様子については、そちらを参照していただきたい。

フェーズ2.5:SDKバージョン4.7.1対応

 すでに述べたように、フェーズ2.5は、アプリの機能の増強を狙ったものではなく、SDLのAndroid用SDKのバージョンアップにともなうAPI変更に対応したものだ。具体的には、フェーズ1とフェーズ2がsdl_androidの4.6.3に対応したものだったのに対し、フェーズ2.5は、同4.7.1に対応している。

 ただし、SDLのSDKには、ドキュメントの記述と実際のAPIの動作が異なることがあって、フェーズ2の機能を4.7.1対応に完全に移行しきれていない部分もある。また4.6.3から4.7.1へのギャップは、マイナー番号の変更の割にはかなり大きく、アプリ側のアーキテクチャの変更を強いる部分もある。その際に、新しいSDK上で動くはずのものが、期待通りに動作しない場合もあるため、代替手段で実現したものもある。

 というわけで、フェーズ2.5のアプリは、過渡期のものと考えられるが、4.7.1以前からAndroidのSDL対応アプリを開発していた人にとっては、移行のための参考になる部分も少なくないと考えられるため、あえてソースコードごと公開してもらうことにした。また、現状のフェーズ2.5のコードで、期待したように動作していない部分、そのためにやや無理のあるコードが混入している部分は、今後SDKのバージョンが進むにつれて、比較的簡単に解消できるようになるだろう。

 具体的なソースコードについては、上記GitHubに公開したものをご覧いただくとして、ここではフェーズ2.5の開発を進める上で発見した課題、それに対する可能な範囲での解決策を、メモとして以下にまとめることとした。

■SdlProxyALMが非推奨となった

 4.6.3では、アプリが車載機と各種情報をやり取りするためのクラスSdlServiceは、IProxyListenerALMインターフェイスを実装するのが標準的な使い方だった。しかし、4.7.1以降では、SdlProxyALMクラスが非推奨となり、IProxyListenerALMも使えないことになった。そのため、SdlServiceクラスの先頭部分は、以下のように変更になる。

[4.6.3]
public class SdlService extends Service implements IProxyListenerALM {
[4.7.1]
public class SdlService extends Service {

 これにともない、以前のSdlServiceに必然的に実装されていた各種リスナーのメソッドは不要となった。

■4.7系での新たなリスナーの書き方

 上記のように、SdlService内のIProxyListenerALMインターフェイスに準拠したリスナーがなくなったので、車載機との通信に必要なリスナーは、SdlManagerListenerの中に含めることになる。

[4.7.1]
SdlManagerListener listener = new SdlManagerListener() {
    @Override
    public void onStart() {
        // HMI Status Listener
        sdlManager.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, new OnRPCNotificationListener() {
            // 従来(4.6.3):onOnHMIStatusに該当する処理をここで行う
        });
        // Menu Selected Listener
        sdlManager.addOnRPCNotificationListener(FunctionID.ON_COMMAND, new OnRPCNotificationListener() {
            // 従来(4.6.3):onOnCommandに該当する処理をここで行う
        });
    ....
}

■Locksreenは独自に実装する必要がなくなる

 車載機と接続してのアプリ動作中にスマホ画面に表示するロックスクリーンは、4.6.3ではLockScreenActivity.javaによって、LockScreenActivityクラスを実装することで表示していた。しかし、4.7.1以降では、特にカスタマイズされたロックスクリーンを表示するのではない限り、アプリ側でLockScreenActivityクラスを実装する必要はないとされている。つまり、SdlManagerを実装し、かつマニフェストでSDLLockScreenActivityを定義してあれば、それだけでデフォルトのロックスクリーンが表示されることになっている。しかし、実際に4.7.1では、このような条件でもロックスクリーンが表示されない。

 そこで現状では、HMILevel(車載機の稼働状態)が、HMI_FULL(フル)となった際に、いわば手動でLockScreenActivityに切り替えてロックスクリーンを表示するようにしている。そして、HMILevelがHMI_FULL以外になれば、ロックスクリーンを、やはり手動で解除している。

■画面への表示リクエストにトランザクションを導入

 車載機画面にテキストなどを表示する場合、4.6.3では表示したいテキスト情報を含むShowクラスのインスタンスをプロキシのsendRPC()メソッドを使ってアプリから車載機に送信するだけでよかった。それが4.7.1では、SDLManagerのScreenManagerを使い、コンプリーションリスナーを用意してトランザクションによって送信し、結果を確認するという手順が必要となった。

[4.6.3]
Show show = new Show();
show.setMainField1("text1");
show.setMainField2("text2");
proxy.sendRPC(show);
[4.7.1]
sdlManager.getScreenManager().beginTransaction();
sdlManager.getScreenManager().setTextField1("text1");
sdlManager.getScreenManager().setTextField2("text2");
sdlManager.getScreenManager().commit(new CompletionListener() {
    @Override
    public void onComplete(boolean success) {
        Log.i(TAG, "ScreenManager update complete: " + success);
    }
});

■画像の表示方法の変更

 車載機画面に画像を表示する場合、4.6.3ではあらかじめ画像をアップロード(アプリから車載機に送信)しておく必要があった。その際に、車載機側での画像識別に必要な名前を付けておき、あとで、その名前を指定して実際の表示を指示するという手順が必要だった。4.7.1では、まず画像からSdlArtworkクラスのインスタンスを作成し、そのアートワークを、SDLManagerのScreenManagerを使って車載機側に設定するというシンプルな手続きで表示できるようになった。

[4.6.3]
image = new Image();
image.setValue('アッフプロードした画像の文字列.png');
image.setImageType(ImageType.DYNAMIC);
show.setGraphic(image);
[4.7.1]
SDLArtwork artwork = new SdlArtwork('画像名.png',FileType.GRAPHIC_PNG, R.drawable.tire, true);
sdlManager.getScreenManager().setPrimaryGraphic(artwork);

 本記事およびソースコードが、読者の方々のオリジナルSDLアプリ開発に役立つことを願っています。



 SDLに関してより詳しい情報、SDLコンソーシアム公式の技術資料は、以下をご覧下さい。

技術資料:https://www.smartdevicelink.com/docs/

開発キット等:https://www.smartdevicelink.com/resources/

【コンテストの開催概要】

■名称:クルマとスマホがつながる SDLアプリコンテスト
■主催:SDLアプリコンテスト実行委員会
   (事務局:角川アスキー総合研究所)
■協力:SDLコンソーシアム日本分科会
■募集内容:SDLを利用したAndroid、またはiOSアプリ
■応募期間:2018年10月3日(水)〜2019年1月31日(木)
■応募方法:コンテスト専用サイトにて( http://sdl-contest.com/

※応募には、下記を提出してください。
 (1)応募作品の動きがわかる動画および画像
 (2)作品内容の説明資料(PDF)

■受賞発表:
・事務局による事前審査の結果発表は2019年3月上旬を予定しています。
・最終審査会を兼ねた受賞発表・表彰式は2019年2月末に予定しています。
・事前審査通過者は、最終審査会でプレゼンテーションをしていただきます。
■賞・賞金等:グランプリ 賞金50万円+副賞
       特別賞   賞金10万円×5本

SDLアプリコンテストのグランプリ副賞はヤマハの電動スクーターE-Vino

(提供:SDLコンソーシアム 日本分科会)

カテゴリートップへ

この特集の記事

注目ニュース

ASCII倶楽部

最新記事

プレミアムPC試用レポート

ピックアップ

ASCII.jp RSS2.0 配信中

ASCII.jpメール デジタルMac/iPodマガジン