工場長のブログ

日々思ったことを書いてます。

ArduinoでCANのテレメトリ with LTE-M Shield + SORACOM

仕事でCANを扱うことになりそうだったので予習を兼ねて自分の車の位置情報とエンジンの回転数、速度をクラウドにアップロードして可視化してみるというのをやってみた。

作ったものの全体像は以下のようなイメージ。クルマに載せるデバイス(canduinoと命名)はエンジンの回転数と速度をOBD2コネクタ経由でCANから、位置情報をGPSモジュールから取得し、Unified Endpoint経由でSORACOM Harvestに格納し、SORACOM Lagoonで可視化している。間にSORACOMが提供するバイナリパーサーという機能を挟むことによって、canduinoから送信されたバイナリをJSON化している。

f:id:imai-factory:20210419073036p:plain

バイスの見てくれはこんなかんじ。クルマのUSBコネクタから電源をとっていて、DB9はOBD2コネクタにつながっている。LTEのアンテナの先っぽにGPSモジュールを巻きつけているのは、いちばんショートしなさそうな場所をさがした結果w

f:id:imai-factory:20210418215700p:plainf:id:imai-factory:20210418215738p:plain

最終的にこんなかんじにSORACOM Lagoonで可視化。首都高で有明から箱崎、C1外回りを通って早稲田まで移動。10秒に1回くらい位置情報を送ってるんだけどなんか荒い。地図の問題なのかデータの問題なのかは今回は検証していない。エリアチャートは上段が時速で下段がエンジンの回転数。

f:id:imai-factory:20210418220148p:plain

CANとは

 CANとはControl Area Networkの略称で、クルマの車内ネットワークなどに利用されるBUSの規格、つまりTCP/IPでいうとL2相当の技術。特徴としては、CANにつながるノードたちは自分のデータに対してCAN IDというIDを付与しそれぞれ一定の周期でBUSに流し(ブロードキャストし)ているということ。データがほしいノードは、BUSに流れてくるデータを受信し、あらかじめ知らされているCAN IDのパケットだけをフィルタリングして処理をしていく。CANのパケットは非常に単純で、CAN IDが11bitもしくは29bit、ペイロードは等しく8byteの長さになっている。

 

クルマで利用される場合は、このBUSにエンジンの回転数やスロットルの開き具合などいろいろな情報がリアルタイムに流れている。もちろん、データ読み取りだけでなく命令の送信などにも使われているので、これを使うことでドアのロックを外せちゃったりもする。らしい。ただし、CANが定義するのはあくまでパケットのフォーマットとそのやりとり手法のみであって、パケットの中身については定義を提供していない。実際、クルマのCAN BUSを流れるパケットは、メーカーや世代、車種ごとにさまざま、かつプロプライエタリである。クルマのチューニングショップがやっているエンジンの燃焼タイミングの最適化なんかは、きっとがんばってこれをリバースエンジニアリングして、CANによる設定の上書きなどしているんだと思う。CANのデータ解析については結構いろんなひとがやっているのでこんなプロジェクトがあったりもする(例がMazdaなのは自分の車がそうだから)。CAN BUSに例えばどんなデータが流れているか興味があるひとは見てみるといいと思う。

Mazda CAN ID - OpenGarages

CANってなに?の詳細についてはこちらのリンクをみてもらうのがいいかなと思う。いままでいろいろなウェブサイトでCANとは?を調べてみたけど、これがいちばんわかりやすかった。

jp.seeedstudio.com

 

バイスを作る

バイスのベースにはArduino Unoに、その上に順番にSeeedのCAN-BUS ShieldSORACOMのLTE-M Shieldをかぶせている。あとは秋月電子のGPSモジュールをいちばんうえに。最終的なデバイスの見た目はこんなかんじ。

f:id:imai-factory:20210417173636p:plain
CAN-BUS Shieldについてググるともうひとつ、SparkFunのGPSモジュールを接続できるシールドが見つかるのだけど、このCAN BUSはなんですか?ArduinoでCAN BUSモジュールを使いましょう!というブログがとてもわかり易くCANについて説明してくれていたのでこちらを使ってみることに。なお、このブログのいいところはOBD2とCANの違いについても説明してくれているのでとてもよかった(CANもOBD2についてもそのくらい素人です)。あともう一点このシールドがよかったのは、DB9コネクタと2本線の端子台どちらでもCANが使えること。ひとまず2セット買っておけば、実際のクルマに繋がなくても開発ができる。さらに言えば、あとでわかったことだけど、SeeedのモジュールはハードウェアレベルでのCAN IDフィルタリングの機能を実装してくれているので、ソフトウェア側でフィルタリング書かなくていいので便利。

 

