☃️

今年もクリスマスイルミネーションを作った話

2022/12/25に公開

この記事は,あくあたん工房 Advent Calendar 2022 22日目の記事です.21日目は「OCamlとアセンブリ言語から末尾再帰最適化を読み解く」でした.

ハードウェア編はこちらをご覧ください: KITクリスマス2022 イルミネーションの技術的な話 マイコン編

クリスマスの輝き

今年も12月がやってきました.コートとマフラーを用意して見に行くものといえば,クリスマスイルミネーションですね!
というわけで,昨年に引き続きTwitterと連携する"インタラクティブ"イルミネーションを作ってみました.

今年は,去年の数倍のLEDを準備したり,サンタの形をしたモチーフライトを調達するなどして去年よりもいい出来栄えになったと思います.

どのようなイルミネーションだったかはツイートを見てもらうことにして,この記事では主にバックエンドと管理用フロントエンドのことを話します.

全体的な構成

前回と同じような構成になっています.違う点は,Google CloudからAWSになったこと,OTA機能が増えたことくらいです.また,図には表れていませんがフロントエンドを作り直しています.
Xmas 2022 Structure

なお,今回はソースコードを公開しています.
https://github.com/StudioAquatan/xmas-api
https://github.com/StudioAquatan/xmas-frontend

Google Cloud → AWSへの乗り換え

Google Cloud IoT Coreが来年度6月でサービス終了となることに伴ってAWS IoT Coreに乗り換えています.

乗り換え自体は簡単,というわけではなくて,

  • Google CloudのAPIとAWS IoT CoreのAPIの使い方が違う
    • regionごとにエンドポイントがあるというの,GCPからすると意外だった
  • 提供する機能の違い
    • デバイスの接続状況は,AWSだとフリートインデックスのsearchIndex APIから取らないといけない
    • GCPとデバイス固有の状態を持つ方法が違う(Device Shadow)

のような混乱もありました.特に,フリートインデックスは取得するのに検索条件が必要で*(ワイルドカード)で対応する必要がある,というのは最初迷いました.最終的にデバイスを取得するコードは以下のような感じになっています.

const thingsResponse = await this.coreClient.searchIndex({
  indexName: 'AWS_Things',
  queryString: 'thingName:*',
});

バックエンド

バックエンドはTypeScriptとExpressというよくあるスタックで書かれています.FaaS等を利用してもいいのですが,Twitterと常時接続したり30秒と短い周期でイルミネーションを点灯させたりするので,今回は古典的にサーバにデプロイしています.

Twitter連携

Twitterは,通知を取得するためにAccount Activity APIを利用しています.Account Activity APIは,いいね・RTなどの反応を数秒の遅延でリアルタイムに集計するもので,今回のイルミネーションに最適です.
今年も昨年と同じく,twitter-api-v2twictを利用してAPIにアクセスしています.

Twitter APIのOAuthでの連携は,Webアプリからイルミネーション用APIを利用する時の認証としても機能しています.これには,(古いですが)passport-twitterconnect-typeormを使うことで,少ないコードで認証や管理を完結させています.

イルミネーション点灯!

イルミネーションの点灯にはいくつかパターンがあり,ツイートのインプレッションに応じて切り替わるようになっています.これは,ハードコーディングではなく,

  • 反応するツイート・インプレッションをみるツイート
  • インプレッションの条件(いいね数,RT数など)
    を含むルールをDBに保管して,それを基に行っています.ルールは後述の管理画面で編集できるようになっています.もし複数のルールが反応したときはランダムにどれかを選ぶようになっているので,RTするごとに違う光り方になるような動きを実現できます.

デプロイ

1回しか使わないので,EC2に手動でデプロイしています.ただ割と余裕があったので,DockerでのデプロイやTerraform等を使ってみても面白いかもしれません.K8sはさすがにオーバーキルかな?

管理用フロントエンド

今回は管理用フロントエンドをReactで作り直しています.UIはChakra UIを使っています.またAPIを叩くレイヤーとしてのswrも使っています.

機能としては,イルミネーションのデバイス管理や点灯の条件管理を行うことができます.

useStateと再レンダリング

最初,管理画面はそこまで多くのものを表示するつもりは無かった(個人の感想)ので,useStateを橋渡ししていくだけの単純な構造で作っていました.この時点でお察しの方もいるかもしれませんが,これはコンポーネントの再レンダリングを多発させてパフォーマンスを大幅に低下させます.
大丈夫だろうと思っていたのですが,それは最初のほうだけで,点灯のルール設定の画面でやはり目に見えて動作が遅くなるということになりました.

その時の様子がこちら

画面全体が再レンダリングされているのが分かります.
これを改善する方法は単純で,stateの更新が伝搬するコンポーネントを少なくすれば改善されます.
そのための手法としてReactで完結させるのであればuseContextなどがありますが,今回はrecoilを使いました.
具体的には,設定画面の要素ごとのatomを作り,更に設定した結果をバックエンドに送信する時の形式にした(オブジェクトを結合しただけですが)selectorを用意しました.selectorにsetterを用意することで,バックエンドで保持している値をUIに反映することもできます.途中からの導入なので,これが一番手っ取り早い方法でした.
ただし,そのおかげでuseRecoilStateが大量にコンポーネントに登場していて,利用箇所が分かりにくくなっています.これは後から改修するのにこまるので,どこかでリファクタリングする必要がありそうです.

結果として,再レンダリングは変更した値のコンポーネントのみになり,動作も軽量になりました.

終わりに

バックエンド・フロントエンドともにある程度の完成度のものにできたとは思います.
あとはハードウェアとしてのイルミネーションのほうですが,購入したLEDに対して場所が少ないとか設置方法を考えてなかったとか,いろいろ問題があったので来年はこれを改善したいところです.また,もっと大規模にイルミネーションを飾ったりして盛り上げていきたい所存です.

Discussion