メインコンテンツまでスキップ

· 約3分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

ESP32-DevKitCでAmazon Dashボタンもどきを作ります。 C言語はわからないのでMicroPythonです。 簡単です。

準備

ESP32-DevKitC IFTTTアカウントとWebhookをトリガーとしたアプレット

開発環境

Windows 10 Visual Studio Code Putty Python 3.7(ストア版)

開発環境構築

Pythonの仮想環境作成

python -m venv .venv
.venv\Scripts\activate

ライブラリー導入

pip install esptool adafruit-ampy

ESP32にMicroPythonファームの導入

http://micropython.org/download#esp32 からファームウェアをダンロードします。 (2019/12/29の最新版はesp32-idf3-20191220-v1.12.bin)

esptool --port COM4 erase_flash
esptool --chip esp32 --port COM4 write_flash -z 0x1000 esp32-idf3-20191220-v1.12.bin

COM4は環境に合わせて修正します。

これでMicroPythonのファームウェアが導入でき、シリアル接続をすると対話インタプリタモード(REPL)でMicroPythonがESP32上で実行できます。

Puttyで接続する場合はCOM4ポートにSpeed115200を指定し、シリアル接続します。

コメント 2019-12-29 143339.png

Dashボタンプログラムの導入

MicroPythonはREPLで実行もできますが、ソースコードをESP32側に導入することでも実行できます。boot.pymain.pyというファイルが標準で用意されていて、boot.pyのあとでmain.pyが呼ばれる仕組みのようです。 main.pyを書き換えます。

main.py
wifi_ssid = '[Wi-Fi SSID]'
wifi_password = '[Wi-Fi パスワード]'
get_url = '[IFTTTのWebhook URL]'

def do_connect():
import network
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect(wifi_ssid, wifi_password)
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())

try:
do_connect()

import urequests
r = urequests.get(get_url)
finally:
pass

import machine
print('deepsleeping..')
machine.deepsleep()

IFTTTのWebhookは予め準備しておいてください。 処理の流れとしては

  1. 電源が入る
  2. Wi-Fi接続
  3. IFTTTにリクエスト送信
  4. ディープスリープに入る

となってます。

肝心のDASHボタンのボタンは、基板上に用意されているENボタンを使います。 ボタンが押されたらリセットし、頭から処理をするだけです。処理が終わったらディープスリープに入って省電力モードに入るとうわけです。

簡単!

電池の持ちは実験してみようと思います。

· 約2分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

※追記 Greengrass Nodejs SDKのGitHubに書いてますね。。 https://github.com/aws/aws-greengrass-core-sdk-js

Rename the file to nodejs8.10 Make sure the file is not a symlink.

シンボリックリンクもだめらしいです。


GreengrassにNode.js(v8.10)のLambdaをデプロイすると、エラーになります。

Deployment xxxxx of type NewDeployment for group xxxxx failed error: worker with xxxxx failed to initialize

ログを確認すると、nodejs8.10というバイナリがないよと言っているようです。

/greengrass/ggc/var/log/system/runtime.log
[ERROR]-runtime execution error: unable to start lambda container.  {"errorString": "failed to run container sandbox: container_linux.go:344: starting container process caused \"exec: \\\"nodejs8.10\\\": executable file not found in $PATH\""}

無理やり作ってやると、うまくいくようになりました。

sudo ln -s /usr/bin/nodejs /usr/bin/nodejs8.10

Node.jsのセットアップの問題?

環境

OS: Ubuntu 18.04(VirtualBox on Mac) Greengrass: 1.9.4 Node.js:10.17.0

