- オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018
- TL;DR
- ODB2/ODBⅡとは
- ODB2 ≠ CAN
- システム構成
- システム説明
- 取得したデータ分析
- まとめ
- ソースコード
実体はこちらです
オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018
オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018
さて、このAdvent Calendarの趣旨に合っているのか?と思っている皆さん。「タイトル」的にはアウトですが、「テーマ」的にはセーフという曲折を含めつつ、書いていきます:)
Advent Calendar の趣旨は?
このアドカレのタイトルは「オフィスや自宅を快適にするIoT」ですが、テーマは「続々と登場するIoTガジェットやサービス。果たして実際に生活は豊かになるのか?皆さんが試してみた、生活や身の回りを改善するIoTプログラミングを、教えてください。」 とあったので、「自分の好きな車 = 身の回り」ってことにしました(すいません🙇♂️)
TL;DR
ODB2 + Raspi で車の走行性能を数値化できる(ようになるための準備ができる) 車種にもよりますが、自分の車では「エンジン回転数」「スピード」を取得してグラフ化しました。下のグラフは回転数のグラフです。
ODB2/ODBⅡとは
OBD = On-Board Diagnostics です。ざっくり言えば、元は「自動車の自己診断システム」で、最近では「車両情報」の意味も含まれていると思います。車速、回転数、水温、湯音、CANデータなどなどです。
歴史
1991年、US カリフォルニア州にて、州内で販売される新車に搭載が義務付けられた。 1996年、US 全土でも新車に搭載が義務付け。 2001年、EU でも排出ガス規制の一環で新車に搭載義務付け。 2006年、日本でも新車に搭載義務付け。
2008年時点で、アメリカで販売される全ての自動車にCANを信号のプロトコルとして埋め込む事が義務づけられている
本来の目的のデータ以外にも、車速、エンジン回転数などが取得できる。 規定されているデータの種類(PID)は100種類 + 各自動車メーカ独自拡張。なので、一概にすべての車に適用はできないかもしれません。
ODB2 ≠ CAN
CAN (Controller Area Network) = プロトコル OBD2 = CAN 上でやり取りされるデータ
CANの一例
プロトコル | 備考 |
---|---|
SAE J1850 PWM | フォードが使用 |
SAE J1850 VPW | ドイツの自動車メーカーが使用 |
ISO 9141-2 | クライスラーや、ヨーロッパ、アジアの車で使われる |
ISO 14230 KWP2000 | Keyword Protocol 2000 |
ISO 15765 CAN | ボッシュによって開発された。他のOBDプロトコルと違って、変種が自動車業界の外でも使用されている。 |
システム構成
今回OBD2からデータ取得するために、以下のようなシステム構成になりました。
物 | スペック等 | 備考 |
---|---|---|
検証車 | TOYOTA カローラフィールダー エアロツアラー | |
OBD2コネクタ | ELM327 Bluetooth対応版 | iPhone未対応 |
RaspberryPi3 Model B+ | Python3, obd2ライブラリ使用 | |
macbook pro |
OBD2コネクタ
結構いろんな種類あるんですが自分はこれを使ってます。はずれが多いらしいんですが、これは問題なく使えてるので大丈夫かと
超小型モデル OBDII 診断 ELM327 Bluetooth ブルートゥース スキャンツール テスター OBD2 新品価格 |
システム説明
主にBluetoothを制御するstart.sh
と、OBD2からデータを取得しExcelに落とし込むlogging.py
の2つのファイルで構成されています。
実行するときにはstart.sh
をsudo
で実行することで、Bluetooth設定が終わったら自動的にlogging.py
が起動します。
start.sh
sudo hciconfig hci0 up sudo rmmod rfcomm sudo modprobe rfcomm sudo rfcomm bind 0 AA:BB:CC:11:22:33 ls /dev | grep rfcomm sudo rfcomm listen 0 1 & sudo python logging.py
この各行の意味を追っていきたいと思います。
hciconfig
hciconfigとはBluetooth(BT)をコマンドラインユーティリティから使うためのコマンドです。Bluezデーモンを使うのでインストールされてない場合は以下の通りインストールします
# bluezを動かすために必要なライブラリ群 $ sudo apt-get install -y libglib2.0-dev libdbus-1-dev libudev-dev libical-dev bluetooth bluez-utils blueman # bluez本体 $ sudo mkdir car_tmp && cd car_tmp $ wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.45.tar.xz $ xz -dv bluez-5.45.tar.xz && tar -xf bluez-5.45.tar $ cd bluez-5.45/ && ./configure --enable-experimental $ make $ sudo make install
無事インストールできれば、BTが認識できているか確認してみます。以下のようにBT機器のMACアドレスが表示されてればOKです。
pi@raspberrypi:~ $ sudo hcitool lescan LE Scan ... 74:DE:1A:E6:4E:4F (unknown) 34:36:3B:C7:FB:E9 (unknown) 34:36:3B:C7:FB:E9 (unknown) 74:DE:1A:E6:4E:4F (unknown) pi@raspberrypi:~ $ pi@raspberrypi:~ $ sudo hcitool scan Scanning ... AA:BB:CC:DD:EE:FF xxxxxxxxxxxxxxxxxxx pi@raspberrypi:~ $
確認ができたらhcitool -a
でBTモジュールを確認します
hcitool -a hci0: Type: USB BD Address: 00:1B:DC:XX:XX:XX ACL MTU: 310:10 SCO MTU: 64:8 UP RUNNING PSCAN RX bytes:384 acl:2323 sco:0 events:255 errors:0 TX bytes:512 acl:4949 sco:0 commands:512 errors:0 Features: 0xff 0xff 0x8f 0xfe 0x9b 0xff 0x59 0x83 Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 Link policy: RSWITCH HOLD SNIFF PARK Link mode: SLAVE ACCEPT Name: 'BlueZ at localhost.localdomain-0' Class: 0x120104 Service Classes: Networking, Object Transfer Device Class: Computer, Desktop workstation HCI Ver: (0x4) HCI Rev: 0x12e7 LMP Ver: (0x4) LMP Subver: 0x12e7 Manufacturer: Cambridge Silicon Radio (10)
RaspiにあるBTモジュールが認識されていれば、このような感じの情報が出力されます。このhci0
というのがデバイスごとに割り振られるデバイスIDです。このデバイスIDを使って通信するBTモジュールを指定します。なので、メモしておきましょう。
sudo hciconfig hci0 up
このコマンドでデバイスIDがhci0
のデバイスの電源をUPにします。このコマンドを実行してsudo hciconfig
を確認したときにUP RUNNING
という表記がされていればOKです。
$ sudo hciconfig hci0: Type: BR/EDR Bus: UART BD Address: B8:27:EB:2E:E0:10 ACL MTU: 1021:8 SCO MTU: 64:1 UP RUNNING RX bytes:717 acl:0 sco:0 events:42 errors:0 TX bytes:1532 acl:0 sco:0 commands:42 errors:0
rfcomm
rfcommはBluetoothの通信プロファイルに1つです。このプロファイルでは、通信上はBluetoothで接続されているデバイスと通信を行うのですが、見かけ上、シリアル通信としてプログラムできるという利点があります。
rmmod
Loadable Kernel Moduleのアンロードを行います。後で説明するmodprode
で読み込んだ情報を削除します。OBD2の接続するとき毎回行う必要はないかもしれないんですが、念のためやってます。rfcomm
自体は再起動すると設定が失われます。逆に言えば電源が入っていればずっと保持されるので、rmmod
しておいたほうが無難かと...。
modprobe
rmmod
の反対でLoadable Kernel Moduleのロードを行います。modprobe rfcomm
でrfcomm
プロトコルが利用可能になります。
rfcomm bind
rfcomm
セクションでも説明したようにrfcomm
プロトコルを利用すれば、シリアル通信としてプログラムできるようになります。しかし、そのままではシリアル通信として利用できないので、シリアルポートにrfcomm
をbind
する必要があります。バインドする先は/dev/rfcommX
です。X
は0~9などの任意の数値を指定できますが、今回は0
を指定しておきます。
sudo rfcomm bind 0 AA:BB:CC:11:22:33
ここのAA:BB:CC:11:22:33
とは接続先のBTのMACアドレス
です。sudo hcitool lescan
やsudo hcitool scan
で見つけたMACアドレスをメモしておきましょう。ただ、今回利用するELM327モジュール
は全てAA:BB:CC:11:22:33
に統一されているようでした。
ls /dev | grep rfcomm
無事にバインドが成功していれば、ls /dev | grep rfcomm
を実行すれば先ほど指定した番号のシリアルポートが確認できるかと思います。
rfcomm listen
先ほどバインドした番号のシリアルポートに来るデータ読む(listenする)コマンドを実行します。これを実行することで/dev/rfcomm0
に流れてくるデータを他のプログラムでも確認することができます。1
はチャンネルを表しています。
sudo rfcomm listen 0 1 &
&
とは、このrfcomm listen
というコマンドはプロセスとして動作します。このlisten
の動作自体はバックグラウンドで動作してもらいたいので&
を付けます。つまり、この&
を付けて実行したコマンドはバックグラウンドで動作するようになります。
logging.py
使用しているOBD2ライブラリの公式サイトはこちら
import obd from obd import OBDStatus import time, csv import os f = open("data.csv", "w") writer = csv.writer(f, lineterminator="\n") connection = obd.OBD() print (connection.status()) if connection.status() == OBDStatus.CAR_CONNECTED: writer.writerow(["rpm", "speed"]) while(True): try: rpm = connection.query(obd.commands.RPM) speed = connection.query(obd.commands.SPEED) writer.writerow([rpm.value.magnitude, speed.value.magnitude]) print (rpm.value.magnitude, speed.value.magnitude) # time.sleep(.2) except KeyboardInterrupt: pass else: connection.close()
obd.OBD()
で先ほどbindしたポート等と確認して自動的にOBD2スキャンツール(ELM327)にアクセスしてくれます。その接続先(データ取得先)をconnection
という変数で保持し、今後この変数に対して、値の要求などを行います。
接続に使用するポートを変更したいときには、以下のように使用するポートを指定します。
import obd # 直接シリアルポートを指定 connection = obd.OBD("/dev/ttyUSB0") # 利用できるシリアルポートから選択する ports = obd.scan_serial() # ['/dev/ttyUSB0', '/dev/ttyUSB1'] connection = obd.OBD(ports[0])
接続できているかは以下で確認できます。
connection.is_connected() #or from obd import OBDStatuus if connection.status() == OBDStatus.CAR_CONNECTED:
OBDStatusには以下が定義されています
# no connection is made OBDStatus.NOT_CONNECTED # "Not Connected" # successful communication with the ELM327 adapter OBDStatus.ELM_CONNECTED # "ELM Connected" # successful communication with the ELM327 and the vehicle OBDStatus.CAR_CONNECTED # "Car Connected"
Basic Usage
import obd connection = obd.OBD() # auto-connects to USB or RF port cmd = obd.commands.SPEED # select an OBD command (sensor) response = connection.query(cmd) # send the command, and parse the response print(response.value) # returns unit-bearing values thanks to Pint
基本的な使い方としては上記の通りで、connection
に対して値を要求するクエリを実行します。そのクエリの返り値が要求した結果となっています。別にcmd
を使わなくてもconnection.query(obd.commands.SPEED)
でも動作します。お好みで
Module Layout
このライブラリのレイアウトは以下の通りです。すべてを使いこなせる気はしませんが...。
import obd obd.OBD # main OBD connection class obd.Async # asynchronous OBD connection class obd.commands # command tables obd.Unit # unit tables (a Pint UnitRegistry) obd.OBDStatus # enum for connection status obd.scan_serial # util function for manually scanning for OBD adapters obd.OBDCommand # class for making your own OBD Commands obd.ECU # enum for marking which ECU a command should listen to obd.logger # the OBD module's root logger (for debug)
csv出力
f = open("data.csv", "w") writer = csv.writer(f, lineterminator="\n")
データ出力する先と改行を指定します。ファイルの出力先はlogging.py
と同じ位置になりますが、パス指定すれば任意の場所に出力することも可能です。データをファイルに書き込むときには以下のように記述します。
# カラム名の書き込み writer.writerow(["rpm", "speed"]) # 取得データの書き込み writer.writerow([rpm.value.magnitude, speed.value.magnitude])
取得したデータ分析
この取得したデータをOctave
を使って、解析的なことをしてみたいなと思います。今回利用するのはGNU Octave
です。
GNU Octave は、主に数値解析を目的とした高レベルプログラミング言語である。Octaveは線形ならびに非線形問題を数値的に解くためのコマンドライン·インタフェースを提供する。また、 MATLABとほぼ互換性のある、数値実験を行うためのプログラミング言語として使用することができる。 Octaveは、GNUプロジェクトの一つでGNU General Public Licenseの条件の下のフリーソフトウェアである。 GNU OctaveとScilabは、MATLABのオープンソース代替品の一つである。 ただし、Octaveは、ScilabよりもMATLABとの互換性維持に重点を置いている Wikipedia - GNU Octave
ダウンロード先はこちら
解析は普通のPCを使います。今回はWindowsを使いますがMacでも同じです。インストール等の開設は省略します。インストーラーで終わってしまうので...。Macはports
かbrew
でインストールになります。
今回は「エンジン回転数」のデータを使って、回転数のピーク部を検出して印付けしたいと思います。100%検出できるわけではないです。以下のような画像のグラフを作成します。
解析データ作成
生成したcsvのデータから、解析したデータのみを取り出します。今回はエンジン回転数のファイルを作成します。ファイルの中身は数値のみになります。
636.5 644.5 642 641.25 638.25 639 639 640.5 :
Octaveでファイル読み込み
Octaveでそれぞれのファイルを読み込みます。この読み込まれたデータはOctave上では行列として認識されます。上記2つのデータの場合「n行1列」のデータ配列となります。しかし、rpm[1]
speed[10]
のようにアクセスしたい場合は「転置」を行う必要があります。
% ファイルからデータ読み込み rpm = load('car_rpm.txt' , ' ') ; % 行列の転置 rpm = rpm.' ;
グラフ設定
そして、横軸(時間軸)を決めていきたいのですが、今回は時間を使用した解析は行わないので「1」から連番で振っていきます。
% % bからcまでのm個を用いた「m X 1」の行列を生成 % a = b : c ; % x = 1 : length(rpm) ;
これでグラフに表示する準備ができました。グラフを表示するには以下のように記述します。
% データをグラフにプロット plot(x, rpm) ; hold on ; % ラベルの表示設定 xlabel('TIME') ; ylabel('rpm') ;
% % 走行データ解析プログラム % コマンドウィンドウで pkg load signal 必須 % % 走行データの読み込み rpm = load('car_rpm.txt', ' ') ; % 走行データ配列の転置 rpm = rpm.' ; % 時間軸の生成 x = 1 : length(rpm) ; % グラフのプロット plot(x, rpm) ; hold on ; % ラベルの表示設定 xlabel('TIME') ; ylabel('RPM') ;
グラフ結果
エンジン回転数のピーク値を見つける
Octaveには様々な計算ライブラリが搭載されています。今回単純にピークを検出するfindpeaks
を用います。この関数はsignal
パッケージに含まれているため、Octaveのコマンドウィンドウで
pkg load signal
と打ってもらう必要があります。
findpeaks
実際に先ほどのプログラムにfindpeaks
を追加してグラフに表示させてみます。
% % 走行データ解析プログラム % コマンドウィンドウで pkg load signal 必須 % % 走行データの読み込み rpm = load('car_rpm.txt', ' ') ; % 走行データ配列の転置 rpm = rpm.'; % 時間軸の生成 x = 1 : length(rpm); [pks1 idx1] = findpeaks(rpm, 'DoubleSided', 'MinPeakDistance', 1); % グラフのプロット plot(x, rpm); hold on; plot(idx1, pks1, 'o') ; % ラベルの表示設定 xlabel('TIME'); ylabel('RPM');
しかし、このfindpeaks
を用いただけでは正常にピーク値を導き出すことができません。今求めたいpeakとは「明らかに突出していて、回転数が前回の測定値よりも格段に大きいとき」という意味を持ちますので、以下のようなpeak値は不要なのです。
なので、プログラムにひと手間加え、次のようなプログラムが完成します。
ピーク値検出
% % 走行データ解析プログラム % コマンドウィンドウで pkg load signal 必須 % % 走行データの読み込み rpm = load('car_rpm.txt', ' ') ; % 走行データ配列の転置 rpm = rpm.'; % 時間軸の生成 x = 1 : length(rpm); [pks1 idx1] = findpeaks(rpm, 'DoubleSided', 'MinPeakDistance', 1); new_pks1 = []; new_idx1 = []; th_rpm = 20; % 認識する閾値を指定 for i = 3 : length(rpm) - 2 prev = rpm(i - 2); % currentの一つ前のデータ current = rpm(i); % peakかどうか確認する点 next = rpm(i + 2); % currentの一つ後のデータ b_slope = current - prev; % 2つのデータの差を計算 a_slope = current - next; % 2つのデータの差を計算 % % b_slope > 0 と a_slope > 0 より current が前後よりも突出している(つまりpeakの可能性) % b_slope, a_slope ともに閾値以上の差(つまりpeakの可能性) % if (b_slope > 0) && (b_slope > th_rpm) if (a_slope > 0) && (a_slope > th_rpm) if (rpm(i) > 1500) % 可能性のあるものだけ、新規変数に追加 new_pks1 = [new_pks1, rpm(i)]; new_idx1 = [new_idx1, i]; endif endif endif endfor % グラフのプロット plot(x, rpm); hold on; plot(new_idx1, new_pks1, 'o'); % ラベルの表示設定 xlabel('TIME'); ylabel('RPM');
修正を加える前よりかは、きれいに取得できていることが分かると思います。
まとめ
世の中には「家具、家電、その他いろいろ」なものに対するIoT製品や記事はよく見かけるのですが、「車」という分野ではあまり見かけないような気がしました。最近では「自動運転」が騒がれていますが、こんな感じの簡単で面白い「情報 X 車」コラボをしたくなったので、あえてレベルを下げてみました。 まだまだ、できることはあります。これをうまく応用して作りこむと
こんな感じの「自作の」ヘッドマウントディスプレイすら作れるようになると思います。ってかできます。なので、この記事で興味を持った方は調べてみてください。もっと面白いことたくさんあります!!
ではでは~。
ソースコード
https://gist.github.com/nomunomu0504/dc3dc538bbc7738ecdff2a771a0c6c29