7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ATOM S3 LiteでAWS IoT Core連携リモートスピーカーシステムを作ってみた

Last updated at Posted at 2025-08-02

この記事は「2025 Japan AWS Jr. Chanpions 夏のQiitaリレー」の31日目の記事です!!
宣伝と過去の投稿(リンク集)はこちら👇

はじめに

スケーラブルなリモートスピーカーシステムを作りたいと思い、ATOM S3 LiteとAWS IoT Coreを組み合わせてみました。イマーシブシアターの小道具や空間演出での利用を想定しています。

この記事で実現すること

  • AWS IoT Core経由でのリモート音声再生
  • 複数デバイスへの拡張可能なアーキテクチャ

イマーシブシアターについて知りたい方はこちら

使用機材・サービス

ハードウェア

  • ATOM S3 Lite:ESP32-S3ベースの小型マイコン
  • ATOM SPK:専用スピーカーユニット

開発環境

  • PlatformIO IDE(VS Code拡張)
  • macOSでの開発

AWS環境

  • AWS IoT Core:MQTT通信とデバイス管理
  • Amazon S3:音声ファイルホスティング
  • AWS Lambda:制御ロジック

作成した構成

Lambda.png

  1. AWS Lambda が IoT Core に {cmd, url} を Publish
  2. Atom S3+SPK Base が MQTT トピックを Subscribe → コマンド受信
  3. Atom S3 が AWS S3 へ HTTP GET リクエスト
  4. AWS S3 から MP3 データを Atom S3 に Streaming で返送
  5. 内蔵スピーカーへ出力

<ポイント>
Lambdaによるプログラム制御のおかげで、
1台のPCから、複数のスピーカーに、違う指示を送ることができる!!

開発環境のセットアップ

PlatformIO IDEの導入

Arduino IDEでも開発可能らしいですが、VS Codeに慣れているのでPlatformIO IDEを選びました。

初期設定はこちらのブログを参考に進めました。

今回は、S3 liteを使っているのでAtom S3を選択しプロジェクトを作成しました。
1753270966209.png

プロジェクト設定

ポートの確認(mac terminalで実行)

ls /dev/cu.usb*
# → /dev/cu.usbmodem101 が表示された

platformio.iniの設定内容

[env:m5stack-atoms3]
platform = espressif32
board = m5stack-atoms3
framework = arduino
upload_port = /dev/cu.usbmodem101
upload_speed = 1500000
monitor_speed = 115200

; --- USB 関連 --------------------------------------------------
build_flags =
  -DARDUINO_USB_MODE=1          ; 0=JTAG, 1=CDC, 2=MSC+CDC
  -DARDUINO_USB_CDC_ON_BOOT=1   ; ← ← ← ここが正しいスイッチ
  -DBOARD_HAS_PSRAM          ; ← ESP32‑S3 の PSRAM 8 MB を有効に
  -mfix-esp32-psram-cache-issue

lib_deps =
  m5stack/M5Unified@
  m5stack/M5Atomic-EchoBase@^1
  https://github.com/schreibfaul1/ESP32-audioI2S.git#2.0.6
  bblanchon/ArduinoJson
  FastLED
  WiFi
  knolleary/PubSubClient@^2.8

重要

  • ARDUINO_USB_CDC_ON_BOOT=1の設定がないとSerialの出力がモニタに表示されません。
  • ESP32-audioI2Sはバージョン3だと動作しないため、2.0.6を指定しています。

AWS IoT Coreの設定

デバイス登録

  1. Thing(モノ)の作成

    • AWS IoT Coreコンソールで「モノを作成」
    • 証明書は「自動生成」を選択
  2. 証明書のダウンロード

    • デバイス証明書(xxx-certificate.pem.crt
    • プライベートキー(xxx-private.pem.key
    • ルートCA証明書(AmazonRootCA1.pem
  3. ポリシーの作成と紐付け
    AWS IoT > 管理 > セキュリティ > ポリシー で作成する。

     {
       "Version": "2012-10-17",
       "Statement": [
         {
           "Effect": "Allow",
           "Action": [
             "iot:Connect",
             "iot:Publish",
             "iot:Receive",
             "iot:Subscribe"
           ],
           "Resource": "arn:aws:iot:*:*:*"
         }
       ]
     }
    

証明書の埋め込み

src/certs.hファイルを作成し、ダウンロードした証明書を埋め込みます。

#pragma once
static const char AWS_ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
(AmazonRootCA1.pemの内容)
-----END CERTIFICATE-----
)EOF";

static const char CLIENT_CERT[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
(xxx-certificate.pem.crtの内容)
-----END CERTIFICATE-----
)EOF";

static const char CLIENT_KEY[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
(xxx-private.pem.keyの内容)
-----END RSA PRIVATE KEY-----
)EOF";

注意
ファイル内容をコピーする際は、PEM本文のみを貼り付けてください。

ファームウェア実装

メインコード

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <FastLED.h>
#include <Audio.h>
#include <ArduinoJson.h>
#include "certs.h"

/* Wi‑Fi */
const char* WIFI_SSID = "XXXXXXXXXXXX";
const char* WIFI_PASS = "XXXXXXXXXXXX";

/* AWS IoT Core */
const char* AWS_EP = "XXXXXXXXXXXXXXXXXXXXXXXXx";
const char* SUB_TOPIC = "music/control";

/* LED */
#define LED_PIN   35
#define PIXEL_PWR 21
CRGB leds[1];

/* SPK AMP enable (ATOM SPK v2 = GPIO25, v1=26) */
#define AMP_EN 25               // ★ 追加

/* MQTT */
WiFiClientSecure net;
PubSubClient     mqtt(net);

/* Audio */
Audio audio;

/* ---------- util ------------------------------------------------ */
void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  for (int i = 0; i < 100 && WiFi.status() != WL_CONNECTED; ++i) delay(100);
  Serial.println(WiFi.status() == WL_CONNECTED ?
                 "WiFi OK  IP=" + WiFi.localIP().toString() : "WiFi FAIL");
}

void connectAWS() {
  net.setCACert(AWS_ROOT_CA);
  net.setCertificate(CLIENT_CERT);
  net.setPrivateKey(CLIENT_KEY);

  mqtt.setServer(AWS_EP, 8883);
  mqtt.setCallback([](char* topic, byte* payload, unsigned len) {
    StaticJsonDocument<256> doc;
    deserializeJson(doc, payload, len);

    const char* cmd = doc["cmd"] | "";
    if (!strcmp(cmd, "led")) {                       // LED 制御
      const char* c = doc["color"] | "off";
      if      (!strcmp(c,"red"))   leds[0] = CRGB::Red;
      else if (!strcmp(c,"blue"))  leds[0] = CRGB::Blue;
      else if (!strcmp(c,"green")) leds[0] = CRGB::Green;
      else                         leds[0] = CRGB::Black;
      FastLED.show();
    }
    else if (!strcmp(cmd, "play")) {                 // 曲再生
      const char* url = doc["url"] | "";
      if (*url) audio.connecttohost(url);
    }
    else if (!strcmp(cmd, "stop")) {                 // 再生停止
      audio.stopSong();                              // ← ここで止める
    }
    Serial.printf("RX %s: %.*s\n", topic, len, payload);
  });

  while (!mqtt.connected()) {
    if (mqtt.connect("AtomS3Lite")) {
      mqtt.subscribe(SUB_TOPIC);
      Serial.println("MQTT connected ✓");
    } else {
      Serial.printf("MQTT err %d\n", mqtt.state());
      delay(2000);
    }
  }
}
/* ---------------------------------------------------------------- */
void setup() {
  pinMode(PIXEL_PWR, OUTPUT); digitalWrite(PIXEL_PWR, HIGH);
  pinMode(AMP_EN  , OUTPUT);  digitalWrite(AMP_EN, LOW);   // ★ 初期はミュート

  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, 1);
  FastLED.setBrightness(40);

  Serial.begin(115200);
  delay(1500);

  Serial.println("Boot OK");
  connectWiFi();
  if (WiFi.status() != WL_CONNECTED) return;   // Wi‑Fi失敗時は止める
  connectAWS();

  /* ---- I²S 設定 ---- */
  audio.setPinout(5, 39, 38);  // BCLK, LRCK, DATA
  audio.setVolume(18);
  audio.setBufsize(32*1024, 64*1024); 
  /*-------------------*/
}

void loop() {
  mqtt.loop();
  audio.loop();
}

制御コマンド形式

JSON形式でコマンドを送信すれば動くようになっています

// LED制御(テスト用)
{"cmd":"led","color":"blue"}

// 音声再生
{"cmd":"play","url":"http://XXXXXXX/audio.mp3"}

// 音声停止
{"cmd":"stop"}

音声ファイル配信システム

S3バケットの設定

音声ファイルをS3に配置し、静的ウェブサイトホスティングを有効にします。
こちらを参考にしました。

httpsにしたい場合は、cloud frontを利用すればできます。こちらが参考になります。

セキュリティ注意
本実装では簡易化のためバケットを公開していますが、本番環境ではCloudFrontや署名付きURLの利用を推奨します。

バケットポリシー例

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket/*"
    }
  ]
}

Lambda制御システム

デバイス制御用のLambda関数を作成します

import json
import os
import boto3

IOT_ENDPOINT = os.environ["IOT_ENDPOINT"]
TOPIC        = os.environ["IOT_TOPIC"]
client = boto3.client("iot-data", endpoint_url=f"https://{IOT_ENDPOINT}")

def lambda_handler(event, context):
    cmd   = event.get("cmd", "")
    url   = event.get("url")
    color = event.get("color")
    
    payload = {"cmd": cmd}
    if url:
        payload["url"] = url
    if color:
        payload["color"] = color

    resp = client.publish(
        topic=TOPIC,
        qos=0,
        payload=json.dumps(payload)
    )
    
    return {
        "statusCode": 200,
        "body": json.dumps({"published": payload})
    }

環境変数設定:

  • IOT_ENDPOINT: AWS IoTのエンドポイント
  • IOT_TOPIC: music/controlはAWS IoTと一致させてください

エンドポイントは、ドメイン設定から取得できます!

動作確認と結果

テスト実行

Lambdaのテスト実行で次のJSONを送信する

{"cmd":"led","color":"red"}
{"cmd":"play","url":"http://XXXXXXXXXXXXXX/audio.mp3"}
{"cmd":"stop"}

無事に音が鳴ります!!!嬉しいですね!!

実現できたこと

  • リモートでの音声制御
  • 低遅延での音声再生(体感では遅延はほとんど感じられない)
  • WiFi経由での安定した通信
  • 小型・軽量で電池駆動可能

課題と今後の改善点

現在の課題

  1. 音質の問題

    • イヤホンジャック経由でノイズが発生
    • メインスピーカーとしては音質が不十分
  2. セキュリティ

    • S3バケットの公開設定
    • 証明書・Wifiパスワードがハードコーディング

今後の改善案

  1. 音質向上

    • イヤホンジャックから音声を出す
  2. セキュリティ強化

    • CloudFront + Signed URLの利用
    • 証明書の外部保存
    • デバイス認証の強化
  3. 機能拡張

    • 複数スピーカーを同時にコントロール
    • 音量調整機能

まとめ

ATOM S3 LiteとAWS IoT Coreを組み合わせることで、低遅延でスケーラブルなリモートスピーカーシステムを構築できました。

本取り組みでできたこと・わかったこと

  • 小型で軽量な電池駆動スピーカー
  • AWS IoTによる柔軟な制御システム
  • 複数デバイスへの拡張可能性
  • イマーシブシアターなどの空間演出への応用可能性

メインスピーカーとしては音質面で課題がありますが、空間演出や小道具としては十分な性能を発揮します。小型スピーカーを部屋のあちこちに仕込むことで、正しい場所から正しく音を鳴らせることができます。適切な場所から音が鳴る(=音が聞こえる)ことで、より没入感のある体験を創出できそうです。

コスト面でのメリット

  • ATOM S3 Lite + ATOM SPEAKER: 約3,000円程度
  • AWS IoT Core: 少量使用なら月数円程度
  • 市販のWiFiスピーカーと比較して大幅にコストダウン

音響技術でのメリット

  • 1台のPCから同時に複数のスピーカーを制御可能になった
  • PCからの配線が不要になった(不安定なBlueToothではなくWifiを利用できる)
  • 小さいのでちょっとした隙間に仕込みやすい

今後は音質改善、スピーカー同時制御に取り組み、よりクオリティの高い音響システムを目指していきます。

余談

初めてIoT触りました。購入からドッキドキです。
そんな超初心者でも、GPT先生が手順をほぼ完璧に教えてくれたので作れました!感謝!!
みなさんもGPT先生に聞いて、やりたいことを挑戦してみてください!!!

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?