マイコン基盤のセンサーデータをAWS IoT FleetWiseで収集してみた

2024.02.23

CX事業本部製造ビジネステクノロジー部の新澤です。

AWS IoT FleetWiseは、いわゆるコネクティッドカーと呼ばれるネットワークに接続された車両で取得されたデータを効率よく収集することに特化したマネージドサービスです。

IoT FleetWiseの機能を試すためにデバイスを準備せずともAWS上で完結するデモも提供されており、簡単に試してみることもできますが、今回はRaspberry PiやArduinoといったマイコン基盤を使って実際の車両を模した構成を作り、IoT FleetWiseでのデータ収集を試してみました。

概要

今回試したのは、一般の車両でもよくある「暗くなったら自動的にヘッドライトを点灯する」という機能を模したものです。 構成は下図の通りです。

Arduino、Raspberry Piなどの接続は下図のようにしました。

環境準備

Arduinoの準備

以下がコードになります。

#include "Arduino.h"
#include "SPI.h"
#include "mcp2515.h"

const int LED_PIN = 7;
const int CANBUS_PIN = 10;

// 送信メッセージ格納用
struct can_frame canMsg;
// CANコントローラに接続したPIN番号でインスタンス初期化
MCP2515 mcp2515(CANBUS_PIN);

void setup()
{
  // LED接続PINの設定
  pinMode(LED_PIN, OUTPUT);

  while (!Serial);
  Serial.begin(9600);
  SPI.begin();

  // CANインターフェースの初期化
  mcp2515.reset();
  // ビットレート1Mbps, クロック8MHzに設定
  mcp2515.setBitrate(CAN_1000KBPS, MCP_8MHZ);
  // CANをノーマルモードに設定
  mcp2515.setNormalMode();
}

void loop()
{
  uint16_t cds_raw; // 光センサーの値(0〜1023)
  uint16_t cds_pct; // 明るさ(0.00〜100.00%を整数で持つ(0〜10000))
  uint8_t led; // LEDの点灯フラグ(HIGH: On, LOW: Off)

  cds_raw = analogRead(A6);
  cds_pct = float(cds_raw) / 1023.0 * 100.0 * 100.0; // 100倍して小数第2位までを整数で持つ

  // 明るさ50%未満でLED On
  if (cds_pct < 5000)
  {
    led = HIGH; // ON
  }
  else
  {
    led = LOW; // OFF
  }
  digitalWrite(LED_PIN, led);

  Serial.println("cds_raw=" + String(cds_raw) + "; cds_pct=" + String(cds_pct) + "; led=" + String(led) + ";");

  // CANにデータを送信
  // 明るさ(CAN ID:1)
  canMsg.can_id = 0x001; // CAN ID: 1
  canMsg.can_dlc = 2; // CANデータ長2バイト
  memcpy(canMsg.data, &cds_pct, 2);
  mcp2515.sendMessage(&canMsg);

  // LED点灯状態(CAN ID:2)
  canMsg.can_id = 0x002; // CAN ID: 2
  canMsg.can_dlc = 1;    // CANデータ長1バイト
  canMsg.data[0] = led;
  mcp2515.sendMessage(&canMsg);

  delay(500);
}

Raspberry Piの構築

SDカードにRaspberry Pi ImagerでOSをインストールしておきます。 今回は、Raspberry OS(64-bit)を使用しました。 インストール時の設定で事前にWiFiルーターへの接続設定も済ませておくと楽です。

OSが準備できたらFleetWise Edge Agentの Raspberry Piチュートリアルのドキュメントに従って、Edge AgentのビルドとRaspberry Piへのデプロイを行います。

AWSリソースの準備

CDKの実装

AWSリソースをCDKで実装してみます。

メインとなるIoT FleetWiseのリソースについて記載します。なお、今回は車両1台の構成となるため、フリートは実装していません。

全体のソースはこちらに置いています。

シグナルカタログ

