ELM327でシフトインジケータアプリを作成・実装編①からのつづきです。

システム開発部のTです。
今回は車ネタってことで、ELM327を使ってAndroid版シフトインジケーターアプリを作ってみたので、そのことについてお話したいと思います。

さて、前回の実装編からの続きになります。
今回はシフトインジケーターメイン画面の実装です。
ポイントはコマンドを送信、受信、送信・・・とシーケンシャルに実行していくところ・・・。
では、つづきを書いていきましょう。

メイン画面(メーター画面)

本機能のメイン画面になります。
機能を箇条書きしてみますね。

  • 初期処理
    最初にタイヤの外径値、各変速ギアごとのギア比などを計算しておきます。
  • ELM327接続処理
    ELM327接続画面で取得済みのMACアドレスから、ELM327のBluetoothデバイスを取得します。
  • ELM327コマンド送信処理
    車速、回転数を取得するためのコマンド送信処理です。
    ポイントはコマンド送信後、そのコマンド結果を受けてから、次のコマンド送信をする必要があるということ。以降で詳しく解説したいと思います。
  • ELM327コマンド受信処理
    車速、回転数の各コマンドからの応答を処理します。
    ここで、車速、回転数を取得し画面に表示。
    さらに、走行中のギア比を計算し、画面に表示。

とりあえず、こんなところで実装の解説をしたいと思います。

初期処理

ここでやっていくのは、タイヤの外径値、各変速ギアの値の取得になります。
ここで、復習の意味をこめて「接続確認編」のときにも示した方程式を見ていきましょう。

上記の計算を逆算することで、ギアレシオ値に紐づくギアポジションを取得するのが目的になりますが、ここで以下の値についてはELM327では取得できません。

  • タイヤ外径
    これ以降で計算することで取得するが、タイヤ幅、扁平率、リム径についてはあらかじめアプリ側に教えておく必要がある。
  • ギアレシオ
    これも同じように、各変速ギアのギア比をアプリ側に設定する必要あり。
  • ファイナル
    上記同様。

そのため、先にもお伝えしたとおり、上記については設定画面などを用意し、アプリ側に設定させる必要がございます。本件では、設定画面は省略し、各値固定値で説明していきます。

タイヤの外径値の取得

本件では4輪ともに同一のタイヤを履いていることが前提となります。
(※競技車両に有りがちな前輪、後輪が別々のタイヤサイズは想定していません)

まず、実際のタイヤサイズを見てみましょう。
大体、以下の表示で示されているかと思います。

195/45R16

タイヤサイズの説明については、以下のサイトに掲載されているので、詳細は省きます。自分の車に装着されているタイヤサイズについては、装着しているタイヤに記載されています。
https://tire.bridgestone.co.jp/about/knowledge/size/index.html

上記のサイズをコードで示すと、以下のようになります。

// 本件では固定値だが、設定画面等を用意して柔軟に対応できるようにしておくこと
val tireSize = 195 // タイヤ幅
val flatSize = 45  // 扁平率
val inchSize = 16  // リム径(インチ)

上記からタイヤサイズを取得する計算式は以下となります。
外径 =((タイヤ幅 × 扁平率 × 2)+(リム径 × 25.4))×円周率

これをコードに起こすと、以下になります。

tireOutSize = ((tireSize * (flatSize / 100.0f) * 2) + (inchSize * 25.4f)) * Math.PI.toFloat()

これでタイヤの外径値が取得できました。

ギア比の取得

走行中のギアポジションを紐づけるため、変速ごとのギア比を求めます。
以下はスイフトスポーツの例になりますが、メーカーサイトの主要諸元に掲載されています。

上記を見てもらうとわかりますが、1速~6速のギア比がありますね。
今回は6速マニュアルミッションで実装したいと思うので、6MTの数値を参照ください。
上記の内容をコードで表すと、以下のようになります。

val gear1 = 3.615f // 1速ギア
val gear2 = 2.047f // 2速ギア
val gear3 = 1.518f // 3速ギア
val gear4 = 1.156f // 4速ギア
val gear5 = 0.918f // 5速ギア
val gear6 = 0.794f // 6速ギア
val gearF = 3.944f // ファイナルギア

