アンペルメンヒェンは東ドイツの人気のシンボルで、街角のあらゆる角の歩行者用信号機に表示されています。彼らのデザインにインスピレーションを得た、ベルリンを拠点とする小売チェーンもあります。
フリーマーケットで中古のアンペルマン信号のセットを見つけたので、携帯電話から制御したいと思いました。同じことをしたい場合は、読み続けてください。
健康に関する警告:このプロジェクトは 220V の主電源を使用します。私は電気技師ではありません。ご自身の責任でこれらの指示に従ってください。
ベルリンの美学を備えた、家を訪れる人が楽しく交流できるようなものを作りたかったのです。残念ながら今年は来場者数が大幅に減少してしまいましたが、2021年は大きな反響を期待して…?
ローカル ネットワーク上の各デバイスには、独自のプライベート IP アドレス (例: 192.168.1.20
) があります。 FritzBox などの一部のルーターでは、ローカル ホスト名で参照することもできるため、 mydevice.fritz.box
で192.168.1.20
にもアクセスできます。信号機の場合、デバイスのホスト名はtraffic-light
なので、 http://traffic-light.fritz.box
でアクセスできます。
Web アプリは、非常にシンプルな応答性の高い単一ページ アプリケーションです。それは以下を示します:
コードは /webapp ディレクトリの下にあります。 CSS 変換、WebSocket、XHR などの標準ブラウザー機能にのみ依存するため、外部依存関係は必要ありません。ここでは実行中のアプリケーションをプレビューできますが、もちろん私の LAN 上にはいないので何も制御しません。ローカル ネットワーク ( http://traffic-light.fritz.box
など) からアクセスした場合は、完全に機能します。
ページをロードすると、アプリケーションは GET リクエストを発行して/api/status
で現在のステータスを確認し、ポート81
でサーバーへの WebSocket 接続を開きます。複数のクライアントの同期を保つために、後続のステータス更新は常に WebSocket 経由で行われます。 WebSocket イベントが到着するたびに、単一のグローバルstate
オブジェクトに変更を適用します。その後すぐに、 updateScreen()
メソッドがこれらの変更を DOM に適用します。
起動時に、ユーザーがモバイル デバイスを使用しているかデスクトップ デバイスを使用しているかを検出し、タッチ イベントまたはクリック イベントを処理します。 iPhone X ではこれがより確実に実行されるため、実際にはtouchend
イベントを使用してサーバーにコマンドを送信します。画面の下から上にスワイプして Safari を終了すると、 touchstart
イベントが発生し、アプリをオンにしないと終了できなくなりました。緑の光!
最後に、可能な限りサーバーの負荷を軽減したいと考えています。 ESP8266 は80MHzプロセッサ上で実行されており、RAM は約50kBしかありません。それは頑丈なデバイスではありません。したがって、ブラウザが非アクティブなときは、WebSocket を切断します。タブまたはブラウザが再度開くと、ステータスを再度確認し、WebSocket に再接続します。
ESP8266 は API リクエストとタイミング コードの処理で忙しいため、Web アプリ自体を提供するために必要なリソースがありません。また、更新を適用するたびにハードウェアに物理的に接続する必要がある場合、Web アプリに表面的な変更を加えるのは困難です。
Web アプリのindex.html は、すべてを Javascript でレンダリングするという単一ページ アプリケーションの原則に従っており、HTML コンテンツ自体は非常に小さくなります。 550バイトくらい小さいです。他のものはすべてクライアントのブラウザによってロードされ、サーバーをさらに呼び出す必要はありません。したがって、Web アプリは実際には、無料の静的サイト ホスティング ツールである GitHub Pages で全体がホストされています。 /index.html
を押すと、実際には GitHub ページへのプロキシ リクエストが行われ、結果がクライアント ブラウザに返されます。
これで、Web アプリ内で何かを変更できるようになり、サーバーは影響を受けません。素晴らしい!まあ、ほぼ...
この Web アプリのコードのほとんどは、 index.html
自体ではなく、CSS および JS ファイルにあります。ブラウザは、ロードされたファイルを再要求するまで、一定期間キャッシュします。 Index.html は変更されないが、新しい JS バージョンをデプロイした場合、クライアントは新しい JS バージョンをロードする必要があることをどのようにして知るのでしょうか?
コードの新しいバージョンを git master
ブランチにプッシュすると、GitHub アクションが実行され、ページが実際に公開される GitHub Pages へのデプロイメントが実行されます。ここでの秘訣は、 index.html
内の独自の CSS ファイルと JS ファイルの末尾にサフィックス?version=latest
追加することです。コンテンツをgh-pages
ブランチにコピーする前に、アクションはコマンドsed
使用して、その「 latest
」を変数$GITHUB_SHA
の値 (実際にはmaster
ブランチの最後のコミット ID) に置き換えます。 (例: b43200422c4f5da6dd70676456737e5af46cb825
のような値)。
次にクライアントが Web アプリにアクセスすると、ブラウザーは?version=
後に新しい別の値を表示し、まだキャッシュされていない新しい更新された JS または CSS ファイルを要求します。
これが実際にどのように機能するかについてはtraffic-light-controller.ino
のsetup(void)
メソッドと Arduino コードのセクションを参照してください。
REST と WebSocket の両方を併用することにしました。 REST は主にクライアントがサーバーを制御するために使用します。 WebSocket は、ステータス情報をクライアントにブロードキャストするために使用されます。 Postman のように REST API を簡単に実験できるツールがたくさんあるので、私はこれの方が便利だと感じました。
HTTP API:こちらの Swagger ドキュメントを参照してください。
WebSocket API: WebSocket 接続は、Web アプリが内部状態を更新するために使用する JSON BLOB を送信します。 WebSocket イベントには、更新する 1 つ以上のフィールドを含めることができます。環境情報を含む例は次のようになります。
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
現在、WebSocket 経由でクライアントからサーバーにデータが送信されることはありませんが、送信することは可能です。
Arduino コードはすべて、説明コメントを含む 1 つのファイル内にあります。
これは、ピンの位置、ライブラリのインポート、HTTP コンテンツ タイプや応答コード値などのハードコードされた値の一連の定義から始まります。これに続くのは、実行時に変更される変数のセットで、すべて先頭にアンダースコアが付いています。 Web サーバー、Web ソケット サーバー、WiFi クライアント、温度センサーなど、いくつかのオブジェクトもここで初期化されます。 「システム クロック」は、 _currentMillis
フィールドによって維持されます。
起動後、 setup(void)
メソッドが実行されます。ピンのセットアップを行った後、REST エンドポイントに必要なマッピングを作成し、クライアント要求をリッスンするサーバーを起動します。他のすべては、 loop(void)
メソッドが担当します。各サイクルで保留中の Web リクエストを処理し、リズム サイクルを更新し、必要に応じてセンサーを読み取ります。パーティー モードの場合は、現在のフラッシュ/パルス状態が設定されます。
リズム (パーティー モード用) は、 RHYTHM_PATTERN
フィールドのシーケンスを再生するようにハードコードされていますが、理論的には、実行時に他のものに変更できます。 rhythm()
メソッドを呼び出すたびに、現在の_bpm
と_currentMillis
値を使用して、パターン内のどの位置にあるべきかを計算します。これは_rhythmStep
フィールドに保存されます。
リズムパターン中に、両方のリレーが実際にオフになる期間があります。ただし、ライトは白熱電球なので、すぐに発光したり消えたりするわけではありません。電球が完全にオンまたはオフになるまでに約 1.7 秒かかるようです。したがって、パターン内に両方のスイッチがオフになる期間を追加することで、電球が暖まり、冷めるときに穏やかなパルスパターンが得られます。
partyFlash()
メソッドでは、現在表示されている (または両方がオフになっている) パターン項目を取得し、適切なパラメーターを指定してlightSwitch(...)
を呼び出します。次に、 lightSwitch(...)
はsendToWebSocketClients(...)
を呼び出し、接続されているすべてのクライアントが新しい状態に更新されます。
ユーザーがライトの 1 つをクリックするだけでオンまたはオフにする場合、プロセスは似ていますが、REST リクエストとして処理されます。 handleX
メソッドの 1 つが呼び出され、リクエストが検証され、次にlightSwitch(...)
が呼び出されます。
より低い間隔で 2 つのエンクロージャの温度をチェックし、これを WebSocket 経由ですべてのクライアントに送信します。これは現在情報提供のみを目的として使用されていますが、温度が一定の安全限界を超えたときにライトを無効にするために使用される可能性があります。
この記事のレビューに協力してくれた @mrcosta の功績です。