モノが揃ったので組み立てて、SeeedやSORACOMの提供してくれているサンプルコードをそれぞれ単体で動かすところまではサクッといったんだけど、CAN経由で取得したデータをLTEでアップロードしようとするとうまく動かない。エラーも吐かない。たぶんCANとLTEのシールドでピンが被ってるんだろうなと回路図とかいろいろ調べてみると、あたりっぽい。CANは9,11-13、LTEは10-11がほしそうで、ピン11が被ってる。ミソとかモシってなんだよ~とかぼやきながら(SPI初めて触ったのです)Seeedのwikiを見るとCANのほうはピンアサインの変更にパターンカットが必要そう。一方、LTE-M Shieldのほうは配線だけで変更できそうなのでこちらを変更することにして、結局3と4を使う形に。

 

 GPSモジュールも含めて、最終的なArduinoから見た接続は以下のような感じ(たぶん)。

f:id:imai-factory:20210417181345p:plain
なんとか動くようになったのでOBD2とDB9の変換ケーブルを使ってドキドキしながらクルマにつなげてみる・・・!

SORACOM Harvestにデータアップロードできたじゃん!!!!1

youtu.be

 

別のスケッチを使って内容をシリアルコンソールにダンプしてみるとこんなかんじでじゃんじゃんCANのデータフレームが流れてくる。

0000.092 RX: [00000000](00) 00 00 00 00 00 53 56 57 
0000.192 RX: [00000000](00) 00 00 00 00 00 53 56 58 
0000.292 RX: [00000000](00) 00 00 00 00 00 53 56 59 
0000.394 RX: [00000000](00) 00 00 00 00 00 53 56 5A 
0000.494 RX: [00000000](00) 00 00 00 00 00 53 56 5B 
0000.594 RX: [00000000](00) 00 00 00 00 00 53 56 5C 
0000.695 RX: [00000000](00) 00 00 00 00 00 53 56 5D 
0000.796 RX: [00000000](00) 00 00 00 00 00 53 56 5E 

 

CANのデータフレームを読むにはメーカーなどが提供するビットの読み取りルールが必要になるので「うん、まあSORACOM Harvestまでデータとんだし、まあ今回はここらへんまでかな・・・」と思っていたけど、もう一歩踏み込んでみることに。

CANIDについて調べてみる

mazda canid」とググってみるとさっそくこんなページ(前述のやつ)に行き当たる。

Mazda CAN ID - OpenGarages

「おおおお!」と思って手元のダンプしたデータと突き合わせてみるが、実際に流れているデータのCAN IDはこちらの表にぜんぜん見当たらない。表の最終更新日時が2016/03/13なのでしょうがないかなーと思いながらもうすこしググっているとこんな情報に突き当たる。

mx5things.blog

曰く

0x202 "Bytes 0,1" | "Bytes 2,3" | "Bytes 4,5" | "Bytes 6,7"
Engine RPM x4 | "Vehicle speed, km/h x100″ |"Throttle position, TPS% x 640" | Unknown

 とのこと。ひとまずこのとおりにCANID:202のメッセージをデコードしてみると・・・

f:id:imai-factory:20210418230239p:plain

読めた!青いグラフがエンジンの回転数で、目盛りは左側。赤が速度で目盛りは右側。家の周りをぐるっと一周走ってきた感じなので、速度はときどき0になったり(信号待ち)しながら、最大で40km/hくらい。回転数も750くらいで下限があるので、アイドルのときのことだろう。また、青と赤の時系列的推移もだいたい一致する。(エンジンの回転数がときどき0になっているのはアイドリングストップのため。)

Disclaimar

ここまで読んでいただけたらわかると思うけど、このCANパケットのデコードは「ググって見つけた情報をそのまま使ってみたら動いた」という話なのであしからず。併せて、情報をオープンにしてくれているみなさまには大変感謝。

GPSモジュールとSoftwareSerialでハマる

 CANからデータ読んでクラウドに送信して、それが読めることがわかってきたので、こんどはデバイスGPSモジュールを追加してみる。

まずハマったのが、SoftwareSerialが複数になる点。今回の構成だと、Arduinoからみてハードウェアシリアルで母艦のWindowsとつながっていて、SoftwareSerialでLTE-M Shield、GPSモジュールとつながっている(CAN ShieldはSPI)。

