M5StickC Plus2 と CO2センサーで室内の CO2 濃度を計測し、AWS IoT Core でデータを可視化してみた

M5StickC Plus2 と CO2センサーで室内の CO2 濃度を計測し、AWS IoT Core でデータを可視化してみた

2025.12.13

製造ビジネステクノロジー部の小林です。

クラスメソッド発 製造業 Advent Calendar 2025 13日目のエントリーです。

突然ですが、室内のCO2濃度って気にしてますか?濃度が高くなると集中力が下がるなんて話もよく聞きますよね(厚労省基準だと1000ppm以下が良いそうです)。
https://www.mhlw.go.jp/bunya/kenkou/seikatsu-eisei10/

そこで今回は、この見えない敵「CO2」を撃退すべく、リアルタイム測定システムを構築してみました!
CO2濃度が高いときは、適度に換気をしたり深呼吸をしたりして集中力を取り戻したいと思います。

このシステムでできること

  • 室内のCO2濃度をリアルタイムで測定
  • AWS IoT Core に wifi 経由でデータを自動送信
  • クラウド上でデータを可視化・蓄積
  • M5StickC Plus2の画面で現在値を確認
  • 10秒間隔での継続的なモニタリング

システム構成

デバイス: M5StickC Plus2
https://docs.m5stack.com/en/core/M5StickC PLUS2

CO2センサー(SCD40)
https://www.switch-science.com/products/8496?_pos=14&_sid=ce29858db&_ss=r

データフロー: [ CO2センサー ] → ( I2C ) → [ M5StickC ] → ( WiFi ) → [ AWS IoT Core ]

┌──────────────────┐
│  SCD40 CO2       │
│  センサー         │ ← CO2/温度/湿度を測定
└────────┬─────────┘
         │ I2C通信 (GPIO 32/33)
         ↓
┌──────────────────┐
│  M5StickC Plus2  │
│  (ESP32-PICO)    │ ← センサーデータを読み取り
│  - WiFi接続      │    画面に表示
│  - 画面表示      │
│  - SPIFFS        │ ← 証明書を安全に保存
└────────┬─────────┘
         │ WiFi/MQTT over TLS (Port 8883)10秒ごとにJSON送信
         ↓
┌──────────────────┐
│  AWS IoT Core    │
│  - Thing登録     │ ← デバイス管理
│  - MQTT Broker   │ ← メッセージング
│  - トピック:     │
│    sensor/co2    │
└──────────────────┘

ということでまずは、M5StickC Plus2 と CO2センサー(SCD40)を購入しました。

M5StickC Plus2

M5StickC Plus2は、M5Stack社が開発している、スティックサイズの超小型IoT開発ボードです。
Wi-FiとBluetooth機能を備えたESP32チップを搭載しており、わずか数センチの小さな本体に、カラー液晶画面、バッテリー、ボタン、ブザー、マイク、6軸センサ(ジャイロ・加速度)などがすべて詰め込まれています。
https://docs.m5stack.com/en/core/M5StickC PLUS2

CO2センサー(SCD40)

SCD40は、スイスのセンシリオン社(Sensirion)が開発した、非常に小型のCO2(二酸化炭素)センサーです。温度・湿度センサーも内蔵しています。
https://docs.m5stack.com/en/core/M5StickC PLUS2

開梱

早速届いたので開梱していきます。
8032_0
8033_0
8034_0
8035_0
8036_0
8037_0

Type-CケーブルでPCと接続したら画面が表示されました!かっこいい...
8038_0
スクリーンショット 2025-12-14 0.40.04

開発環境のセットアップ

Arduino IDE の準備

まず、Arduino IDEでESP32を使えるようにします。

Arduino IDE

Arduino IDE(Integrated Development Environment:統合開発環境)は、Arduinoマイコンボード用のプログラムを作成・書き込むための公式ソフトウェアです。
https://docs.arduino.cc/software/ide-v2/tutorials/getting-started-ide-v2/

ESP32

ESP32C は、中国の Espressif Systems 社が開発した、Wi-Fi と Bluetooth 機能を内蔵した低価格マイコンチップです。

ESP32ボードマネージャーの追加

  1. Arduino IDE を開く
  2. 環境設定 → 追加のボードマネージャーのURL に以下を追加
