2
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?

AWS全冠トロフィーをIoTで輝かせるスタンドを作った

Posted at

はじめに

こんにちは、ほうき星です。

皆さん、AWS 全冠トロフィーを受け取ってから、そのまま箱にしまい込んでいませんか?

このトロフィーは 2025 Japan All AWS Certifications Engineers の SWAG として、AWS Summit Japan で配布されたものです。

この手のクリスタルトロフィーは台座付きなら飾りやすいのですが、AWS 全冠トロフィーには台座がなく、立てて飾るには心もとないです。横に寝かせるとせっかくの文字が見えづらくなってしまいます。結果として「箱にしまったまま…」という方も多いのではないでしょうか。

せっかくの記念品、どうせならいい感じに飾りたいですよね。
そこで今回は、AWS 全冠トロフィーを映える形で飾れる IoT スタンドを作りましたので作り方をご紹介します。

作ったもの

作成した AWS 全冠トロフィーを飾る IoT スタンドの概要を紹介します。

  • AWS 全冠トロフィーに完全にフィットする3Dプリント製のスタンド
  • 内部にESP32を内蔵し LED を制御
  • AWS IoT Core と連携し、スマホや PC から操作

作り方

AWS IoT Core の設定

(物理ボタンで制御できた方が使い勝手良い説ありますが)
IoT Core に接続してスマホなどから遠隔操作できる IoT なスタンドにします。

モノ(Thing)を作成する

モノ(Thing)とポリシーを作成し、IoT スタンドに書き込む証明書を取得します。

  1. マネジメントコンソールにて AWS IoT > 管理 > モノ を開きます

  2. モノを作成 から1つのモノを作成します
    image.png

  3. モノのプロパティ は今回以下のようにしました

    • モノの名前all-cert-trophy-stand
    • Device Shadow名前のないシャドウ(クラシック)
      シャドウステートメントを編集brightnessを持たせています
      シャドウステートメント
      {
        "state": {
          "reported": {
            "brightness": 0
          },
          "desired": {
            "brightness": 0
          }
        }
      }
      
  4. デバイス証明書を設定 では新しい証明書を自動生成(推奨)を選択します
    image.png

  5. 証明書にポリシーをアタッチポリシーを作成から以下のようなポリシーを作成し、アタッチします

    ポリシー
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Condition": {
            "Bool": {
              "iot:Connection.Thing.IsAttached": "true"
            }
          },
          "Effect": "Allow",
          "Action": "iot:Connect",
          "Resource": "arn:aws:iot:<リージョン>:<AWSアカウントID>:client/${iot:Connection.Thing.ThingName}"
        },
        {
          "Effect": "Allow",
          "Action": [
            "iot:Publish",
            "iot:Receive"
          ],
          "Resource": "arn:aws:iot:<リージョン>:<AWSアカウントID>:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*"
        },
        {
          "Effect": "Allow",
          "Action": "iot:Subscribe",
          "Resource": "arn:aws:iot:<リージョン>:<AWSアカウントID>:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*"
        }
      ]
    }
    

    上記ポリシーは ポリシー変数 を利用しています。
    ポリシー変数についてはこちらの記事: [AWS IoT] ポリシー変数で複数のモノをセキュアに管理してみた で詳しく解説されていますのでご確認ください。

  6. モノを作成 を押下すると証明書とキーをダウンロードするモーダルが表示されるので、すべてダウンロードしておきます
    image.png

補足

IoT ポリシー とは

IoT ポリシーはデバイスに付与するアクセス許可(IAMポリシーのようなもの)です。
モノは証明書を使用し IoT Core に接続しますが、その証明書にポリシーをアタッチして「何を許可するか」を定義します。
IoT ポリシーで定義できるアクションやリソース指定の方法等は以下ドキュメントを参考にしてください。

Device Shadow とは

Device Shadow は IoT Core 上に保存されるモノの状態です。
モノがオフラインでも、最新の状態を IoT Core 上に保持したり、あるいはモノのあるべき状態を設定して、オンライン時に同期させることができます。

Device Shadow の設定では以下の2つのフィールドを設定できます。

  • desired:モノのあるべき状態
  • reported:モノの現在の状態

desired と reported に差がある場合、delta フィールドが生成され差分を取得することができます。
詳しくは以下ドキュメントを参考にしてください。