オフィシャルドキュメントには必要に応じて`listen()`を呼べばいいよと書いてあるが、これだとどうもうまく行かない(LTEの通信はうまくいくが、GPSモジュールのデータが読めない)。いろいろ調べていると(さまよいすぎてURLを見失ったが、Arduino Forumでこのトピックについてすごくわかりやすくて網羅的な解説をしてくれている一がいた。URL見つけたら追記する。)、以下のことがわかった。

  • ArduinoのSoftwareSerialは、複数のインスタンスがあっても、受信バッファはひとつ
  • listen()はこの受信バッファを切り替えるメソッド
  • listen()すると受信バッファがクリアされる
  • なお、受信バッファは64byte

この特性を理解したうえでこんどはGPSモジュールのマニュアルを読んでいると「このモジュールはデフォルトで1Hzで位置情報を送信する」ということがわかってくる。つまり、GPSモジュールと接続されているSoftwareSerialのポートをlistenし始めてから1秒待たないと情報が揃わない可能性があると。ということでざっくり以下のようなコードを書いたらうまく情報が得られるようになった。

gpsSerial.listen();

delay(1000);

while (gpsSerial.available() > 0) {

  // データを読み取っていろいろ処理

}

 このGPSモジュールの取り扱いについては下記のブログに大変お世話になりました。ありがとうございます!

garchiving.com

クラウド側でデータを受け取って処理する

CANとGPSのデータが取れるようになったので、あとはそれをクラウドに送っていい感じに処理してやればいいというフェーズに。ちなみにデータのパースや各種処理をデバイス側でやるという選択肢は、エッジ側に処理やロジックを持たせなくないので、今回は考えない。ということでデバイスは、おめあてのCANIDのメッセージを受け取るごとに以下の情報をバイナリとしてそのままクラウドに送信する。緯度と経度を100倍してintとして扱っているのはArduinoのコード的に扱いやすいから。(後述のSORACOMのBinary Parserで1/100して取り扱う。)

  • CANID(char array 4byte)
  • CANペイロード(byte array 8byte)
  • 緯度を100倍した値 (int 4byte )
  • 経度を100倍した値(int 4byte)

CANとGPSを読んでひとつのバイナリとしてデータを送信する最終的なデバイス側のコードは以下のとおり。

 

canduino.ino · GitHub

 

SORACOMにはBinary Parserという素敵な機能があって、固定長のバイナリを任意にパースしてJSON化することができる。上記のコードから送信されるデータを受け取るために、今回は以下のようなパーサーを定義した。詳細に興味をもってくれた方はこちらのリファレンスを見ていただければと思うが、下記のパーサーを通すと・・・

canId:0:char:4 rpm:4:uint:16:/4 speed:6:uint:16:/100 latitude:12:int:16:/100 longitude:14:int:16:/100

 

バイスからから送信されたバイナリ(16進数で表記)は・・・

30323032000000322D0000

こんなかんじにパースされてSORACOM Harvestに転送してくれる。(上記のバイナリのCANペイロードと位置情報は適当なものなので注意)

{"canId":"0202","rpm":776.75,"speed":3.05,"latitude":35.71,"longitude":139.73,"binaryParserEnabled":true}

出来上がった!

クラウドJSONでデータが保存されれば勝利!!!!今回はSORACOM Harvest + SORACOM Lagoonで可視化を実現しているが、SORACOM BeamやSORACOM FunnelでAWSGoogle Cloudに転送していろいろ処理してやることももちろん可能。ということで今回はいったんここまで。

このさき

ちなみに今回は、「Mazdaの0x202というCAN IDのメッセージ(と、GPSによる位置情報)を読み取ってクラウドに送信する」というデバイス側のコードを書いているが、もう一歩すすめるのであれば、読み取るCANIDをクラウドから取得してそのデータを送信する、くらいまでもっていけるといいなと思っている。

 

かのクリーンアーキテクチャでは「インフラストラクチャに1行でも依存したコードは”ファームウェア”だ!」と宣言されているので、今回わたしの書いたコードはMazda車の(しかも特定の世代や車種に依存した)ファームウェアということになる。よりクイックにいろいろなデータを試験的に収集することができるようになるためには、「どのデータを取得する」みたいな部分を外部から注入できるようになるといまどきっぽいね。(依存性の注入なんで「いまどき」どころではない話だが、物理デバイスの世界ではまだまだ新しい。気がする。)このへんは、例えばSORACOMのメタデータサービスを使えば実装できると思っているが、今回はてがまわっていないのでまた今度。