https://espressif.github.io/arduino-esp32/package_esp32_index.json
  1. ツール → ボード → ボードマネージャー
  2. "esp32" で検索して esp32 by Espressif Systems をインストール

必要なライブラリのインストール

ライブラリマネージャーから以下をインストールします。

  • M5StickCPlus2: M5StickC Plus2の制御
  • Sensirion I2C SCD4x: CO2センサーの制御
  • Sensirion Core: センサー共通ライブラリ
  • PubSubClient (by Nick O'Leary): MQTT通信

下記のドキュメントに詳細が記載してあります。
https://docs.m5stack.com/en/arduino/m5stickc_plus2/program

macOS でのUSB ドライバインストール

M5StickC Plus2 は CH9102F USB-シリアル変換チップを使用しています。macOS で認識させるにはドライバが必要です。

brew install --cask wch-ch34x-usb-serial-driver

接続確認

M5StickC Plus2 を接続すると、/dev/cu.usbserial-xxxxxxxx のようなポートが表示されます。

Arduino IDE で下記を実行します。

  1. ツール → ボード → ESP32 Arduino → M5StickC Plus2 を選択
  2. ツール → Port → /dev/cu.usbserial-xxxxxxxx を選択

スクリーンショット 2025-12-14 1.21.07

CO2センサーの動作確認

CO2センサーの動作確認をしていきます。まずは、M5StickC Plus2 と CO2センサーを付属のケーブルでつなぎます。

テストコード

Arduino IDE で新しいスケッチを作成し、以下のコードを書き込みます。

#include <M5StickCPlus2.h>
#include <Wire.h>
#include <SensirionI2cScd4x.h>

SensirionI2cScd4x scd4x;

void setup() {
  M5.begin();
  Serial.begin(115200);

  // I2Cピンで初期化(SDA=32, SCL=33)
  Wire.begin(32, 33);  // SDA=32, SCL=33

  M5.Display.setRotation(1);
  M5.Display.fillScreen(BLACK);
  M5.Display.setTextColor(WHITE);
  M5.Display.setTextSize(1);

  // センサー初期化(I2Cアドレス: 0x62)
  Serial.println("Initializing SCD40...");
  scd4x.begin(Wire, 0x62);

  // 既存の測定を停止
  uint16_t error = scd4x.stopPeriodicMeasurement();
  if (error) {
    Serial.print("Error stopping: ");
    Serial.println(error);
  }

  delay(500);

  // 測定開始
  error = scd4x.startPeriodicMeasurement();
  if (error) {
    Serial.print("Error starting: ");
    Serial.println(error);
  }

  Serial.println("Waiting 5 seconds for first measurement...");
  delay(5000);
}

void loop() {
  uint16_t co2 = 0;
  float temperature = 0.0f;
  float humidity = 0.0f;

  // センサーから測定値を読み取り
  uint16_t error = scd4x.readMeasurement(co2, temperature, humidity);

  if (error == 0 && co2 > 0) {
    Serial.print("CO2: ");
    Serial.print(co2);
    Serial.println(" ppm");

    // 画面表示
    M5.Display.fillScreen(BLACK);
    M5.Display.setCursor(5, 10);
    M5.Display.setTextSize(2);
    M5.Display.println("CO2 Sensor");

    // CO2濃度
    M5.Display.setCursor(5, 35);
    M5.Display.setTextSize(3);
    M5.Display.print(co2);
    M5.Display.setTextSize(2);
    M5.Display.println(" ppm");

    // 温度
    M5.Display.setCursor(5, 70);
    M5.Display.setTextSize(2);
    M5.Display.print("Temp: ");
    M5.Display.print(temperature, 1);
    M5.Display.println(" C");

    // 湿度
    M5.Display.setCursor(5, 95);
    M5.Display.setTextSize(2);
    M5.Display.print("Hum: ");
    M5.Display.print(humidity, 1);
    M5.Display.println(" %");
  } else {
    Serial.print("Error reading sensor: ");
    Serial.println(error);
  }

  delay(5000); // 5秒ごとに測定
}

アップロードが正常に完了すると、下記のように M5StickC Plus2 の画面に CO2濃度などの情報が表示されます。
8031

CO2センサーに息を吹きかけると、CO2濃度が上昇します。
8039

CO2濃度の目安

室内のCO2濃度は、厚生労働省の建築物環境衛生管理基準で 1000ppm 以下が推奨されています。

  • 400-1000 ppm: 正常範囲(良好な室内環境)
  • 1000-2000 ppm: 眠気や集中力低下の可能性
  • 2000 ppm以上: 換気が必要

室内のCO2濃度が1000ppmを超えたら、窓を開けて換気することをおすすめします!
https://www.mhlw.go.jp/bunya/kenkou/seikatsu-eisei10/

私の部屋、CO2濃度高いですね...😅

AWS IoT Core の設定

それでは IoT Core を構築していきます。その前に簡単に IoT Core について確認します。

AWS IoT Core とは?

AWS IoT Core は、IoTデバイスをクラウドに安全に接続するためのAWSサービスです。
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/what-is-aws-iot.html

主な機能

  • 証明書ベースの安全な認証
  • MQTT プロトコルでの双方向通信
  • 他のAWSサービスとの連携(Lambda, DynamoDB, S3など)
  • 数百万台のデバイスに対応

以下の手順で、AWS IoT Core を設定していきます。

  1. AWS CDK でインフラ構築(Thing, Policy)
  2. 証明書の作成
  3. 証明書のアタッチ
  4. M5StickC Plus2 への証明書配置

それでは、順番に進めていきましょう!

証明書による認証の仕組み

なぜ証明書が必要か?
AWS IoT Core では、デバイス認証に X.509 証明書 を使用します。これにより、なりすましを防止、通信内容の暗号化、デバイスごとの権限管理を行えます。

なぜSPIFFSに保存するのか?
証明書(特に秘密鍵)は、安全に管理する必要があります。

  • ソースに機密情報をハードコードしないため
  • Git に機密情報コミットしないため
  • 証明書の更新が容易

SPIFFS(SPI Flash File System)

SPIFFS(SPI Flash File System) は、ESP32 のフラッシュメモリ上に作成できるファイルシステムです。

ESP32のメモリ
├── プログラム領域(スケッチが格納)
└── SPIFFS領域(ファイルが格納)← ここ!
    ├── rootCA.pem
    ├── cert.pem
    ├── private.key
    ├── wifi_ssid.txt
    └── wifi_password.txt

以下のような特徴があります。

  • PCのようにファイルを保存できる
  • 電源OFFでもデータが保持される
  • テキストファイルやバイナリファイルを扱える

SPIFFS を使うと、秘密鍵などの機密情報を、ファイルとして分離することができ、ソースにハードコードせずに済みます。

CDKでのインフラ構築

AWS IoT Coreのリソースは、AWS CDKを使って構築しました。

CDKスタックの実装

lib/m5-co2-monitor-stack.ts に以下のコードを記述します。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iot from 'aws-cdk-lib/aws-iot';

export class M5Co2MonitorStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // IoT Thing: デバイスの論理的な表現
    const iotThing = new iot.CfnThing(this, 'M5Co2Sensor', {
      thingName: 'm5-co2-sensor',
    });

    // IoT Policy: デバイスの権限を定義
    const iotPolicy = new iot.CfnPolicy(this, 'M5Co2SensorPolicy', {
      policyName: 'm5-co2-sensor-policy',
      policyDocument: {
        Version: '2012-10-17',
        Statement: [
          {
            Effect: 'Allow',
            Action: ['iot:Connect'],
            Resource: `arn:aws:iot:${this.region}:${this.account}:client/m5-co2-sensor`,
          },
          {
            Effect: 'Allow',
            Action: ['iot:Publish'],
            Resource: `arn:aws:iot:${this.region}:${this.account}:topic/sensor/co2`,
          },
        ],
      },
    });
  }
}

デプロイが完了すると、IoT Thingと IoT Policy が作成されます。

IoT Thing

物理的なデバイス(M5StickC Plus2)の AWS 上での論理的な表現。デバイスと証明書を紐付けるための「箱」のようなものでしょうか。

IoT Policy

デバイスが何をできるかを定義する権限設定です。IAMポリシーに似ていますが、IoTデバイス専用です。センサーデータを送信するために必要です。

デバイス証明書とキーの作成

AWS IoT Coreでは、デバイス認証にX.509証明書を使用します。

aws iot create-keys-and-certificate \
  --set-as-active \
  --certificate-pem-outfile m5-co2-cert.pem \
  --public-key-outfile m5-co2-public.key \
  --private-key-outfile m5-co2-private.key
  • m5-co2-cert.pem: デバイス証明書(公開)
  • m5-co2-public.key: 公開鍵
  • m5-co2-private.key: 秘密鍵

コマンド実行時に表示される certificateArn をメモしておきます。

Amazon Root CA証明書のダウンロード

AWS IoT CoreのTLS接続には、Amazon Root CA証明書も必要です。

curl https://www.amazontrust.com/repository/AmazonRootCA1.pem -o AmazonRootCA1.pem

証明書とポリシーのアタッチ

証明書をThingとPolicyにアタッチします。