上記を以下のようにテーブル化しておきます。
以降の処理で走行中のギアポジションを求めるときに楽になるので・・・。
以下では、ギア比とファイナル、それに1000000を掛けた値を設定しています。ギアポジションを紐づける際、以下の計算値で紐づけを行うことになります。

val gearRatioList = arrayListOf(
            Pair(1, gear1 * gearF * 1000000),
            Pair(2, gear2 * gearF * 1000000),
            Pair(3, gear3 * gearF * 1000000),
            Pair(4, gear4 * gearF * 1000000),
            Pair(5, gear5 * gearF * 1000000),
            Pair(6, gear6 * gearF * 1000000)
            )

以上でギアポジションに必要なデータを取得できました。
続けて、ELM327との接続処理になります。

ELM327接続処理

とはいえ、ここの接続処理自体は実装編①のときと変更ございません。
ただ、実装編①のときにすでに取得しているELM327デバイスのMACアドレスを利用することで、直接ELM327に接続していく実装になります。

処理としては、以下のような感じです。

fun connectDevice() {
    val device = 
        BluetoothAdapter
           .getDefaultAdapter()
           .getRemoteDevice("ELM327のMACアドレス")
    val rxBluetooth = RxBluetooth(this)
    rxBluetooth
        .observeConnectDevice(device, UUID.fromString(SPP_UUID))
        .retry(3)
        .map { BluetoothConnection(it) }
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({
            // BluetoothConnectionのInstanceはプロパティに保持
            this.bluetoothConnection = it
            // コマンド受信処理にInstanceを渡す
            this.receiveResponse(this.bluetoothConnection)
            // コマンド送信処理にInstanceを渡す
            this.sendCommand(this.bluetoothConnection)
        }, {
            Log.e("connectDevice", it)
        })
}

BluetoothAdapterを利用することで、MACアドレスからBluetoothDeviceを取得できます。
後は、実装編①と同様にRxBluetoothobserveConnectDeviceでELM327に接続します。接続できたら、そのままオペレーターのmapBluetoothConnectionを取得し、subscribeconnectionを流してしまいましょう。
取得したconnectionは、終了処理でも利用するので、プロパティに保持しておきます。
また、コマンド送受信時も利用するので、それぞれの処理のメソッドにも渡しています。

ELM327コマンド送信処理

ELM327へのコマンド送信処理です。
コマンド送信については、BluetoothConnectionsendメソッドを利用することで、送信可能です。これ自体の実装はシンプルです。

注意が必要なのは、ELM327に対して送信できるコマンド数は1コマンドのみとなります。例えば、車速コマンドを送信し、車速コマンドのレスポンスが返却されるまでは、続けて回転数コマンドを送信することはできません。次のコマンドを送信できるタイミングは、前のコマンドのレスポンスがかえってきた後となります。

そういう理由もあり、送信処理については、以下の実装となっております。

private val commandList = arrayListOf("010C\r", "010D\r")
private var commandNo = 0

private fun sendCommand(connection: BluetoothConnection) {
    // 回転数の取得コマンドを送信
    commandNo = 0
    connection.send(commandList[commandNo].toByteArray())
    // 以降は、コマンド結果受信の都度、次のコマンドを送信するようにする
    // 回転数を受信後、車速を取得するコマンドを送信
    meterLiveData.observe(fragment, Observer {
        commandNo++
        commandNo %= commandList.size
        connection.send(commandList[commandNo].toByteArray())
    })
}

見ていただけると分かる通り、最初に送信するコマンド以降は、LiveDataからのコマンド受信通知が来るたびに次のコマンドを送信する内容となっております。このように処理を構成することで、多重にコマンド送信をしないようにしているわけです。

以上が送信処理の実装になります。

コマンドを連続で送信することができないのが辛いところであります。
本件では、車速と回転数のみなので特に問題はなかったのですが、参照したいデータがもっと多い場合は、それだけ複数のコマンドを送信しなければならず、全てのコマンドの送信結果を取得し画面に表示するとなると、処理をきちんと考えていかないといけないかな?と思いました。

内容が長くなりますので、今回はコマンドの送信処理までとします。次こそ実装編、完結です。どうぞお待ちください。

次回
「ELM327でシフトインジケータアプリを作成・実装編③」へ続く

前回
「ELM327でシフトインジケータアプリを作成・実装編①」はこちら



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる