ラズパイのボタンをプッシュしたら写真撮ってメールに添付する

##モチベーション
[ラズパイのボタンをプッシュしたら写真撮ってS3へ上げる](https://qiita.com/atmaru/items/17c9929241873f0477a2)の続き。
S3に上げた写真をメールに添付して自分に送信するよ。

##環境
- ラズパイ
 - RaspberryPi3B+
 - Raspbian 9.8
 - RPi.GPIO
 - picamera
 - python 2.7
- AWS
 - S3
 - SES
 - boto3
 - python 3.6

##サマリー
- Lambda関数作成
- S3からLambda関数を呼び出す


##Lambda関数作成
- Lambda→関数→関数の作成→いちから作成
- 関数名：attachEmailSender
- ランタイム：Python3.6
- 実行ロール：既存のロールを使用する：Lambda_to_ses(既に自分で作成していたロールで、説明は省略します)
- 関数の作成
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/4832ad84-055d-2807-ae4a-6e5865a8c4f8.png)


S3さんが吐くjasonさんの中身の一部

```
"eventTime": "2019-03-24T11:33:30.430Z",
"sourceIPAddress": "14.132.xxx.xxx"：グローバルアドレス
"object": {
    "key": "20190324203324.jpg",
    "size": 152563,
    "eTag": "95c64f47889f9bcf39d993ea73c9c5aa",
    "sequencer": "005C976B099E707E9D"
}
```

"object"→"key"から3/24 20:33:24 がラズパイでボタンが押された時間
"T11:33:30.430Z",→UTCで11:33:30:430 → JSTで20:33:30:430がS3がラムダに発行したイベントの時間なので、6秒程度かかったということが分かる。

lambdaからkeyのファイルを添付できればよいということになる。（まだやってません）


##S3のファイルをLambdaのtmpにDownloadする
- lambda_handler: S3のイベントから呼ばれる関数
- send_email：メール送信する関数
- get_s3_file：S3からLambdaの/tmpに画像をdownloadする関数

```Lambda_function.py
import json
import boto3

SRC_MAIL = "xxx@gmail.com"
DST_MAIL = "xxx@gmail.com"
REGION = "us-east-1"
ATTACHMENT = "path/to/customers-to-contact.xlsx"
# The character encoding for the email.
CHARSET = "utf-8"

def send_email(source, to, subject, body):
    client = boto3.client('ses', region_name=REGION)
    try: 
        response = client.send_email(
            Source=source,
            Destination={
                'ToAddresses': [
                    to,
                ]
            },
            Message={
                'Subject': {
                    'Data': subject,
                },
                'Body': {
                    'Text': {
                        'Data': body,
                    },
                }
           }
        )
     
        return response
    # Display an error if something goes wrong.	
    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        print("Email sent! Message ID:"),
        print(response['MessageId'])

def lambda_handler(event, context):
    subject = "mail from raspi-lambda"
    bucket_name= event['Records'][0]['s3']['bucket']['name']
    filename=event['Records'][0]['s3']['object']['key']
    get_s3_file(bucket_name, filename)
    r = send_email(SRC_MAIL, DST_MAIL, subject, filename)
    return r

def get_s3_file(bucket_name, filename):
    s3 = boto3.resource('s3')
    bucket = s3.Bucket(bucket_name)
    bucket.download_file(filename, '/tmp/' + filename)
```



##S3バケットのイベント通知でLambdaを起動
S3 バケットのイベント通知を有効化および設定する方法

Amazon S3→atsmaru-bucket→プロパティ→Events→通知の追加
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/4c80fc34-2f73-9aca-5e2c-8086dd6907da.png)

ファイル名の最後がjpgであるファイルが作成されたらattachEmailSenderというラムダ関数を呼ぶ。CreateObjectEventという名のイベントを作成する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/1cc5e427-b036-ff33-a51a-252c53030415.png)

##LambdaにS3へのアクセス権を与える：ロールにポリシーをアタッチ
Lambda関数をテストしてみて、「when calling the HeadObject operation: Forbidden」というエラーになった。S3へのアクセス権がないためであり、ロールにアクセス権を追加する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/2b7fad58-af84-e308-ef60-c95fa34e2a38.png)
「S3」で検索して、S3に関係するポリシーだけを表示し、「AmazonS3ReadOnlyAccess」を選択して、「ポリシーのアタッチ」
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/0759ffbe-e629-3975-b4c6-d645a9d987ec.png)

「when calling the HeadObject operation: Forbidden」のエラーはでなくなった。
Designerのアクセスが許可されているリソースにもS3が追加された。DesignerからGUIで追加できるといいのにな（Amazonさんよろしくw）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/ff6ac5cc-a1c1-af61-3d7d-bcaac1b648be.png)

##メール添付するようにLambdaを変更