# 証明書をThingにアタッチ
aws iot attach-thing-principal \
  --thing-name m5-co2-sensor \
  --principal <certificateArn>

# ポリシーを証明書にアタッチ
aws iot attach-policy \
  --policy-name m5-co2-sensor-policy \
  --target <certificateArn>

M5StickC Plus2のプログラム実装

証明書をSPIFFSにアップロード

Arduino でスケッチを新規に作成します。

Arduino のスケッチフォルダ内に data フォルダを作成し、名前 m5_co2_aws_iot をつけて保存します。
スクリーンショット 2025-12-14 2.09.34

下記で data フォルダを作成します。

# dataフォルダを作成
mkdir ~/Documents/Arduino/m5_co2_aws_iot/data

先ほど作成した、証明書をdataフォルダにコピーします。

フォルダ構造:

m5_co2_aws_iot/
├── m5_co2_aws_iot.ino
└── data/              ← ここに証明書を配置
    ├── AmazonRootCA1.pem
    ├── m5-co2-cert.pem
    └── m5-co2-private.key

証明書とあわせて M5StickC Plus2 の WiFi 設定も SPIFFS に保存します。

# WiFi SSID
echo "your-wifi-ssid" > ~/Documents/Arduino/m5_co2_aws_iot/data/wifi_ssid.txt

# WiFi パスワード
echo "your-wifi-password" > ~/Documents/Arduino/m5_co2_aws_iot/data/wifi_password.txt

⚠️ 注意

  • your-wifi-ssid を実際のWiFi名に変更
  • your-wifi-password を実際のパスワードに変更
  • 2.4GHz WiFiのみ対応(5GHzは非対応)