シグナルカタログに登録するシグナルは、 シグナルカタログの標準であるVSS(Vehicle Signal Specification)から今回の機能に合わせて以下の2つを選び定義しました。

  • Vehicle.Body.Lights.Beam.Low.IsOn: ロービームヘッドライトの点灯状態
  • Vehicle.Exterior.LightIntensity: 明るさ(%)
    const signalCatalog = new fleetwise.CfnSignalCatalog(this, "SignalCatalog", {
      name: "catalog",
      description: "Sample Signal Catalog",
      nodes: [
        {
          branch: {
            fullyQualifiedName: "Vehicle",
          },
        },
        {
          branch: {
            fullyQualifiedName: "Vehicle.Body",
          },
        },
        {
          branch: {
            fullyQualifiedName: "Vehicle.Body.Lights",
          },
        },
        {
          branch: {
            fullyQualifiedName: "Vehicle.Body.Lights.Beam",
          },
        },
        {
          branch: {
            fullyQualifiedName: "Vehicle.Body.Lights.Beam.Low",
          },
        },
        {
          actuator: {
            fullyQualifiedName: "Vehicle.Body.Lights.Beam.Low.IsOn",
            dataType: "BOOLEAN",
          },
        },
        {
          branch: {
            fullyQualifiedName: "Vehicle.Exterior",
          },
        },
        {
          sensor: {
            fullyQualifiedName: "Vehicle.Exterior.LightIntensity",
            dataType: "FLOAT",
            unit: "percent",
            min: 0,
            max: 100,
            description: "Light intensity as a percent. 0 = No light detected, 100 = Fully lit.",
          },
        },
      ],
    });
モデルマニフェスト

モデルマニュフェストでは、定義する車両モデルで利用可能なシグナルをシグナルカタログから選んで定義します。

    const modelManifest = new CfnModelManifest(this, "ModelManifest", {
      name: "model-manifest",
      status: "ACTIVE",
      nodes: [
        "Vehicle.Body.Lights.Beam.Low.IsOn",
        "Vehicle.Exterior.LightIntensity",
      ],
      signalCatalogArn: signalCatalog.attrArn,
    });
デコーダーマニフェスト

デコーダーマニフェストでは、車両モデルで定義したシグナルのデータがEdge Agentで収集された時にバイナリから人間が読める形式に変換するための定義を行います。

IoT FleetWiseの構成要素の中で、最も車両データ収集に特化されている特徴的な要素だと思います。

    const decoderManifest = new CfnDecoderManifest(this, "DecoderManifest", {
      name: "decoder-manifest-001",
      status: "ACTIVE",
      modelManifestArn: modelManifest.attrArn,
      networkInterfaces: [
        {
          interfaceId: this.INTERFACE_ID,
          type: "CAN_INTERFACE",
          canInterface: {
            name: "can0",
            protocolName: "CAN",
          },
        },
      ],
      signalDecoders: [
        {
          interfaceId: this.INTERFACE_ID,
          type: "CAN_SIGNAL",
          fullyQualifiedName: "Vehicle.Exterior.LightIntensity",
          canSignal: {
            name: "Light_Intensity",
            messageId: "1",
            isBigEndian: "false",
            isSigned: "false",
            startBit: "0",
            length: "16", // ドキュメントでは"バイト数"と書かれていますが、実際には"ビット数"なので注意
                        // Edge Agentから送信されるデータは、value = raw_value * factor + offsetになります
            // ここでは、factorでArduino側で0〜10000の整数にした数値を0.00〜100.00に戻します
            factor: "0.01", 
            offset: "0",
          },
        },
        {
          interfaceId: this.INTERFACE_ID,
          type: "CAN_SIGNAL",
          fullyQualifiedName: "Vehicle.Body.Lights.Beam.Low.IsOn",
          canSignal: {
            name: "Light_Low_IsOn",
            messageId: "2",
            isBigEndian: "false",
            isSigned: "false",
            startBit: "0",
            length: "1",
            factor: "1",
            offset: "0",
          },
        },
      ],
    });
車両

車両には、車両モデルとデコーダーマニフェストを関連付けます。

車両はIoT CoreのThingと紐づけられる必要がありますが、Thingを新規作成するか、既存の同名Thingと紐づけるかをassociationBehaviorで定義します。

ここでは、前述のEdge Agentをビルドする手順のなかでThingを作成する工程が含まれているため、既存Thingと紐づける設定(ValidateIotThingExists)にしています。

    const vehicle = new fleetwise.CfnVehicle(this, "Vehicle", {
      name: this.THING_NAME,
      decoderManifestArn: decoderManifest.attrArn,
      modelManifestArn: modelManifest.attrArn,
      associationBehavior: "ValidateIotThingExists",
    });
