iOS
ifttt
セキュリティ
Swift
IoT

車のセキュリティシステム作ってみた

【まえがき】

最近よく車の盗難事件の話が耳に入ってきますね。

1位 プリウス 59件(19.7%)
2位 ハイエース 43件(14.3%)
3位 ランドクルーザー 28件(9.3%)
4位 アクア 27件(9.0%)
5位 レクサス 16件(5.3%)
6位 インプレッサ 14件(4.7%)
7位 クラウン 12件(4.0%)
8位 アルファード 7件(2.3%)
8位 カローラ 7件(2.3%)
10位 スカイライン 6件(2.0%)
10位 フォワード 6件(2.0%)

(※「盗難車 ランキング」とかで検索)

そして、私の乗っている車が盗難ランキング上位に食い込む車種なので不安で夜しか眠れません。

早急に盗難対策が必要です。(?)

【作ったもの】

車が振動や移動を感知したらLINEに通知してくれるシステム

【デモ動画】
まずは、デモ動画を見てもらいましょう


はい、こんな感じです。案外使えそうです。

【システムの概要】

とりあえず全体像をざっくり説明します

【登場するもの】
・iPhone 2台
・自作iPhoneアプリ
・swift (プログラミング言語)
・IFTTT (ウェブサービス)
・LINEのアプリ

【ざっくりとした流れ】
①iPhoneの内臓センサーを利用した振動検知のアプリを作成
②iPhoneを車に設置し、アプリが振動を検知したらHTTPリクエストを送信
③HTTPリクエストをIFTTTが受け取って、LINEに通知
④LINEを見て驚く

image.png

上にも書きましたが、計測用のiphoneと、通知を受け取る用のiphoneが必要です。
私は以前使用していたiphone5sを計測用として車に設置しています。
自宅のwifiが車まで届くので、SIMカードを入れとく必要もないです。
5G帯を使わなければ屋外でwifiを使用しても電波法にはひっかかりません。

【ちょっと詳しい処理の流れ】

①アプリの計測ボタンが押されたら、加速度サンプルを取得

┗x,y,z軸の加速度の基準値となる値を算出します。
┗それぞれ30個ずつ加速度を取得し、各平均をとったものを基準値とします。

②基準値の取得が終わったら感知モードに移行する

┗観測した加速度の値が、①で取得した基準値から一定量離れた状態が連続した場合に異常と判定
┗異常と判定されたらIFTTTで設定したパスにHTTPリクエストを投げる

③IFTTTがリクエストを受け取り、LINEに通知

┗IFTTTのAppletってやつを作る必要がありますが簡単です
┗webhookってやつを使えばHTTPリクエストをトリガーにLINE通知してくれます

全体的にそんなに難しいことはしてないんですが、「どうなったら異常と判定するか」の部分が重要で、設定が難しいという感じです。

加速度の生データはiPhoneの置き方によって全然違う値になるので、サンプル値の取得は必須でした。

サンプル値からどれぐらい離れたら異常判定とするかは、実際にiPhoneを手で揺らしてみて、
これぐらいかなーという値を基本設定としましたが、設定画面で感度を3段階で調整できるようにしたので、実際に運用してみて感度を決めるのがよさそうです。

ただ、感度をMAXにするとたぶん風の強い日とかは通知が鳴りやまないです。
感度が低めだと明らかに車が移動したりおかしな振動があった時だけ通知されるので、
高すぎないほうが実運用には向いているかもしれません。

【アプリ画面】

左が加速度とかを出してる画面で右が感度設定画面です。
正直どうでもいいのでそのまま下にスクロールしていいです。

image.png

【ソースコード抜粋 (参考程度で..)】