また、Device Shadow は MQTT を使用して取得することができます。
現在の Device Shadow を get(取得) することができるほか、差分(delta)が発生したタイミングにメッセージを受取ることもできます。
こちらも詳しくは以下ドキュメントを参考にしてください。

ESP32と電子工作

IoT スタンドには Seeed Studio XIAO ESP32S3 と テープLED を組み込み、IoT Core への接続と LED の制御を行います。

  • Seeed Studio XIAO ESP32S3 は秋月電子等で購入するのが、技適などの面でも安心です
  • Nch MOSFET は同じく秋月電子で購入可能な 2N7000 を選定しました
  • テープLEDは COB タイプで白色のものを選定しました

配線と電子工作

ESP32S3 と COBテープLED、Nch MOSFET は以下のような配線としています。

  • ESP32S3の5Vピンを COBテープLED の+端子と接続
  • COBテープLED の-端子を Nch MOSFET のドレインと接続
  • Nch MOSFET のソースは ESP32S3 の GND へ接続
  • Nch MOSFET のゲートは ESP32S3 のGPIO4 と接続し、プルダウン抵抗として22kΩを入れています

参考:ESP32S3 のピン配置

配線図

配線図.png

実際の配線

image.png

プログラム書き込み

今回 ESP32S3 の制御には MicroPython を使用しました。
以下のプログラムと IoT Core にてモノ(Thing)を作成した時にダウンロードした証明書を書き込みます。

ESP32S3 への MicroPython の導入やThonnyを使用したプログラムの書き込みは、こちらのドキュメント:MicroPython のインストールをご確認ください。

フォルダ構成図

書き込むプログラムと証明書の階層は以下の通りです。

ESP32S3(root)/
├── certs/
│   ├── certificate.der ※certificate.pem.crtをDER形式に変換したもの
│   ├── private.der ※private.pem.keyをDER形式に変換したもの
│   └── routeca.der ※AmazonRootCA1.pemをDER形式に変換したもの
├── lib/
│   └── umqtt/
│       └── simple.py
└── main.py

MicroPython での MQTT の利用には umqtt.simple ライブラリを利用します。
こちらのgitリポジトリ:umqtt.simpleから入手してください。

モノ(Thing)を作成した際にダウンロードした証明書は umqtt.simple での利用にあたりDER形式に変換してください。
PEM形式からDER形式への変換にはこちらの記事:TLS/SSL証明書のファイル形式(PEMとDER)をご確認ください。

プログラム本体

main.py
import gc
import time
import json
from network import WLAN, STA_IF
from machine import Pin, PWM, reset
from ssl import SSLContext, PROTOCOL_TLS_CLIENT
from asyncio import run, Loop, sleep_ms, get_event_loop

from umqtt.simple import MQTTClient

class LED:
    """LEDをPWM制御するクラス
    """

    FREQUENCY: int = 100

    def __init__(self) -> None:
        self._brightness: int = 0
        self._leds: list[PWM] = [
            PWM(Pin(4), freq=self.FREQUENCY),
        ]
        self._set_duty(0)

    def _clamp(self, value: int | float, min_value: int = 0, max_value: int = 100) -> int:
        return int(max(min_value, min(max_value, value)))

    def _set_duty(self, duty: int) -> None:
        for led in self._leds:
            led.duty_u16(int(65535 * self._clamp(duty) / 100))

    def get_brightness(self) -> int:
        return self._brightness

    def set_brightness(self, brightness: int) -> None:
        self._brightness = self._clamp(brightness)

    async def routine(self): # -> Corutine
        while True:
            self._set_duty(self._brightness)
            await sleep_ms(100)