キャンペーン

車両からデータを収集する条件・値、を定義します。

この例では、10秒間隔でデータ収集する定義となっていますが、特定の値が条件に一致したときをトリガーとして収集することもできます。

データの保存先はTimestreamとS3バケットが選択できます。両方同時に設定することはできません。

    const campaign = new fleetwise.CfnCampaign(this, "Campaign", {
      name: "TimeBasedCampaign001",
      action: "APPROVE",
      priority: 0,
      targetArn: vehicle.attrArn,
      collectionScheme: {
        timeBasedCollectionScheme: {
          periodMs: 10000,
        },
      },
      signalCatalogArn: signalCatalog.attrArn,
      signalsToCollect: [
        {
          name: "Vehicle.Body.Lights.Beam.Low.IsOn",
        },
        {
          name: "Vehicle.Exterior.LightIntensity",
        },
      ],
      spoolingMode: "TO_DISK",
      diagnosticsMode: "SEND_ACTIVE_DTCS",
      dataDestinationConfigs: [
        {
          // -- Timestreamに保存する場合
          timestreamConfig: {
            timestreamTableArn: `arn:aws:timestream:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:database/${timestreamDb.databaseName}/table/${timestreamTable.tableName}`,
            executionRoleArn: role.roleArn,
          },
          // -- S3に保存する場合
          // s3Config: {
          //   bucketArn: bucket.bucketArn,
          //   dataFormat: "JSON",
          //   prefix: "TimeBasedCampain",
          // },
        },
      ],
    });

キャンペーンのデプロイ

IoT FleetWiseのマネージドコンソールでキャンペーンを表示し、デプロイを行うと、車両に対してデコーダーマニフェストとキャンペーンが配布され、データ収集が開始されます。

  1. キャンペーン一覧からキャンペーンを選択します。

  2. 「デプロイ」をクリックします。

  3. 確認事項をチェックし、「キャンペーンのデプロイ」をクリックします。

  4. ステータスがRUNNINGになっていれば、車両へのキャンペーンのデプロイが開始されました。車両へのデプロイの進捗状況に合わせて、「接続された車両の数」が更新されます。

データを確認してみる

FWEのログを確認

Raspberry Piにて以下のコマンドでFWEのログを確認します。

sudo journalctl -fu fwe@0 --output=cat

以下のようなログが出ていれば、データ収集と送信が成功しています。

[Thread: 1053] [2024-02-20T23:57:09.418Z] [INFO ] [DataSenderManagerWorkerThread.cpp:219] [operator()()]: [FWE data ready to send with eventID 1940761109 from arn:aws:iotfleetwise:us-east-1:123456789012:campaign/TimeBasedCampaign001 Signals:40 [3:0.000000,3:0.000000,3:0.000000,3:0.000000,3:0.000000,3:0.000000, ...] first signal timestamp: 1708473428833 trigger timestamp: 1708473429418 raw CAN frames:0 DTCs:0 Geohash:]
[Thread: 1053] [2024-02-20T23:57:09.418Z] [INFO ] [DataSenderManager.cpp:295] [send()]: [A Payload of size: 415 bytes has been uploaded]
[Thread: 1053] [2024-02-20T23:57:13.922Z] [INFO ] [DataSenderManager.cpp:356] [checkAndSendRetrievedData()]: [Upload of persisted payloads is finished]

Timestreamのデータを確認

Timestreamの管理コンソールでクエリを実行して、収集されたデータを確認してみます。

  1. Timestreamのテーブルリストからテーブルを選択します。

  2. クエリエディタを開き、テーブル"campaign"のメニューから「データをプレビュー」をクリックします。

  3. クエリ結果を確認します。

2種類のセンサーデータが収集されていることが確認できました!

最後に

今回は、実際にArduinoやRaspberry Piといったマイコン基盤を使ってIoT FleetWiseのデータ収集を試してみました。

キャンペーンによるデータ収集条件の定義と車両への配布、デコーダーマニフェストによるバイナリデータのデコードといった開発が非常に面倒な部分がサービスとして提供されているので、データ収集の開発コストの抑止や開発品質の早期確立に役立つのではないかと感じました。

まだ2つのリージョンでしか提供されていないですが、今後がとても楽しみなサービスです。