最終的な data フォルダはこのようになります。
```sh
Arduino/
└── m5_co2_aws_iot/
    ├── m5_co2_aws_iot.ino
    └── data/
        ├── rootCA.pem          ← Amazon Root CA
        ├── cert.pem            ← デバイス証明書
        ├── private.key         ← 秘密鍵
        ├── wifi_ssid.txt       ← WiFi SSID
        └── wifi_password.txt   ← WiFi パスワード

SPIFFS書き込み用コードの作成

このコードは一度だけ実行します
Arduino IDE で m5_co2_aws_iot.ino へ以下のコードを貼り付けます。

#include <M5StickCPlus2.h>
#include <SPIFFS.h>

/**
 * WiFi情報と証明書を一度だけSPIFFSに書き込むコード
 */

// WiFi設定(ここに一時的に入力)
const char* wifiSSID = "YOUR_PHONE_SSID";
const char* wifiPassword = "YOUR_PHONE_PASSWORD";

// 証明書の内容
const char* rootCA = R"EOF(
-----BEGIN CERTIFICATE-----
AmazonRootCA1.pemの内容
-----END CERTIFICATE-----
)EOF";

const char* deviceCert = R"EOF(
-----BEGIN CERTIFICATE-----
m5-co2-cert.pemの内容
-----END CERTIFICATE-----
)EOF";

const char* privateKey = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
m5-co2-private.keyの内容
-----END RSA PRIVATE KEY-----
)EOF";

void setup() {
  M5.begin();
  Serial.begin(115200);

  M5.Display.fillScreen(BLACK);
  M5.Display.setTextColor(WHITE);
  M5.Display.setTextSize(1);
  M5.Display.setCursor(5, 10);
  M5.Display.println("Writing to SPIFFS...");

  Serial.println("Initializing SPIFFS...");

  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS initialization failed!");
    M5.Display.println("SPIFFS FAILED!");
    return;
  }

  Serial.println("SPIFFS initialized!");

  File file;

  // WiFi SSID
  file = SPIFFS.open("/wifi_ssid.txt", "w");
  if (file) {
    file.print(wifiSSID);
    file.close();
    Serial.println("wifi_ssid.txt written");
    M5.Display.println("WiFi SSID OK");
  }

  // WiFi Password
  file = SPIFFS.open("/wifi_password.txt", "w");
  if (file) {
    file.print(wifiPassword);
    file.close();
    Serial.println("wifi_password.txt written");
    M5.Display.println("WiFi Password OK");
  }

  // Root CA
  file = SPIFFS.open("/rootCA.pem", "w");
  if (file) {
    file.print(rootCA);
    file.close();
    Serial.println("rootCA.pem written");
    M5.Display.println("rootCA.pem OK");
  }

  // Device Certificate
  file = SPIFFS.open("/cert.pem", "w");
  if (file) {
    file.print(deviceCert);
    file.close();
    Serial.println("cert.pem written");
    M5.Display.println("cert.pem OK");
  }

  // Private Key
  file = SPIFFS.open("/private.key", "w");
  if (file) {
    file.print(privateKey);
    file.close();
    Serial.println("private.key written");
    M5.Display.println("private.key OK");
  }

  Serial.println("\nAll data written to SPIFFS!");
  M5.Display.fillScreen(GREEN);
  M5.Display.setCursor(5, 50);
  M5.Display.setTextSize(2);
  M5.Display.println("DONE!");
  M5.Display.setTextSize(1);
  M5.Display.setCursor(5, 80);
  M5.Display.println("All saved!");
  M5.Display.println("Upload main code now.");
}

void loop() {
  delay(1000);
}

このファイルをアップロードし、「DONE!」が表示されたら成功です。

メインプログラムの実装

AWS IoT Core に接続してCO2データを送信するプログラムを実装します。長いので折りたたんでいます。
Arduino IDE で下記のコードを新しいスケッチとして作成しアップロードします。

メインプログラム
/**
 * ================================================
 * M5StickC Plus2 CO2 Sensor - AWS IoT Core Integration
 * ================================================
 */

#include <M5StickCPlus2.h>
#include <Wire.h>
#include <SensirionI2cScd4x.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <SPIFFS.h>

// ================================================
// AWS IoT Core 設定
// ================================================
const char* AWS_IOT_ENDPOINT = "a2ohov8mh1yeba-ats.iot.ap-northeast-1.amazonaws.com";
const int AWS_IOT_PORT = 8883;
const char* CLIENT_ID = "m5-co2-sensor";

// ================================================
// MQTT トピック設定
// ================================================
const char* PUBLISH_TOPIC = "sensor/co2";

// ================================================
// グローバル変数
// ================================================
SensirionI2cScd4x scd4x;
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);

String wifiSSID;
String wifiPassword;

// ================================================
// ユーティリティ関数
// ================================================
String readFile(const char* path) {
  File file = SPIFFS.open(path, "r");
  if (!file) {
    Serial.print("Failed to open file: ");
    Serial.println(path);
    return "";
  }

  String content = "";
  while (file.available()) {
    content += (char)file.read();
  }
  file.close();
  content.trim();
  return content;
}

// ================================================
// WiFi 接続
// ================================================
void connectWiFi() {
  M5.Display.fillScreen(BLACK);
  M5.Display.setCursor(5, 10);
  M5.Display.setTextSize(1);
  M5.Display.println("Connecting WiFi...");

  Serial.println("\n=== WiFi Connection ===");
  Serial.print("SSID: ");
  Serial.println(wifiSSID);

  WiFi.begin(wifiSSID.c_str(), wifiPassword.c_str());

  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi Connected!");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

    M5.Display.fillScreen(BLACK);
    M5.Display.setCursor(5, 10);
    M5.Display.println("WiFi Connected!");
    M5.Display.setCursor(5, 30);
    M5.Display.print("IP: ");
    M5.Display.println(WiFi.localIP());
    delay(2000);
  } else {
    Serial.println("\nWiFi Connection Failed!");
    M5.Display.fillScreen(RED);
    M5.Display.setCursor(5, 50);
    M5.Display.println("WiFi Failed!");
    while (1) delay(1000);
  }
}

// ================================================
// AWS IoT Core 接続
// ================================================
void connectAWS() {
  Serial.println("\n=== AWS IoT Core Connection ===");
  Serial.println("Loading certificates from SPIFFS...");

  String rootCA = readFile("/rootCA.pem");
  String cert = readFile("/cert.pem");
  String privateKey = readFile("/private.key");

  if (rootCA.isEmpty() || cert.isEmpty() || privateKey.isEmpty()) {
    Serial.println("ERROR: Failed to load certificates!");
    M5.Display.fillScreen(RED);
    M5.Display.setCursor(5, 50);
    M5.Display.println("Cert Load Failed!");
    while (1) delay(1000);
  }

  Serial.println("Certificates loaded successfully");

  wifiClient.setCACert(rootCA.c_str());
  wifiClient.setCertificate(cert.c_str());
  wifiClient.setPrivateKey(privateKey.c_str());

  mqttClient.setServer(AWS_IOT_ENDPOINT, AWS_IOT_PORT);

  M5.Display.fillScreen(BLACK);
  M5.Display.setCursor(5, 10);
  M5.Display.println("Connecting AWS...");

  int attempts = 0;
  while (!mqttClient.connected() && attempts < 5) {
    Serial.print("Attempt ");
    Serial.print(attempts + 1);
    Serial.println(": Connecting to AWS IoT Core...");

    if (mqttClient.connect(CLIENT_ID)) {
      Serial.println("✓ AWS IoT Connected!");

      M5.Display.fillScreen(BLACK);
      M5.Display.setCursor(5, 10);
      M5.Display.println("AWS Connected!");
      delay(1000);
    } else {
      Serial.print("✗ Failed, rc=");
      Serial.println(mqttClient.state());

      switch (mqttClient.state()) {
        case -4: Serial.println("  → Timeout"); break;
        case -3: Serial.println("  → Connection lost"); break;
        case -2: Serial.println("  → Network failed"); break;
        case 2:  Serial.println("  → Authentication failed"); break;
        case 5:  Serial.println("  → Unauthorized"); break;
      }

      Serial.println("  Retrying in 5s...");
      delay(5000);
      attempts++;
    }
  }

  if (!mqttClient.connected()) {
    Serial.println("ERROR: AWS Connection Failed!");
    M5.Display.fillScreen(RED);
    M5.Display.setCursor(5, 50);
    M5.Display.println("AWS Failed!");
  }
}

// ================================================
// センサーデータ送信
// ================================================
void publishSensorData(uint16_t co2, float temperature, float humidity) {
  String payload = "{";
  payload += "\"co2\":" + String(co2) + ",";
  payload += "\"temperature\":" + String(temperature, 1) + ",";
  payload += "\"humidity\":" + String(humidity, 1) + ",";
  payload += "\"timestamp\":\"" + String(millis()) + "\"";
  payload += "}";

  Serial.println("\n=== Publishing Data ===");
  Serial.print("Topic: ");
  Serial.println(PUBLISH_TOPIC);
  Serial.print("Payload: ");
  Serial.println(payload);

  if (mqttClient.publish(PUBLISH_TOPIC, payload.c_str())) {
    Serial.println("✓ Published successfully!");
  } else {
    Serial.println("✗ Publish failed!");
    Serial.print("MQTT State: ");
    Serial.println(mqttClient.state());
  }
}

// ================================================
// 初期化(setup)
// ================================================
void setup() {
  M5.begin();
  Serial.begin(115200);

  M5.Display.setRotation(1);
  M5.Display.fillScreen(BLACK);
  M5.Display.setTextColor(WHITE);
  M5.Display.setTextSize(1);

  Serial.println("\n\n====================================");
  Serial.println("  M5StickC Plus2 CO2 Sensor");
  Serial.println("  AWS IoT Core Integration");
  Serial.println("====================================\n");

  Serial.println("Step 1: Initializing SPIFFS...");
  if (!SPIFFS.begin(true)) {
    Serial.println("ERROR: SPIFFS initialization failed!");
    M5.Display.fillScreen(RED);
    M5.Display.setCursor(5, 50);
    M5.Display.println("SPIFFS Failed!");
    while (1) delay(1000);
  }
  Serial.println("✓ SPIFFS initialized");

  Serial.println("\nStep 2: Loading WiFi credentials...");
  wifiSSID = readFile("/wifi_ssid.txt");
  wifiPassword = readFile("/wifi_password.txt");

  if (wifiSSID.isEmpty() || wifiPassword.isEmpty()) {
    Serial.println("ERROR: WiFi credentials not found!");
    M5.Display.fillScreen(RED);
    M5.Display.setCursor(5, 50);
    M5.Display.println("WiFi Config Missing!");
    while (1) delay(1000);
  }
  Serial.println("✓ WiFi credentials loaded");

  Serial.println("\nStep 3: Connecting to WiFi...");
  connectWiFi();

  Serial.println("\nStep 4: Connecting to AWS IoT Core...");
  connectAWS();

  Serial.println("\nStep 5: Initializing CO2 sensor...");
  Wire.begin(32, 33);
  scd4x.begin(Wire, 0x62);
  scd4x.stopPeriodicMeasurement();
  delay(500);
  scd4x.startPeriodicMeasurement();

  Serial.println("✓ CO2 sensor initialized");
  Serial.println("\nWaiting 5 seconds for first measurement...");
  delay(5000);

  Serial.println("\n====================================");
  Serial.println("  Setup Complete!");
  Serial.println("  Starting main loop...");
  Serial.println("====================================\n");
}

// ================================================
// メインループ(loop)
// ================================================
void loop() {
  if (!mqttClient.connected()) {
    Serial.println("MQTT disconnected. Reconnecting...");
    connectAWS();
  }

  mqttClient.loop();

  uint16_t co2 = 0;
  float temperature = 0.0f;
  float humidity = 0.0f;

  uint16_t error = scd4x.readMeasurement(co2, temperature, humidity);

  if (error == 0 && co2 > 0) {
    Serial.println("\n--- Sensor Data ---");
    Serial.print("CO2: ");
    Serial.print(co2);
    Serial.println(" ppm");
    Serial.print("Temperature: ");
    Serial.print(temperature, 1);
    Serial.println(" °C");
    Serial.print("Humidity: ");
    Serial.print(humidity, 1);
    Serial.println(" %");
    Serial.println("-------------------");

    publishSensorData(co2, temperature, humidity);

    M5.Display.fillScreen(BLACK);

    M5.Display.setCursor(5, 10);
    M5.Display.setTextSize(2);
    M5.Display.println("CO2 Sensor");

    M5.Display.setCursor(5, 35);
    M5.Display.setTextSize(3);
    M5.Display.print(co2);
    M5.Display.setTextSize(2);
    M5.Display.println(" ppm");

    M5.Display.setCursor(5, 70);
    M5.Display.setTextSize(2);
    M5.Display.print("Temp: ");
    M5.Display.print(temperature, 1);
    M5.Display.println(" C");

    M5.Display.setCursor(5, 95);
    M5.Display.setTextSize(2);
    M5.Display.print("Hum: ");
    M5.Display.print(humidity, 1);
    M5.Display.println(" %");

  } else {
    Serial.print("Error reading sensor: ");
    Serial.println(error);
  }

  delay(10000);
}

シリアルモニターでの確認

アップロード後、シリアルモニター(115200 baud)を開くと、以下のようなログが表示されます。
スクリーンショット 2025-12-14 0.15.43

AWS IoT Coreでの確認

AWS マネジメントコンソールから AWS IoT Core を開き、「テスト」→「MQTTテストクライアント」で sensor/co2 トピックをサブスクライブすると、デバイスから送信されたデータを確認できます。
スクリーンショット 2025-12-14 0.17.02

10秒ごとに以下のログが繰り返されます。
スクリーンショット 2025-12-14 0.17.42

おわりに

今回は、M5StickC Plus2 と CO2センサーを使って、室内の CO2 濃度をリアルタイムで AWS IoT Core に送信するシステムを作ってみました。

実は当初、受信したデータをそのまま Bedrock Agent Core に連携させてAI分析まで行う予定だったのですが、接続周りで予想以上にハマってしまい……今回は「IoT Core でのデータ表示」までをゴールとしました。次回の記事で再度挑戦しようと思います!

少し駆け足の紹介となりましたが、今回初めて AWS IoT Core に触れ、手元のデバイスから送ったデータがクラウド上で可視化された瞬間はとても嬉しかったです。今後もさまざまなデバイスやサービスの連携に挑戦していきたいと思います。

この記事をシェアする

FacebookHatena blogX

関連記事