class AllCertTrophyStand:
    """AWS IoT Coreと接続して、Device Shadowを操作するクラス
    """

    SSID: str = "Your WiFi SSID"
    PASSWORD: str = "Your WiFi Password"

    THING_NAME: str = "all-cert-trophy-stand"
    IOT_CORE_ENDPOINT: str = "Your AWS IoT Core Endpoint"
    HEARTBEAT_INTERVAL: int = 5

    def __init__(self) -> None:
        self._led: LED = LED()

    def _connect_wifi(self) -> None:
        wlan: WLAN = WLAN(STA_IF)
        wlan.active(True)

        if not wlan.isconnected():
            print("connecting to wifi...")
            wlan.connect(self.SSID, self.PASSWORD)
            while not wlan.isconnected():
                pass
        print("connected to wifi:", wlan.ifconfig())

    def _connect_iot_core(self) -> None:
        ssl_context: SSLContext = SSLContext(PROTOCOL_TLS_CLIENT)
        ssl_context.load_cert_chain("/certs/certificate.der", "/certs/private.der")
        self.mqtt_client: MQTTClient = MQTTClient(
            self.THING_NAME,
            self.IOT_CORE_ENDPOINT,
            keepalive=self.HEARTBEAT_INTERVAL * 3,
            ssl=ssl_context
        )
        self.mqtt_client.connect()
        print("connected to iot core")

    def _report_device_shadow(self, brightness: int) -> None:
        shadow: dict = {
            "brightness": brightness
        }
        self.mqtt_client.publish(
            f"$aws/things/{self.THING_NAME}/shadow/update",
            json.dumps({
                "state": {
                    "reported": shadow
                }
            }).encode()
        )

    def _subscribe_shadow_update_delta(self) -> None:
        def set_brightness(topic: bytes, message: bytes) -> None:
            delta: dict = json.loads(message.decode())["state"]
            if "brightness" in delta:
                brightness: int = delta["brightness"]
                self._led.set_brightness(brightness)
            else:
                brightness: int = self._led.get_brightness()
            self._report_device_shadow(brightness)
        self.mqtt_client.set_callback(set_brightness)
        self.mqtt_client.subscribe(f"$aws/things/{self.THING_NAME}/shadow/update/delta")
        self._report_device_shadow(0)

    async def _start_routine(self):  # -> coroutine
        def handle_exception(loop: Loop, context: dict) -> None:
            print(f"loop error! reason: {context['exception']}")
            time.sleep(1)
            reset()
        loop: Loop = get_event_loop()
        loop.set_exception_handler(handle_exception)
        loop.create_task(self._check_msg())
        loop.create_task(self._ping())
        loop.create_task(self._led.routine())
        loop.run_forever()

    async def _check_msg(self):  # -> Coroutine
        while True:
            gc.collect()
            await sleep_ms(50)
            self.mqtt_client.check_msg()

    async def _ping(self):  # -> Coroutine
        while True:
            gc.collect()
            await sleep_ms(self.HEARTBEAT_INTERVAL * 1000)
            self.mqtt_client.ping()

    def execute(self) -> None:
        self._connect_wifi()
        self._connect_iot_core()
        self._subscribe_shadow_update_delta()
        run(self._start_routine())

if __name__ == "__main__":
    AllCertTrophyStand().execute()

SSID や PASSWORD、IOT_CORE_ENDPOINT を自身の環境に合わせて書き換えてください。
エンドポイントはAWS IoT > 接続 > ドメイン設定ドメイン名から確認できます。

動作確認

TypeCケーブルを接続し、モノ(Thing)の Devoce shadow の desired フィールドのbrightness を100に設定し、LED が点灯すれば正常に動作しています。
image.png

スタンドの組み立て

筐体の印刷

一般のご家庭にある3Dプリンタを使用して筐体を印刷します。
STLデータは以下に置いていますので、個人利用の範囲でご自由にご利用ください。

image.jpg

組み立て

印刷した筐体と ESP32(とLED等)を組み立てます。

  1. COBテープLED をベースプレートに貼り付け、ESP32S3 も IC 面を下にして載せます
    image.png

  2. 固定カバーで M2皿ネジ で取り付け ESP32S3 を固定します
    image.png

  3. 最後にベースプレートをスタンド本体に M2皿ネジ で取り付ければ完成です
    image.png

さいごに

本記事では AWS 全冠トロフィーを展示するための IoT スタンドの作成をご紹介しました。

今回は IoT スタンドの作成まででしたが、EventBridgeスケジュール等を使用して定時にオン/オフすると少し実用的になると思います。
また、作ったものに記載したXのポストのように、S3 Hosting 等と組み合わせてスマホからオン/オフできるようにすると、さらに使い勝手が良くなりますので皆さんもお好みの操作UIを構築してもらえればと思います。

この記事が皆さんの「箱にしまったまま」の AWS 全冠トロフィーの活用の一助になれば幸いです。

関連リンク

2
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
2
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?