先日車が壊れたのを機に車について調べていたら OBD (On-board diagnostics) という診断機能を知りました。大抵の車には OBD のコネクタが搭載されていて、これに ELM327というマイコンを使った製品をつなぐと Bluetooth のシリアル通信などで簡単に情報をやりとりできるそうです。おもしろそうなので試してみました。
重要このポストと同じ方法で車の診断ログをリセットしたりすることもできます。診断ログが正しくない状態になると、修理の適切なタイミングなどがわからなくなるはずです。コマンドを車に送る前によく考えた上で自己責任で試してください。
ELM327 のドングルを手に入れる
これらの製品は Amazon等で廉価に入手できるようです。全製品で固定っぽい Bluetooth の接続パスワードが書かれていたので念のため黒塗りにしました。
車のOBDポートを探す
運転席のどこかにある場合が多いようです。私の車はここにありました。
ここに購入した上記のドングル型デバイスを接続します。
通信方法を確認
送信
AT コマンドというものを送るとマイコンを操作することができるようです。今回は以下の2つのコマンドを使いました。
AT Z
: 状態をリセットするAT SP 0
: OBDの通信プロトコルを自動で検出
通信プロトコルは様々なものがあるようですが、私の車で試した限りでは AT SP 0
の自動検出でうまく通信できました。
AT コマンドの他に OBD コマンドというものがあり、これで様々な OBD の情報を取得できました。Wikipedia にコマンドのリストがあったため、これを参照しました。
OBD コマンドは Mode
と PID
から構成されています。現在のデータを読み取る 01
というモードを今回は使いました。取得できるデータごとに PID
が振られており、例えば PID
が 0C
のエンジン回転数の情報を読み取る OBD コマンドは 010C
になります。
受信
データは、41 0C AA BB ...
のようにスペース区切りの16進数の文字列で帰ってきます。1つめが Mode
の値に40を足したもの、2つめが PID
で、それ以降が実際のデータになります。例えばエンジン回転数の場合は値が4倍されているなど、それぞれ決まりがあるようです。
Node.js でシリアル通信をする
serialportというライブラリが著名そうだったので利用しました。
Node.js のランタイムでシリアルポートをオープンする TypeScript のコードはこんな感じ。ELM327 のデバイスに Bluetooth で接続した後にコードを実行すると、SerialPort
のインスタンスが得られます。
importSerialPortfrom'serialport'constDEVICE_PATH='/dev/tty.OBDII-SPPslave'exportconstconnect=()=>newPromise<SerialPort>((resolve,reject)=>{constport=newSerialPort(DEVICE_PATH,{baudRate:9600,autoOpen:false})console.log(`[SP] opening on ${DEVICE_PATH}..`)port.open((error)=>{if(error){console.log('[SP] port opening error.')reject(error)}else{console.log('[SP] port opened.')resolve(port)}})})
ポートに書き込むには write
メソッドを使います。コマンドのデリミタはキャリッジリターンなので注意してください。
// `AT Z` の AT コマンドを送出port.write('AT Z\r')
データはイベントリスナーを使って取得できます。
文字列を処理して、目的の値を抽出します。
port.on('data',(data:string)=>{const[mode,pid,...values]=data.split('')letmodeNumtry{modeNum=parseInt(mode,16)-40}catch{return}if(modeNum===1){consthexValue=parseInt(values.filter(value=>value!=='').join(''),16)if(pid==='0C'){console.log('EngineRPM: '+hexValue/4)}}})
WebSocket でデータをブラウザに送信できるようにする
上記でデータが取得できる準備が整ったので、あとは WebSocket を使ってブラウザにデータを送ってみます。wsというライブラリを使います。
WebSocket サーバーを立ち上げるコードはこんな感じ。
importWebSocketfrom'ws'exportconstcreateSocket=()=>newPromise<WebSocket>((resolve,reject)=>{console.log('[WS] connecting...')constwss=newWebSocket.Server({port:8081})wss.on('connection',(ws)=>{console.log('[WS] connected.')resolve(ws)})})
WebSocket
のインスタンスを使って、シリアル通信で得られたデータを送ります。上記のシリアル通信のイベントリスナーに socket.send(data)
を差し込めば OK です。
クライアント側は、こんな感じ。
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><style>html,body{margin:0;padding:0;width:100%;height:100%;}#display{position:fixed;width:500px;height:100px;top:10px;left:10px;font-size:2em;}</style></head><body><textareaid="display"></textarea><script>constws=newWebSocket('ws://localhost:8081')constdisplay=document.getElementById('display')letcurrentData={}ws.onmessage=(event)=>{currentData={...currentData,...JSON.parse(event.data)}constnextValue="エンジン回転数: "+currentData.engineRPM+'rpm\n'display.value=nextValue}</script></body></html>
動作させる
以上で全ての材料が揃ったので、これを組み込んでいきます。今回は Mac 上で以下の全てのプロセスを立ち上げて試してみました。
- 車とシリアル通信をする WebSocket サーバー
- クライアントを表示する ウェブサーバー
また、エンジン回転数以外にも現在の運転速度と冷却水温度も取得してみました。
動画を撮りながら走ってみると、いい感じにデータが取れているようです✌️
上記のコードは以下のリポジトリにあります。
https://github.com/kamataryo/parse-obd-serial