```Lambda_function.py
import os
import boto3
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication

SRC_MAIL = "atsmaru@gmail.com"
DST_MAIL = "atsmaru@gmail.com"
REGION = "us-east-1"
# The character encoding for the email.
CHARSET = "utf-8"
SUBJECT = "mail from raspi-lambda"
    
def send_email(source, to, subject, body):
    client = boto3.client('ses', region_name=REGION)

    try: 
        response = client.send_email(
            Source=source,
            Destination={
                'ToAddresses': [
                    to,
                ]
            },
            Message={
                'Subject': {
                    'Data': subject,
                },
                'Body': {
                    'Text': {
                        'Data': body,
                    },
                }
           }
        )
     
        return response
    # Display an error if something goes wrong.	
    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        print("Email sent! Message ID:"),
        print(response['MessageId'])

def lambda_handler(event, context):
    bucket_name= event['Records'][0]['s3']['bucket']['name']
    print("bucket_name:"+bucket_name)
    filename=event['Records'][0]['s3']['object']['key']
    print("filename:"+filename)
    get_s3_file(bucket_name, filename)
    isfile=os.path.isfile( '/tmp/' + filename)
    if isfile:
        print("isfile:true")
    else:
        print("isfile:false")
#    r = send_email(SRC_MAIL, DST_MAIL, SUBJECT, filename)
    r = send_raw_email(SRC_MAIL, DST_MAIL, SUBJECT, filename)
    return r

def get_s3_file(bucket_name, filename):
    s3 = boto3.resource('s3')
    bucket = s3.Bucket(bucket_name)
    print("download_file:"+filename)
    bucket.download_file(filename, '/tmp/' + filename)

def send_raw_email(source, to, subject, filename):
    # Replace sender@example.com with your "From" address.
    # This address must be verified with Amazon SES.
    SENDER = source
    
    # Replace recipient@example.com with a "To" address. If your account 
    # is still in the sandbox, this address must be verified.
    RECIPIENT = to
    
    # Specify a configuration set. If you do not want to use a configuration
    # set, comment the following variable, and the 
    # ConfigurationSetName=CONFIGURATION_SET argument below.

    # If necessary, replace us-west-2 with the AWS Region you're using for Amazon SES.
    AWS_REGION = REGION
    
    # The subject line for the email.
#    SUBJECT = "Customer service contact info"
    
    # The full path to the file that will be attached to the email.
#    ATTACHMENT = "path/to/customers-to-contact.xlsx"
    ATTACHMENT = '/tmp/' + filename
    
    # The email body for recipients with non-HTML email clients.
    BODY_TEXT = "Hello,\r\nPlease see the attached file for a list of customers to contact."
    
    # The HTML body of the email.
    BODY_HTML = """\
    <html>
    <head></head>
    <body>
    <h1>Hello!</h1>
    <p>Please see the attached file for a list of customers to contact.</p>
    </body>
    </html>
    """
    
    # Create a new SES resource and specify a region.
    client = boto3.client('ses',region_name=AWS_REGION)
    
    # Create a multipart/mixed parent container.
    msg = MIMEMultipart('mixed')
    # Add subject, from and to lines.
    msg['Subject'] = subject 
    msg['From'] = SENDER 
    msg['To'] = RECIPIENT
    
    # Create a multipart/alternative child container.
    msg_body = MIMEMultipart('alternative')
    
    # Encode the text and HTML content and set the character encoding. This step is
    # necessary if you're sending a message with characters outside the ASCII range.
    textpart = MIMEText(BODY_TEXT.encode(CHARSET), 'plain', CHARSET)
    htmlpart = MIMEText(BODY_HTML.encode(CHARSET), 'html', CHARSET)
    
    # Add the text and HTML parts to the child container.
    msg_body.attach(textpart)
    msg_body.attach(htmlpart)
    
    # Define the attachment part and encode it using MIMEApplication.
    att = MIMEApplication(open(ATTACHMENT, 'rb').read())
    
    # Add a header to tell the email client to treat this part as an attachment,
    # and to give the attachment a name.
    att.add_header('Content-Disposition','attachment',filename=os.path.basename(ATTACHMENT))
    
    # Attach the multipart/alternative child container to the multipart/mixed
    # parent container.
    msg.attach(msg_body)
    
    # Add the attachment to the parent container.
    msg.attach(att)
#    print(msg)
    try:
        #Provide the contents of the email.
        response = client.send_raw_email(
            Source=SENDER,
            Destinations=[
                RECIPIENT
            ],
            RawMessage={
                'Data':msg.as_string(),
            },
#            ConfigurationSetName=CONFIGURATION_SET
        )
        return response
    # Display an error if something goes wrong.	
    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        print("Email sent! Message ID:"),
        print(response['MessageId'])
```

### Lambdaのタイムアウト変更
デフォルトの3秒だとタイムアウトするようになってしまったので、基本設定→タイムアウトを30秒に延長しました。実績では4秒程度なので、十分に余裕ある数値です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29032/12b90ecd-fbb9-ef54-1b2c-3bfc748d651d.png)

###最後に
動くようになりました。

```
button
┌──┴─┬─────────┐
│GPIO│picamera │
├────┴─────────┤
│RaspberryPi3B+├── S3 ── Lambda ──  SES
└──────────────┘
```
AWSのきれいな構成図書きたいな。