お久しぶりです。石川です。ブログ書くのは3ヶ月ぶりくらいでしょうか…気付けばもう12月も半分終わってしまいました。早すぎる。
はじめ
この記事は FIXER Advent Calendar 2021 14日目の記事です。前日の記事は椎名くんの こちら です。3Dモデリング、ちょっと興味あるんですけどなかなか手が出ません。時間が足りねえ…
久しぶりのブログに何を書こうか考えたんですが、最近戯れている Windows Server 絡みの記事かなぁ〜とか思ってたんですがもっと寝かせていた趣味の方に突っ走ろうと思います。
ということで今回は、
車のデータを取得してAzureに送信する
という IoT みたいなことをやってみます。
東京から四日市に移住してそろそろ1年という時、ふと実家で眺めていた車が欲しくなり、車を買っていました。
車というのは実は大量のセンサーとそれらが生み出すデータで溢れています。それらを制御/処理するため、モノによってはその辺りに転がっているようなパソコンよりも高性能なシステムを積んでいたりもします。自動運転などの技術もそういった技術を突き詰めていた結果できているのかなぁとか思ったり。
まずは前提となる技術を軽く説明しましょう。
OBD(On Board Diagnosis)
自動車にはOn Board Diagnosisと呼ばれる自己診断機能が組み込まれています。エンジンチェックランプとかが点灯するアレです。あれは車がエンジン回りでエラーが起きたと自己診断を行いメーターパネルへの表示を行なっています。
歴史など詳しいことは Wikipedia あたりを調べてみてください。
運転席付近に車載コンピュータと通信を行うための端子が用意してあります。最近(10年前〜今くらい)の車だと OBD-II と呼ばれる規格で統一されています。
そちらに通信用の端子などぶっ込んであげることで車両の情報を取得することができちゃう、という感じです。今回は OBD2 での通信を楽にしてくれる ELM327 というチップを積んだ bluetooth 経由で通信ができるドングルを入手してたのでそれを使います。
アーキテクチャ?
今回作るものの全体像は
こんな感じです。雑にPower BIで見れるようにしたらおもしろいかな〜と思ったので可視化まで含めてます。
実装
※ 以下に、車載システムと通信を行うコードを記載しています。車載システムは通常ユーザーが通信を行わない想定で制作されており、想定外の故障の原因となりうる可能性があります。同様のことを行う場合は自己責任にてお願いします。
持っていたドングルは車に刺して電源をオンにし、 bluetooth でペアリングを行うと Windows からはシリアルポートとして見えます。
ありがたいことに、シリアル通信のインターフェースで OBD2通信をラップしているライブラリ がNugetに公開してあったので先人の知恵に感謝しつつ使わせていただきます。
一番壁になるのは車両との接続なので、書くコード自体は少ないです。(夜中の2時間くらいで作ったので dynamic でHTTPリクエストを組み立てているところなどはスルーしてください…)
サンプルを参考にします。
コンソールアプリ
using System;
using System.Dynamic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OBD.NET.Common.Devices;
using OBD.NET.Common.Logging;
using OBD.NET.Common.OBDData;
using OBD.NET.Desktop.Communication;
using OBD.NET.Desktop.Logging;
namespace CarObd.Console
{
static class Program
{
private static HttpClient _client;
static async Task Main()
{
_client = new HttpClient();
var comPort = "COM3"; // bluetoothのシリアルポート
using (SerialConnection connection = new SerialConnection(comPort))
using (ELM327 dev = new ELM327(connection, new OBDConsoleLogger(OBDLogLevel.None)))
{
dev.Initialize();
while (true) // 無限に送りたい
{
dynamic req = new ExpandoObject();
var rpm = await dev.RequestDataAsync<EngineRPM>();
if (rpm != null) req.rpm = rpm.Rpm.Value;
var speed = await dev.RequestDataAsync<VehicleSpeed>();
if (speed != null) req.speed = speed.Speed.Value;
var oilTemp = await dev.RequestDataAsync<EngineOilTemperature>();
if (oilTemp != null) req.oilTemp = oilTemp.Temperature.Value;
var fuelLevel = await dev.RequestDataAsync<FuelTankLevelInput>();
if (fuelLevel != null) req.fuel = fuelLevel.Level.Value;
req.carName = "BMW 320i"; // 識別できるように決め打ち
var reqStr = JsonConvert.SerializeObject(req);
await _client.PostAsync("<Logic Appのエンドポイント>", new StringContent(reqStr, Encoding.UTF8, "application/json"));
Console.WriteLine("Sent!");
}
}
}
}
}
イニシャライズしてデータを取得して HTTP POST で送信するだけの単純なプログラムです。
ライブラリのサンプルコードで接続確認ができたらちょっと書き換えるだけです。
ロジックアプリ
以下のような json をコンソールアプリから送信するのでそれに噛み合うように組み立てます。
{
"rpm": 671.5,
"speed": 0,
"oilTemp": 90,
"fuel": 8.627450980392158,
"carName": "BMW 320i"
}
各項目の意味は
- rpm: エンジン回転数(rpm)
- speed: 車速(km/h)
- oilTemp: エンジンオイル温度(℃)
- fuel: 残燃料(%)
- carName: 車両名(識別用なので何でもいい)
といった感じです。
同じように SQL Database のテーブルを組んで型を噛み合うように変換し、 Insert するアクションを作っておきます。
出来上がったものが こちら
API を書くのが手間なときは Logic App で受けちゃえるの楽すぎて感動しました。推しです。
結果を確認
出来上がったら実際に動かして確認します。ちょっとドライブ。
可視化
家に帰ったら SQL Database に接続してみてみましょう。
無事データが入っているのを確認できたので雑に Power BI に食わせます。コード上で待ち時間をかけてないのでデータは秒間 1〜2 レコードくらい飛んできます。
2軸の折れ線グラフを選択し、お試しで値に車速とエンジン回転数をセットしてみます。軸はもちろん時間です。
その結果がこちら!
値の動きが激しすぎる。
2本とも 0 に落ちているのは信号待ちのアイドリングストップです。エンジン止まってもデータは取れるんですね。
うーん。なんか相関あるのかなこれ。
加速時のデータが極端に崖みたいになってたら運転荒いよってことなんでしょうか。街乗りは加減速が多くてグラフガッタガタになることはわかりました。高速でも試してみたい。
オイル温度は時間に応じて綺麗に右肩上がりになっていますね。
アイドリングストップ時に下がっているのも特徴的です。
おわり
ということでお手軽?に車を IoT 化?してみました。
お手軽じゃないとか IoT じゃないとかいう意見は受け止めます。
もっと色々なデータが取れそうですが、取ったデータの使い道があまり思いつかないのでこれっきりでお蔵入りになるかもしれません。
PCがないと動かせないの結構面倒というかボトルネックなので Raspberry Pi とかで動くように改良したいですね。もっとも、半導体不足の煽りを受けてるのか国内在庫がスッカラカンな気がしますが。
次は Windows Server ネタでもやるかな…
それでは!