Node.jsのインストールは公式サイト( https://github.com/nodesource/distributions/blob/master/README.md )に従いました。(2019/11/24時点で最新のv10.17.0がインストールされました)

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs

Raspberry Piでは続きがあります

Raspberry Piでも同様の問題が起きますが、上記手順でデプロイは成功するようになります。ただし、Lambdaの実行時にエラーとなって実行されません。 こちらは解決法がわかりません。。。ヘルプ。。。

Raspberry Pi Zero W OS:Raspbian Buster Greengrass: 1.9.3 Node.js:10.16.3

Lambdaが呼び出されると、以下のエラー

/greengrass/ggc/var/log/user/[リージョン]/[アカウントID]/[Lambda名].log
[ERROR]-standard_init_linux.go:207: exec user process caused "operation not permitted"

· 約8分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

CloudWatch Logsに出力されたLambdaのログをS3に保管する方法です。

CloudWatch Logsのサブスクリプションという機能でログをKinesis Data Firehoseに送信します。 そのままS3に出力すると複数のログが1行に並ぶ形になってしまいますが、Kinesis Data Firehoseのデータ変換機能で改行を加えることで解決します。

こんな感じです。

名称未設定ファイル.png

S3バケットの作成

ログを出力するバケットを作成します。今回はlog-backup-xxxxxとします。

データ変換用Lambdaの作成

データ変換用LambdaはAWSが設計図を用意してくれているので簡単に作成できます。 マネジメントコンソールでLambda関数を作成します。

項目選択説明
作成方法設計図の使用
設計図kinesis-firehose-cloudwatch-logs-processorNode.js版。Python2.7版のkinesis-firehose-cloudwatch-logs-processor-pythonもあります
関数名kinesis-firehose-cloudwatch-logs-processor任意
実行ロール基本的なLambdaアクセス権限で新しいロールを作成する

ソース中にコメントに以下の記載があり、どのような形式のログが渡ってくるかがわかります。

/*
For processing data sent to Firehose by Cloudwatch Logs subscription filters.

Cloudwatch Logs sends to Firehose records that look like this:

{
"messageType": "DATA_MESSAGE",
"owner": "123456789012",
"logGroup": "log_group_name",
"logStream": "log_stream_name",
"subscriptionFilters": [
"subscription_filter_name"
],
"logEvents": [
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208016,
"message": "log message 1"
},
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208017,
"message": "log message 2"
}
...
]
}

The data is additionally compressed with GZIP.

The code below will:

1) Gunzip the data
2) Parse the json
3) Set the result to ProcessingFailed for any record whose messageType is not DATA_MESSAGE, thus redirecting them to the
processing error output. Such records do not contain any log events. You can modify the code to set the result to
Dropped instead to get rid of these records completely.
4) For records whose messageType is DATA_MESSAGE, extract the individual log events from the logEvents field, and pass
each one to the transformLogEvent method. You can modify the transformLogEvent method to perform custom
transformations on the log events.
5) Concatenate the result from (4) together and set the result as the data of the record returned to Firehose. Note that
this step will not add any delimiters. Delimiters should be appended by the logic within the transformLogEvent
method.
6) Any additional records which exceed 6MB will be re-ingested back into Firehose.
*/

処理手順も色々ありそうですが、そのあたりはすでに実装済みなので、変換する形式を変更したい場合はtransformLogEvent関数を修正するだけです。 設計図での実装は、付加情報は全部除外して、ログのメッセージに改行を付与して出力しています。

/**
* logEvent has this format:
*
* {
* "id": "01234567890123456789012345678901234567890123456789012345",
* "timestamp": 1510109208016,
* "message": "log message 1"
* }
*
* The default implementation below just extracts the message and appends a newline to it.
*
* The result must be returned in a Promise.
*/
function transformLogEvent(logEvent) {
return Promise.resolve(`${logEvent.message}\n`);
}

例えば、logEventの内容をすべて出力し改行を付与する場合は、以下の様になると思います。

function transformLogEvent(logEvent) {
return Promise.resolve(`${JSON.stringify(logEvent)}\n`);
}

また、Amazon Kinesis Data Firehose CloudWatch Logs Processorというテストイベントも用意されているので、マネジメントコンソールで簡単にテストができます。

最後に、Lambdaのタイムアウトを1分以上にしておきましょう。

Kinesis Data Firehoseのストリームを作成

マネジメントコンソールで作成します。

項目選択説明
Delivery stream nameCloudWatchLogs-to-S3任意
Choose a source
sourceDirect PUT or other sources
---次のページ---
Transform source records with AWS Lambda
Record transformationEnabled
Lambda functionkinesis-firehose-cloudwatch-logs-processor作成したLambda
Lambda function version$LATEST
Convert record format
Record format conversionDisabled
---次のページ---
Select a destination
DestinationAmazon S3
S3 destination
S3 destinationlog-backup-xxxxx作成したバケット
S3 prefixlogs/
S3 error prefixerror/
S3 backup
Source record S3 backupDisabled
S3 buffer conditions
Buffer size5MBデフォルト値
Buffer interval300secondsデフォルト値
S3 compression and encryption
S3 compressionDisabled
S3 encryptionDisabled
Error logging
Error loggingEnabled
Permissions
IAM roleCreate new or choose新しくIAMロールを作成するといい感じにアクセス権限を付与してくれます

CloudWatch Logsに付与するIAMロールを作成

このあとの手順で作成するCloudWatch Logsのサブスクリプションフィルターに、Firehoseにアクセスする権限が必要なので、IAMロールを作成します。 ただ、マネジメントコンソール上からは、CloudWatch Logsに付与するIAMロールはそのままでは作れないので、以下の手順で作成します。

まずはマネジメントコンソールでIAMロールを作成します。

項目選択説明
信頼されたエンティティの種類を選択AWSサービス
このロールを使用するサービスを選択EC2
Attach アクセス権限ポリシーなし次の手順で付与します
ロール名CWLtoKinesisFirehoseRole
ロールの説明削除説明がEC2になってるので削除しておく

次にIAMポリシーを作成します。

項目選択説明
サービス1
サービスFirehose
アクションすべてのFirehoseアクション
リソースarn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3作成したFirehoseの配信ストリームのARN
サービス2
サービスIAM
アクションPassRole
リソースarn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole作成したIAMロールのARN
---次のページ---

| ポリシーの確認 | 名前 | Permissions-Policy-For-CWL |

再度IAMロールの編集画面に戻り、CWLtoKinesisFirehoseRoleロールにPermissions-Policy-For-CWLポリシーをアタッチします。

最後にCWLtoKinesisFirehoseRoleロールの信頼関係タブの信頼関係の編集をクリック。"Service": "ec2.amazonaws.com"の部分を"Service": "logs.ap-northeast-1.amazonaws.com"に変更し、保存します。

CloudWatch Logsサブスクリプションフィルターの作成

マネジメントコンソールからは作成できないようですので、CLIで作成します。

パラメータ(キー)パラメータ(値)説明
--log-group-name/aws/lambda/xxxxxxサブスクリプションフィルターを追加したいロググループ名
--filter-nameLogs-to-Firehose任意
--filter-pattern""フィルターせず、全ての場合
--destination-arnarn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3作成したFirehoseの配信ストリームのARN
--role-arnarn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole作成したIAMロールのARN
aws logs put-subscription-filter --log-group-name [ロググループ名] --filter-name Logs-to-Firehose --filter-pattern "" --destination-arn arn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3 --role-arn arn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole 

完成

これで無事にS3にCloudWatchLogsがS3に保存されます。 GlueやAthenaでもクエリーがかけられそうです。

参考

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#FirehoseExample https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/data-transformation.html https://docs.aws.amazon.com/cli/latest/reference/logs/put-subscription-filter.html

· 約4分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

Greengrass Coreがv1.9.3でArmv6lをサポートしました。ラズパイZeroでもGreengrassが動作するようになりました。

https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/what-is-gg.html

久しぶりにGreengrassに触るので、Greengrassコネクタを使ってLチカをしてみました。 GreengrassコネクタにRaspberry Pi GPIOコネクタが用意されているので簡単です。

ラズパイZeroの準備

以下の公式ドキュメントに従って行います。

Raspberry Pi のセットアップ https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/setup-filter.rpi.html

モジュール 2: AWS IoT Greengrass Core ソフトウェアのインストール https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/module2.html

Greengrassの設定

リソースの追加

Raspberry Pi GPIOコネクタがGPIOにアクセスするため、リソースを設定します。

対象のGreengrassグループを選択し、リソースタブを表示し、ローカルリソースの追加ボタンを押します。

設定項目設定値
リソース名任意の名前
リソースタイプデバイス
デバイスパス/dev/gpiomem
グループ所有者のファイルアクセス許可リソースを所有するLinuxグループのOSグループアクセス許可を自動的に追加
Lambda関数の関連無指定でOK

コネクタの追加

Raspberry Pi GPIOコネクタを追加します。

対象のGreengrassグループを選択し、コネクタタブを表示し、コネクタの追加ボタンを押します。

Raspberry Pi GPIOを選択したあと、パラメータはこんな感じで指定しました。

設定項目設定値
Resource for /dev/gpiomem device作成したリソース
Input GPIO pins2 ※ボタンのGPIOピン番号
Input GPIO polling period50(millisecond)
Output GPIO pins17 ※LEDのGPIOピン番号

Lambdaの作成

ボタンが押された/離されたイベントで起動し、LEDのオン/オフを設定する処理を行います。試行錯誤したのであまりきれいではありませんが。。

import os
import greengrasssdk
import json
import sys
import logging

## Setup logging to stdout
logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

iot_client = greengrasssdk.client('iot-data')

thingName = os.environ['AWS_IOT_THING_NAME']

def get_read_topic(gpio_num):
return '/'.join(['gpio', thingName, str(gpio_num), 'read'])

def get_write_topic(gpio_num):
return '/'.join(['gpio', thingName, str(gpio_num), 'write'])

def send_message_to_connector(topic, message=''):
iot_client.publish(topic=topic, payload=str(message))

def set_gpio_state(gpio, state):
send_message_to_connector(get_write_topic(gpio), str(state))

def read_gpio_state(gpio):
send_message_to_connector(get_read_topic(gpio))

def function_handler(event, context):
logger.info("Received message!")
logger.info(event)
logger.info(type(event))

# event
# 1 : button off
# 0 : button on

state = 0
if(event == 0):
state = 1
set_gpio_state(17, state)

return

LambdaにはAWS IoT Greengrass Core SDK for Pythonを含める必要があります。このあたりを参考にしました。

Lambda 関数の作成とパッケージ化 https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/create-lambda.html

Lambdaの追加

作成したLambdaをGreengrassに追加します。

対象のGreengrassグループを選択し、Lambdaタブを表示し、Lambdaの追加ボタンを押します。

設定項目設定値
Lambdaの追加既存の Lambda 関数の使用
Lambda の選択作成したLambda
Lambda バージョンの選択作成したLambdaのバージョン

サブスクリプションの追加

対象のGreengrassグループを選択し、サブスクリプションタブを表示し、サブスクリプションの追加ボタンを押します。

ボタンイベント -> Greengrassコネクタ -> Lambda呼び出し

ソースターゲットトピック
Raspberry Pi GPIOLambdagpio/+/ボタンのGPIOピン番号/state

Lambda -> Greengrassコネクタ -> LEDオンオフ

ソースターゲットトピック
LambdaRaspberry Pi GPIOgpio/+/LEDのGPIOピン番号/write

サブスクリプションをいじれば、クラウド経由でLチカも簡単です。

デプロイ

これで設定は完了です。デプロイしましょう。

· 約5分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

簡単なPythonプログラムをFargateで実行するまでの道のりです。

app.py
import os

import boto3

bucket = os.getenv('BUCKET_NAME', '')
key = 'HelloWorld.txt'
body = 'Hello, World!'

s3 = boto3.client('s3')
s3.put_object(Bucket=bucket, Key=key, Body=body)

環境変数で渡したバケットに固定文字列のファイルを出力するだけです。

試した環境は

  1. Windows環境で実行
  2. Docker環境で実行
  3. Fargate環境で実行(1回だけ実行)
  4. Fargate環境で実行(定期実行)

7/7 更新 S3エンドポイントは不要でしたので記述を削除しました。


Windows環境で実行

環境

  • Windows 10
  • Python 3.6

Python仮想環境の作成

python -m venv .venv
.venv\Scripts\activate

ライブラリーの取得

pip install boto3 awscli

AWS CLIの設定(認証情報の設定)

aws configure
AWS Access Key ID [None]: [アクセスキー]
AWS Secret Access Key [None]: [シークレットアクセスキー]
Default region name [None]: [ap-northeast-1などのリージョン]
Default output format [None]:

バケット名の指定(環境変数)

バケット名は環境変数から取得するようにしたので、環境変数にバケット名をセットします

set BUCKET_NAME=[バケット名]

実行!

python -m app

無事、S3にファイルが出力されました。

Docker環境で実行

ファイルの用意

requirements.txtDockerfileを用意し、app.pyと同じフォルダーに格納します。

requirements.txt
boto3==1.9.183
Dockerfile
FROM python:alpine

WORKDIR /app
ADD . /app

RUN python3 -m pip install -r requirements.txt
CMD ["python3", "-m", "app"]

Dockerイメージのビルド

docker build -t [Dockerイメージのタグ名] .

実行!!

コンテナ内にはAWSの認証情報がないので、環境変数で渡します。バケット名も同様です。

docker run -e AWS_ACCESS_KEY_ID=[アクセスキー] -e AWS_SECRET_ACCESS_KEY=[シークレットアクセスキー] -e BUCKET_NAME=[バケット名] [Dockerイメージのタグ名]

無事、S3にファイルが出力されました。

Fargate環境で実行の準備

準備1:ECRへの登録

Fargateで実行するため、DockerイメージをECR(Elastic Container Registry)に登録します。

リポジトリの作成

aws ecr create-repository --repository-name [リポジトリ名]

docker loginコマンドの取得と実行

aws ecr get-login --no-include-email

コンソールに出力されるコマンドを実行します。

リモートリポジトリに登録するローカルDockerイメージを指定する

docker image tag [ローカルのDockerイメージのタグ名] [リポジトリ名]:[リモートのDockerイメージ名]

Dockerイメージをリポジトリにプッシュする

docker image push [リポジトリ名]:[リモートのDockerイメージ名]

準備2:ECSのクラスターの作成

マネジメントコンソールでECSの画面を表示。クラスターを作成する。

設定項目設定内容
クラスターテンプレートの選択ネットワーキングのみ
クラスターの設定このクラスター用の新しいVPCを作る

準備3:ECSのタスク定義の作成

マネジメントコンソールでECSの画面を表示。タスク定義を作成する。

設定項目設定内容
起動タイプの互換性の選択FARGATE
タスクとコンテナの定義の設定↓↓↓
タスク実行ロールAmazonECSTaskExecutionRolePolicyの他にS3へのPutObject権限も必要
コンテナの定義↓↓↓
イメージECRのイメージ URI(マネジメントコンソールで確認する)
環境変数BUCKET_NAMEにバケット名を指定

Fargate環境で実行(1回だけ実行)

タスク定義から作成したものを選び、アクションタスクの実行を行います。

設定項目設定内容
起動タイプFARGATE
クラスター作成したもの
タスクの数1
VPC作成したもの

実行!!!

ウィザードの最後のタスクの実行を押すと、実行されます。

Fargate環境で実行(定期実行)

クラスターから作成したものを選び、画面下のタブにあるタスクのスケジューリングの先の作成を選びます。

設定項目設定内容
スケジュールルールタイプお好みで
起動タイプFARGATE
タスク定義作成したもの
タスクの数1
クラスター VPC作成したもの

実行!!!!

ウィザードの最後の作成を押すと、指定したスケジュールに従って実行されます。 固定された間隔で実行の場合、初回起動は作成を押してから固定された間隔が経過したあとになるようです。

まとめ

後半がかなり手抜きになりましたが、一応Fargateで動作するところまでできました。

参考サイト

AWS ECS/Fargate 操作覚え書き