動けばいいや精神で書いたので良いコードではないですがキモの部分だけ一応載せておきます。
設定画面で閾値を変える部分とか、画面表示用の変数とかは消しちゃってますので仕組みの部分だけです。

    // MotionManager
    let motionManager = CMMotionManager()

    //URLSession
    let session: URLSession = URLSession.shared

    //falseだと計測モードになる
    var getsampleflag:Bool = true;

    //サンプルしまっとく配列 
    var xsamples:[Double] = [];
    var ysamples:[Double] = [];
    var zsamples:[Double] = [];

    //サンプル平均を入れとく変数    
    var xsample:Double = 0;
    var ysample:Double = 0;
    var zsample:Double = 0;

    //サンプル取得数」カウンター
    var counter:Int = 0;

    //閾値 小さいほど過敏に反応する
    var threshold:Double = 0.05;

    //1つ前の加速度データが閾値を超えていたかどうかフラグ
    var xflag:Bool = false;
    var yflag:Bool = false;
    var zflag:Bool = false;

    //センサー取得をスタートする関数
    func startAccelerometer(){
        if motionManager.isAccelerometerAvailable {
            motionManager.accelerometerUpdateInterval = 0.2            
            motionManager.startAccelerometerUpdates(
                to: OperationQueue.current!,
                withHandler: {(accelData: CMAccelerometerData?, errorOC: Error?) in
                    self.outputAccelData(acceleration: accelData!.acceleration)
            })        
        }
    }

    //サンプル取得関連の処理
    func getsample(acceleration: CMAcceleration){

        //それぞれのサンプルを収納
        xsamples.append(acceleration.x);
        ysamples.append(acceleration.y);
        zsamples.append(acceleration.z);

        //サンプル取得数30    
        if(counter == 30){
            //サンプル取得フラグをfalseにしとく
            getsampleflag = false;

            for i in xsamples {
                xsample = xsample + i;
            }
            for i in ysamples {
                ysample = ysample + i;
            }
            for i in zsamples {
                zsample = zsample + i;
            }

            //x,y,z軸のサンプル値をセットしとく
            xsample = xsample/30;
            ysample = ysample/30;
            zsample = zsample/30;
        }
    }

    //加速度データ取得ごとに呼ばれる関数
    func outputAccelData(){ 

        if(getsampleflag){
            //サンプル関連の処理
            getsample(acceleration:acceleration);
        }else{                
            if(acceleration.x < (xsample - threshold)||xsample + threshold < acceleration.x){
                if(xflag){
                 toIFTTT();
                }else{
                 xflag = true;
                }
            }
            else if(acceleration.y < (ysample - threshold)||ysample + threshold < acceleration.y){
                if(yflag){
                    toIFTTT();
                    yflag = true;
                }else{
                }
            }
            else if(acceleration.z < (zsample - threshold)||zsample + threshold < acceleration.z){
                if(zflag){
                    toIFTTT();
                }else{
                    zflag = true;
                }
            }else{
            }
        }
        counter+=1;     
    }

    // センサー取得をストップする関数
    func stopAccelerometer(){
        if (motionManager.isAccelerometerActive) {
            motionManager.stopAccelerometerUpdates()
        }
    }

    // IFTTTに通知する関数    
    func toIFTTT(){
        let listUrl = "IFTTTで発行したURLをここに書く"      
        guard let url = URL(string: listUrl) else { return }     
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error!.localizedDescription)
            }            
            guard let data = data else { return }            
            _ = try? JSONDecoder().decode(JsonSample.self, from: data)
            }.resume()
    }
    //Jsonの構造体
    struct JsonSample : Codable{
        var id : Int
        var name : String
    }

【あとがき】

半分ネタなので、問題点は多々あります。

バッテリー問題
┗アプリ起動しっぱなしなので、1日ぐらいが限界かもしれません
┗夜は超えれる気がしますが、ずっと動かすにはモバイルバッテリか何かを繋いでおかないと
(アプリの画面を黒にしたのはささやかな抵抗)

メモリ問題
┗メモリ解放ちゃんとしてないので、たぶんずーっと動かしてるとアプリ落ちます

人の車盗むやつ許せない問題
┗ほんとうはこんなアプリで抵抗するのではなく拳でわからせてやりたい