数年前に作成したAlexaスキル「アレクサ、部屋の温度を教えて」を実現のための第8歩になります。
その時は、AWS のLambda関数に事前に調べたルータのグローバルIPアドレスを書き込む方法で開発しました。(Blynk local server を使うとAlexaが答えてくれない)
Blynkが答えるためには、ルータにぶら下がるBlynk Local serverが外部インターネットから見えてる必要があります。
このときの問題は、ルータが再起動するとグローバルIPアドレスが変わってしまうことにありました。そのたびに、Lambda関数のグローバルIPアドレスのコードをアップデートしなければならず、非常に使い勝手の悪いものでした。最近、その対策を思い立ちLambda関数のコードを見直して、外部のサイトを利用してIPアドレスの取得を試みましたが、次の根本的問題がわかり頓挫してしまいました。
- アドレス取得をLambda関数に実装 (http.get('http://checkip.amazonaws.com')) すると、 ユーザーの実際のグローバルIPではなくLambdaのパブリックIP を取得してしまう。理由はわからないが、そういう仕様らしい。
- AWS Lambda関数が外部サイトからグローバルIPアドレスを取得できない理由は、Lambda関数が固定のIPアドレスを持たないかららしい。Lambda関数は、実行されるたびに異なるIPアドレスが割り当てられるため、外部サイトから見ると常に変わることになる。
- この問題はLambda関数が固定のIPアドレスをもつようにすれば解決できるが、そのためには有料のサービスの利用が必要である。(VPC + NAT Gateway、AWS Global Acceleratorなど。まったく何のことかわからない。)
- 「ユーザーのグローバルIP」を取得したい場合は、クライアント(ユーザーのデバイスやブラウザ)でIPを取得し、それをLambdaにプッシュ送信する設計にする必要がある。
4番目の問題点を眺めていると、Bylnk Local server を使えば、3,4番の問題は解決する気がして来ました。そして、悪戦苦闘しながらIPアドレスの自動取得に成功した記録です。
Bylnk Local server が定期的に自動で取得した globalIP を使用して、Blynk Local server へセンサーのデータを取得をリクエストできます。
もっとも、AWSの知識はないし習得する気もないので、
ChatGPTや
Copilotを駆使して開発しました。
目次
✅ 手順
-
準備
(1-1) Raspberry Pi に Python3 がインストールされていることを確認
(1-2) 必要なライブラリのインストール -
Python スクリプトの作成
(2-1) グローバルIPを取得するスクリプトの作成
(2-2) スクリプトを Raspberry Pi に保存 -
定期実行の設定
(3-1)crontab
を使って 10 分ごとにスクリプトを実行 -
AWS Lambda との通信
(4-1) API Gateway を設定して Lambda 関数を呼び出せるようにする
(4-2) Lambda 関数を作成し、IP を受け取る処理を書く
(4-3) Raspberry Pi から API Gateway にリクエストを送る -
動作確認とトラブルシューティング
(5-1) Raspberry Pi のスクリプトが正しく動いているか確認
(5-2) API Gateway と Lambda の接続を確認
(5-3) ログをチェックし、エラーを修正
1. 準備
この章では、Raspberry Pi に必要な環境を整えます。
(1-1) Raspberry Pi に Python3 がインストールされていることを確認
Raspberry Pi には通常 Python3 がプリインストールされていますが、念のため確認し、なければインストールします。
✅ Python3 のバージョンを確認する
以下のコマンドを Raspberry Pi のターミナルで実行してください。
表示例:
Python 3.9.2
「Python 3.x.x」と表示されれば OK です。
✅ Python3 がインストールされていない場合
もし command not found
などのエラーが出る場合、以下のコマンドでインストールします。
(1-2) 必要なライブラリのインストール
Raspberry Pi の Python スクリプトでは、requests
というライブラリを使って API を呼び出します。
以下のコマンドを実行し、requests
をインストールしてください。
✅ インストールの確認
次のコマンドを実行し、正しくインストールされたか確認します。
バージョン番号(例:2.25.1
)が表示されれば OK です。
エラーが出た場合は、インストールがうまくいっていない可能性があります。
これで 「1. 準備」 が完了しました。
次は 「2. Python スクリプトの作成」 に進みますが、ここまでで不明点はありますか?
2. Python スクリプトの作成
この章では、Raspberry Pi 上で Python スクリプトを作成し、定期的にグローバル IP を取得して AWS Lambda に送信する処理を実装します。
(2-1) Python スクリプトの作成
✅ スクリプトを保存するディレクトリに移動
まず、スクリプトを保存するためのディレクトリを作成し、そこに移動します。
✅ Python スクリプトを作成する
テキストエディタ (nano
) を使って、新しい Python スクリプトを作成します。
以下のコードをコピーして貼り付けてください。
編集が終わったら、Ctrl + X
→ Y
→ Enter
で保存して終了します。
(2-2) スクリプトの実行権限を付与
作成した Python スクリプトを実行できるように、以下のコマンドを実行してください。
(2-3) スクリプトの動作確認
以下のコマンドを実行し、スクリプトが正しく動作するか確認します。
- 正常に動作すれば、以下のようなメッセージが表示されます。
Sent IP: 123.45.67.89, Response: 200, OK
- もしエラーが出た場合、どのようなエラーなのか教えてください。
これで 「2. Python スクリプトの作成」 は完了です。
次は 「3. Raspberry Pi でスクリプトを自動実行」 に進みますが、ここまでで不明点はありますか?
(2-4) エラーの修正 1
SyntaxError: Non-UTF-8 code starting with '\xa4' in file /home/pi/blynk-ip-reporter/report_ip.py on line 4, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
このエラーは、Python スクリプトの文字エンコーディングが UTF-8
でないために発生しています。これは、ファイルの保存時に文字コードが ASCII
や ISO-8859-1
になってしまった可能性があります。
以下の手順で修正してください。
(1) ファイルのエンコーディングを確認
まず、現在のファイルのエンコーディングを確認します。
utf-8
でない場合は、次の手順で修正します。
(2) ファイルのエンコーディングを UTF-8 に修正
-
ファイルを開き直す
nano
エディタでファイルを開いて、上部にUTF-8
のエンコーディング宣言を追加します。 -
1行目に以下のコメントを追加
修正後の最上部は以下のようになります。
-
保存して終了
Ctrl + X
→Y
→Enter
で保存してください。
(3) スクリプトの実行権限を再設定
(4) 再度スクリプトを実行
もう一度、スクリプトを実行してください。
この手順で修正できるはずですが、もしまだエラーが出る場合は、エラーメッセージを教えてください。
(2-5) エラーの修正 2
File "/home/pi/blynk-ip-reporter/report_ip.py", line 9 """\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u04e5\ufffd\ufffd\ufffd\u0224a\u01a5\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdХ\ufffd IP \ufffd\ufffd\ufffd\ufffd\ufffd""" ^ SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xb3 in position 0: invalid start byte
エラーの原因は、report_ip.py
のファイル内部に文字化けが発生していることです。これは、ファイルのエンコーディングが UTF-8
以外の形式で保存されている、または編集時に壊れた可能性があります。
(1) まず、現在のファイルのエンコーディングを確認する
以下のコマンドを実行し、ファイルのエンコーディングを確認してください。
出力例:
report_ip.py: text/x-python; charset=iso-8859-1
もし utf-8
以外の charset=iso-8859-1
などが表示された場合、エンコーディングを UTF-8
に変換する必要があります。
(2) ファイルのエンコーディングを UTF-8
に変換する
以下のコマンドを実行して、エンコーディングを修正してください。
もし ISO-8859-1
以外だった場合、-f
の値を file -i
コマンドの出力に合わせて変更してください。
変換後、元のファイルを置き換えます。
(3) nano
エディタで開いて確認する
- 文字化けがないことを確認する
- 最上部に
UTF-8
のエンコーディング宣言があることを確認する - 保存方法:
Ctrl + X
→Y
→Enter
(4) Python スクリプトを実行
(5) それでも解決しない場合
もしまだエラーが出る場合、次の情報を教えてください。
file -i report_ip.py
の結果cat report_ip.py
の出力(文字化けがある場合、部分的に教えてください)nano report_ip.py
で開いたときに、ファイルの内容が読めるか
この手順で修正できる可能性が高いですが、もしうまくいかなければ、追加の対策を考えます。
了解しました。では、API Gateway の設定 から一緒に進めていきましょう。
「API Gateway の作成」 から説明します。
4. AWS Lambda との通信
(4-1) API Gateway の作成(REST API を使用)
1. API Gateway を開く
- AWS マネジメントコンソール にログイン
- 検索バー に「API Gateway」と入力し、開く
2. REST API を作成
- 「API の作成」 をクリック
- 「REST API」 を選択
- 「HTTP API」ではなく、REST API を使います(設定が簡単なため)
- 「構築」 をクリック
3. API の基本設定
- 「新しい API を作成」 を選択
- API 名 を入力(例:
ip-reporter-api
) - エンドポイントタイプ は「リージョン」を選択
- 「API を作成」 をクリック
4. リソースとメソッドを作成
① リソースを作成
- 左のメニューで「リソース」を開く
- 「アクション」 → 「リソースの作成」 をクリック
- リソース名を
ip-report
に設定 - リソースパスを
/ip-report
に設定 - 「リソースの作成」をクリック
② メソッドを作成
- 「アクション」 → 「メソッドの作成」 をクリック
- 「POST」 を選択し、チェックマークをクリック
- 統合タイプ:「Lambda 関数」 を選択
- Lambda 関数名に「ip-reporter-lambda」(後で作成)を入力
- 「保存」をクリックし、「OK」で確認
5. API のデプロイ
- 左メニューで「ステージ」を開く
- 「ステージの作成」をクリック
- ステージ名を
prod
に設定(他の名前でもOK) - 「デプロイ」をクリック
- 「Invoke URL」 が表示されるので、コピー(例:
https://abcd1234.execute-api.ap-northeast-1.amazonaws.com/prod
)
6. Raspberry Pi に設定
report_ip.py
を開くLAMBDA_ENDPOINT
を以下のように変更- スクリプトを実行してテスト
これで API Gateway のエンドポイントが完成し、Raspberry Pi からデータを送信できるようになります。
ここまでの手順で不明点があれば教えてください!
おっしゃる通り、**「ステージの作成」**をクリックした際に、「Deployment is required」というエラーが表示される場合、ステージを作成する前にデプロイを行う必要があるという状況です。このエラーは、API Gatewayにリソースやメソッドを設定した後、その設定を「デプロイ」していないため、ステージを作成できないことを意味しています。
解決方法
-
デプロイの確認
ステージを作成する前に、まずAPIの設定をデプロイする必要があります。これを行わないと、ステージが作成できません。以下の手順でデプロイを行ってください。- API Gateway ダッシュボードにアクセス
- 左側のメニューから 「API」 を選択し、作成したAPIをクリック
- 左のメニューから 「アクション」 ボタンをクリックし、その中から 「デプロイ」 を選択
- 「デプロイ先ステージ」 で 「新しいステージを作成」 または既存のステージを選択
- ステージ名 を入力(例:
prod
) - デプロイ をクリック
この操作を行うことで、API設定がデプロイされ、その後にステージ作成が可能になります。
-
ステージの作成
デプロイが成功した後、以下の手順でステージを作成できます。- API Gateway ダッシュボードに戻り、再度ステージの作成を行います。
- 先程のデプロイ時に作成したステージ(例えば
prod
)を選び、ステージが正常に作成されるはずです。
補足
- **ステージ作成に必要な「デプロイ」**がまだ行われていない場合、どんな設定をしてもステージは作成できません。デプロイを先に行うことで、その後ステージ作成が可能になります。
もしこの手順でも問題が解決しない場合、もう一度API Gatewayの設定やエラーメッセージについて確認をお願いします。
(4-2) Lambda 関数を作成
そこで、今までのLambda関数を次の内容に改変する。
- 定数 globalIP を追加し、Raspberry Pi から受信した時にこの値を更新します。
- Alexaスキルを呼び出す際には、格納された globalIP を使います。
Lambda関数のコード順の処理説明
- グローバルIPアドレスの初期化:
globalIP
変数を初期化します。Raspberry Piから受信するまでデフォルトのIPアドレスが設定されています。
- レスポンスの生成関数:
- Alexaスキルのレスポンスを生成するためのヘルパー関数です。タイトル、メッセージ、リプロンプトテキスト、セッション終了フラグを受け取り、スキルの応答を構築します。
- ウェルカムメッセージの生成:
- スキルが起動されたときのウェルカムメッセージを生成します。このメッセージはユーザーにスキルの使い方を案内します。
- セッション終了時の処理:
- ユーザーがスキルを終了したときに呼び出される関数です。終了メッセージを生成してセッションを終了します。
- セッション属性の生成:
- 指定された場所の属性をセッションに追加します。
- 温度情報の取得:
- 指定された場所と測定項目(例:温度、湿度、気圧など)に基づいてBlynkサーバーからセンサーデータを取得し、ユーザーに提供します。
- エラーハンドリングを行い、データの取得に失敗した場合に適切なメッセージを表示します。
- セッション開始時の処理:
- スキルのセッションが開始されたときに呼び出される関数です。セッションの開始をログに記録します。
- スキルの起動時の処理:
- スキルが起動されたときにウェルカムメッセージを表示する関数です。
- インテントリクエストの処理:
- ユーザーがインテント(特定のアクションや質問)を発したときに呼び出される関数です。対応する処理関数(例:
readTemperatureInSession
)を呼び出します。
- ユーザーがインテント(特定のアクションや質問)を発したときに呼び出される関数です。対応する処理関数(例:
- セッション終了時の処理:
- スキルのセッションが終了されたときに呼び出される関数です。セッションの終了をログに記録します。
- Lambda関数のエントリーポイント:
- すべてのリクエストを受け取り、適切な処理関数を呼び出します。
- グローバルIPアドレスの更新、セッションの管理、ロギングとデバッグを行います。
これで各処理がどの関数で行われているかをコードと対比して理解できるようになりました。他に質問や修正が必要な箇所があれば教えてくださいね。
Lambda関数のコード
3. 定期実行の設定
(2-1) Python スクリプトの作成で作った report_ip.py は10分毎にIPアドレスを送信するので、これを修正します。
以下は、crontab を利用して 10 分ごとに実行するため、スクリプト自体は単発実行になるように修正したコード例です。
(※ 元のコードの内容はほぼそのまま残し、ループと time.sleep を削除しています。)
(3-1) crontab を使って 10 分ごとにスクリプトを実行
-
スクリプトの保存場所と実行権限の確認
このコードを/home/pi/blynk-ip-reporter/report_ip.py
に保存してください。
また、実行権限を付与します。 -
crontab の編集
ターミナルで以下のコマンドを実行して crontab エディタを開きます。 -
10 分ごとの実行スケジュールを追加
エディタの一番下に、以下の行を追加します。*/10 * * * *
は「毎時 10 分ごと」に実行する設定です。/usr/bin/python3
は Python3 のパスです。環境に合わせてwhich python3
で確認してください。- ログは
/home/pi/blynk-ip-reporter/report_ip.log
に出力されます。
-
crontab の保存
nano などのエディタの場合、Ctrl+X
→Y
→Enter
で保存します。 -
設定確認
次のコマンドで現在の crontab 設定を確認できます。
ポイント
- このコードは単発実行用に作成しています。
- crontab によって 10 分ごとにスクリプトが起動されるため、スクリプト内で無限ループする必要はありません。
- 送信する JSON は、Alexa スキル用の情報も含んでいますので、元のコードの構造をほぼ踏襲しています。
このように設定することで、Raspberry Pi のスクリプトが crontab によって 10 分ごとに実行され、グローバル IP アドレスが Lambda に送信されるようになります。
(3-2) 補足: ログファイルが大きくなりすぎるのを防ぐ
ログファイルが大きくなりすぎるのを防ぐためには、以下のような方法があります。
1. logrotate を利用する方法
- logrotate は Linux の標準的なログローテーションツールです。
/etc/logrotate.conf
や/etc/logrotate.d/
内に設定ファイルを作成し、一定期間またはサイズに応じてログファイルをローテーション(分割・圧縮・削除)できます。
/etc/logrotate.d/report_ip
以下のような設定ファイルを作成します(root 権限で作業してください)。
以下、それぞれの設定の意味を簡単に説明します。- daily → ログファイルを毎日1回、新しいファイルに切り替えます。
- rotate 7 → 古いログファイルを7世代まで(直近7日分)保持します。7日より前のログは自動的に削除されます。
- compress → ローテーションしたログファイルをgzipで圧縮し、ディスク容量を節約します。
- missingok → ログファイルが存在しなくてもエラーにせず、処理を続けます。
- notifempty → ログファイルが空の場合は、ローテーション(切り替え)を行いません。
- create 644 pi pi → 新しく作成するログファイルに、パーミッション「644」と、所有者をユーザー「pi」とグループ「pi」に設定します。
つまり、この設定では毎日ログが切り替えられ、直近7日分のログが保存され、古いログは削除されます。また、空のログは保存されず、ログファイルは圧縮されるのでディスク容量が節約されます。
2. スクリプト内でログ出力を最小限にする
- 例えば、デバッグ用のログ出力を必要最低限にするか、または不要な情報を出力しないように変更します。
- また、
>>
でなく>
にすると、実行毎に上書きされます。ただし、過去のログが必要な場合は注意が必要です。
例:
crontab のエントリを以下のように変更すると、ログが毎回上書きされます(前回のログが失われるので、履歴が不要な場合のみ推奨):
まとめ
- 長期間のログ蓄積が不要なら、
>
を使って上書きする方法もあります。 - 履歴を保持しつつログサイズを管理するなら、logrotate を設定するのが一般的です。
どちらの方法が適しているかは、用途と必要なログ履歴に応じて選んでください。
課題
- 定数 globalIP を追加し、Raspberry Pi から受信した時にこの値を更新します。
- Alexaスキルを呼び出す際には、格納された globalIP を使います。
Raspberry pi は10分毎にIPアドレスをAPI Gatewayを介して、Lambda関数に渡します。Lambda関数はセッション(Alexaとの対話)の間、渡された globalIPを使えますが、セッションが終了すると、globalIPが消失してしまします。
言い換えれば、Raspberry pi からLambda関数に渡されたIPアドレスは、Lambda関数が終了すると消えてしまい、次のRaspberry pi からの送信までIPアドレスがない状態になります。この間にスキルからLambda関数を呼び出して、セッションを開始してもエラーになります。
実験をした結果、10分ではダメで、5分ならOKでした。Lambda関数のタイムアウト(セッションを持続する時間)は3秒で、API Gatewayのタイムアウトは29秒なので、時間スケールが異なります。なぜこの時間になるのかはわかりません。
もう一点の課題は、常にLambda関数が動いていなければ、スキルが正常に動作しないことです。Alexaに部屋の温度を聞くのは、極まれに気の向いた時だけなのに、その時のためにLambda関数は常に動かしている必要があることです。本来であれば、スキルが呼び出されたら、動作を始めるべきだと思います。