Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Hannari python10 20181019

50 views

Published on

はんなりPython#10
メールを使ってラズパイを遠隔操作する

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Hannari python10 20181019

  1. 1. Hello!! 組み込み系(マイコン、Linux) Python 歴 6年くらい @takey
  2. 2. ラズパイを出先でも操作したい
  3. 3. 調べたら色々出てくる ● SSH接続 ● VPN接続 ● TeamViewer ● AWS IoT ● etc...
  4. 4. メールを使う方法ってないな…
  5. 5. メールのすごいところ ● 環境を整備する必要なし ○ インフラ化している ● フリーメール ○ 無料 ○ アカウント作りたい放題 ○ SSL/TLSで暗号化できる ● スマホやPCなど端末を選ばず操作可能
  6. 6. システム概要 (1) メール送信 メールサーバ 自宅 (2) メール受信 (4) 実行結果をメール返信 (3) メール解析& コマンド実行 (5) 返信受信
  7. 7. 目次 1. メールの説明 2. Pythonでメールを送信する 3. Pythonでメールを受信する 4. メールを解析してコマンドを実行する
  8. 8. 目次 1. メールの説明 2. Pythonでメールを送信する 3. Pythonでメールを受信する 4. メールを解析してコマンドを実行する
  9. 9. メールプロトコル ● SMTP ○ 送信用のプロトコル ● POP3 ○ 受信用のプロトコル(ローカルにダウンロードして閲覧) ○ Thunderbird, Outlookなど ● IMAP4 ○ 受信用のプロトコル(サーバで閲覧) ○ Gmail, Yahoo!メールなど
  10. 10. メールプロトコル ● SMTP ○ 送信用のプロトコル ● POP3 ○ 受信用のプロトコル(ローカルにダウンロードして閲覧) ○ Thunderbird, Outlookなど ● IMAP4 ○ 受信用のプロトコル(サーバで閲覧) ○ Gmail, Yahoo!メールなど
  11. 11. 目次 1. メールの説明 2. Pythonでメールを送信する 3. Pythonでメールを受信する 4. メールを解析してコマンドを実行する
  12. 12. メールを送信してみよう
  13. 13. Gmail ホスト名: smtp.gmail.com 通信方式:STARTTLS, SSL/TLS ※必ず暗号化必須 Yahoo!メール ホスト名: smtp.mail.yahoo.co.jp 通信方式:暗号化なし, SSL/TLS 国内のフリーメール
  14. 14. Gmailを使うときの注意 https://myaccount.google.com/u/1/lesssecureapps?pageId=none Gmailの設定で「セキュリティの低いアプリの許可」を有効にする必要あり
  15. 15. 主なライブラリ ● smtplib (https://docs.python.jp/3/library/smtplib.html) ○ smtpプロトコル機能を提供するライブラリ(メール送信できるライブラリ) ● email.mime (https://docs.python.jp/3/library/email.mime.html) ○ MIMEメッセージを作成するライブラリ MIMEメッセージとは、メールシステム上で扱えるようにした データのこと メールとは、MIMEメッセージのやりとりと言える
  16. 16. 処理の流れ 1. MIMEメッセージを作成する ○ メール用のデータを作成! 2. MIMEメッセージに必要なヘッダ情報を付与する ○ FromとかToとか! 3. SMTPクライアントインスタンスを作成する ○ メール送信の準備! 4. SMTPサーバにログインする ○ 認証! 5. メール送信(MIMEメッセージを送信する)
  17. 17. import base64 import smtplib import ssl from email.mime.text import MIMEText from email.utils import formatdate # (1) MIMEメッセージを作成する main_text = "これが本文です" charset = “utf-8” msg = MIMEText(main_text, "plain", charset) メール送信サンプルコード(1/2) # (2) MIMEメッセージに必要なヘッダを付ける msg.replace_header("Content-Transfer-Encoding", "base64") msg["Subject"] = "これが件名です" msg["From"] = "from@gmail.com" msg["To"] = "to@gmail.com" msg["Cc"] = "" msg["Bcc"] = "" msg["Date"] = formatdate(None,True)
  18. 18. import base64 import smtplib import ssl from email.mime.text import MIMEText from email.utils import formatdate # (1) MIMEメッセージを作成する main_text = "これが本文です" charset = “utf-8” msg = MIMEText(main_text, "plain", charset) メール送信サンプルコード(1/2) # (2) MIMEメッセージに必要なヘッダを付ける msg.replace_header("Content-Transfer-Encoding", "base64") msg["Subject"] = "これが件名です" msg["From"] = "from@gmail.com" msg["To"] = "to@gmail.com" msg["Cc"] = "" msg["Bcc"] = "" msg["Date"] = formatdate(None,True)
  19. 19. import base64 import smtplib import ssl from email.mime.text import MIMEText from email.utils import formatdate # (1) MIMEメッセージを作成する main_text = "これが本文です" charset = “utf-8” msg = MIMEText(main_text, "plain", charset) メール送信サンプルコード(1/2) # (2) MIMEメッセージに必要なヘッダを付ける msg.replace_header("Content-Transfer-Encoding", "base64") msg["Subject"] = "これが件名です" msg["From"] = "from@gmail.com" msg["To"] = "to@gmail.com" msg["Cc"] = "" msg["Bcc"] = "" msg["Date"] = formatdate(None,True)
  20. 20. # (3) SMTPクライアントインスタンスを作成する host = "smtp.gmail.com" port = 587 smtpclient = smtplib.SMTP(host, port, timeout=10) smtpclient.ehlo() smtpclient.starttls() smtpclient.ehlo() メール送信サンプルコード(2/2) # (4) SMTPサーバにログインする user = "from@gmail.com" password = "password" smtpclient.login(user, password) # (5) メールを送信する smtpclient.send_message(msg) smtpclient.quit()
  21. 21. # (3) SMTPクライアントインスタンスを作成する host = "smtp.gmail.com" port = 587 smtpclient = smtplib.SMTP(host, port, timeout=10) smtpclient.ehlo() smtpclient.starttls() smtpclient.ehlo() メール送信サンプルコード(2/2) # (4) SMTPサーバにログインする user = "from@gmail.com" password = "password" smtpclient.login(user, password) # (5) メールを送信する smtpclient.send_message(msg) smtpclient.quit()
  22. 22. # (3) SMTPクライアントインスタンスを作成する host = "smtp.gmail.com" port = 587 smtpclient = smtplib.SMTP(host, port, timeout=10) smtpclient.ehlo() smtpclient.starttls() smtpclient.ehlo() メール送信サンプルコード(2/2) # (4) SMTPサーバにログインする user = "from@gmail.com" password = "password" smtpclient.login(user, password) # (5) メールを送信する smtpclient.send_message(msg) smtpclient.quit()
  23. 23. # (3) SMTPクライアントインスタンスを作成する host = "smtp.gmail.com" port = 587 smtpclient = smtplib.SMTP(host, port, timeout=10) smtpclient.ehlo() smtpclient.starttls() smtpclient.ehlo() メール送信サンプルコード(2/2) # (4) SMTPサーバにログインする user = "from@gmail.com" password = "password" smtpclient.login(user, password) # (5) メールを送信する smtpclient.send_message(msg) smtpclient.quit()
  24. 24. 目次 1. メールの説明 2. Pythonでメールを送信する 3. Pythonでメールを受信する 4. メールを解析してコマンドを実行する
  25. 25. メールを受信してみよう
  26. 26. 主なライブラリ ● imaplib (https://docs.python.jp/3/library/imaplib.html) ○ imap4プロトコル機能を提供するライブラリ(メール受信できるライブラリ) ● email.header (https://docs.python.jp/3/library/email.header.html) ○ MIMEメッセージのヘッダを解析するライブラリ ○ FromヘッダとかSubjectヘッダとかを取り出すのに便利
  27. 27. 処理の流れ 1. IMAP4クライアントインスタンスを作成する ○ メール受信の準備! 2. IMAP4サーバにログインする ○ 認証! 3. メール受信(MIMEメッセージを受信する) 4. メール解析 ○ 件名とか、本文とかをデコードする!
  28. 28. import email import ssl import imaplib from email.header import decode_header, make_header # (1) IMAP4クライアントインスタンスを作成する host = "imap.gmail.com" port = 993 context = ssl.create_default_context() imapclient = imaplib.IMAP4_SSL(host, port, ssl_context=context) # (2) IMAPサーバーにログインする username = "username@gmail.com" password = "password" imapclient.login(username, password) メール受信サンプルコード(1/1) # (3) メール受信 imapclient.select() typ, data = imapclient.search(None, "ALL") datas = data[0].split() #datas = [b’1’, b’2’, ...] fetch_num = 5 # 取得したいメッセージの数 msg_list = [] # 取得したMIMEメッセージを格納するリスト for num in datas[len(datas)-fetch_num::]: typ, data = imapclient.fetch(num, '(RFC822)') msg = email.message_from_bytes(data[0][1]) msg_list.append(msg) imapclient.close() imapclient.logout() # (4) メール解析(各ヘッダ情報や本文を取得する) for msg in msg_list: # ヘッダ情報は、ディクショナリのようにアクセスできる # str(make_header(decode_header(msg["From"]))) payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: payload = payload.decode(charset, "ignore") print(payload) # 本文表示
  29. 29. import email import ssl import imaplib from email.header import decode_header, make_header # (1) IMAP4クライアントインスタンスを作成する host = "imap.gmail.com" port = 993 context = ssl.create_default_context() imapclient = imaplib.IMAP4_SSL(host, port, ssl_context=context) # (2) IMAPサーバーにログインする username = "username@gmail.com" password = "password" imapclient.login(username, password) メール受信サンプルコード(1/1) # (3) メール受信 imapclient.select() typ, data = imapclient.search(None, "ALL") datas = data[0].split() #datas = [b’1’, b’2’, ...] fetch_num = 5 # 取得したいメッセージの数 msg_list = [] # 取得したMIMEメッセージを格納するリスト for num in datas[len(datas)-fetch_num::]: typ, data = imapclient.fetch(num, '(RFC822)') msg = email.message_from_bytes(data[0][1]) msg_list.append(msg) imapclient.close() imapclient.logout() # (4) メール解析(各ヘッダ情報や本文を取得する) for msg in msg_list: # ヘッダ情報は、ディクショナリのようにアクセスできる # str(make_header(decode_header(msg["From"]))) payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: payload = payload.decode(charset, "ignore") print(payload) # 本文表示
  30. 30. import email import ssl import imaplib from email.header import decode_header, make_header # (1) IMAP4クライアントインスタンスを作成する host = "imap.gmail.com" port = 993 context = ssl.create_default_context() imapclient = imaplib.IMAP4_SSL(host, port, ssl_context=context) # (2) IMAPサーバーにログインする username = "username@gmail.com" password = "password" imapclient.login(username, password) メール受信サンプルコード(1/1) # (3) メール受信 imapclient.select() typ, data = imapclient.search(None, "ALL") datas = data[0].split() #datas = [b’1’, b’2’, ...] fetch_num = 5 # 取得したいメッセージの数 msg_list = [] # 取得したMIMEメッセージを格納するリスト for num in datas[len(datas)-fetch_num::]: typ, data = imapclient.fetch(num, '(RFC822)') msg = email.message_from_bytes(data[0][1]) msg_list.append(msg) imapclient.close() imapclient.logout() # (4) メール解析(各ヘッダ情報や本文を取得する) for msg in msg_list: # ヘッダ情報は、ディクショナリのようにアクセスできる # str(make_header(decode_header(msg["From"]))) payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: payload = payload.decode(charset, "ignore") print(payload) # 本文表示
  31. 31. import email import ssl import imaplib from email.header import decode_header, make_header # (1) IMAP4クライアントインスタンスを作成する host = "imap.gmail.com" port = 993 context = ssl.create_default_context() imapclient = imaplib.IMAP4_SSL(host, port, ssl_context=context) # (2) IMAPサーバーにログインする username = "username@gmail.com" password = "password" imapclient.login(username, password) メール受信サンプルコード(1/1) # (3) メール受信 imapclient.select() typ, data = imapclient.search(None, "ALL") datas = data[0].split() #datas = [b’1’, b’2’, ...] fetch_num = 5 # 取得したいメッセージの数 msg_list = [] # 取得したMIMEメッセージを格納するリスト for num in datas[len(datas)-fetch_num::]: typ, data = imapclient.fetch(num, '(RFC822)') msg = email.message_from_bytes(data[0][1]) msg_list.append(msg) imapclient.close() imapclient.logout() # (4) メール解析(各ヘッダ情報や本文を取得する) for msg in msg_list: # ヘッダ情報は、ディクショナリのようにアクセスできる # str(make_header(decode_header(msg["From"]))) payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: payload = payload.decode(charset, "ignore") print(payload) # 本文表示
  32. 32. import email import ssl import imaplib from email.header import decode_header, make_header # (1) IMAP4クライアントインスタンスを作成する host = "imap.gmail.com" port = 993 context = ssl.create_default_context() imapclient = imaplib.IMAP4_SSL(host, port, ssl_context=context) # (2) IMAPサーバーにログインする username = "username@gmail.com" password = "password" imapclient.login(username, password) メール受信サンプルコード(1/1) # (3) メール受信 imapclient.select() typ, data = imapclient.search(None, "ALL") datas = data[0].split() #datas = [b’1’, b’2’, ...] fetch_num = 5 # 取得したいメッセージの数 msg_list = [] # 取得したMIMEメッセージを格納するリスト for num in datas[len(datas)-fetch_num::]: typ, data = imapclient.fetch(num, '(RFC822)') msg = email.message_from_bytes(data[0][1]) msg_list.append(msg) imapclient.close() imapclient.logout() # (4) メール解析(各ヘッダ情報や本文を取得する) for msg in msg_list: # ヘッダ情報は、ディクショナリのようにアクセスできる # str(make_header(decode_header(msg["From"]))) payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: payload = payload.decode(charset, "ignore") print(payload) # 本文表示
  33. 33. 目次 1. メールの説明 2. Pythonでメールを送信する 3. Pythonでメールを受信する 4. メールを解析してコマンドを実行する
  34. 34. システム概要(おさらい) (1) メール送信 メールサーバ 自宅 (2) メール受信 (4) 実行結果をメール返信 (3) メール解析& コマンド実行 (5) 返信受信
  35. 35. ラズパイ宛に送るメール仕様 件名が#raspiのメールはラズパイ宛のメール と判定させる 本文1行目は認証用の文字列とする (誤って勝手に実行されるのを防ぐ) 本文2行目に、ラズパイに実行させたいコマンドを書く ラズパイは定期的にメールサーバにアクセスし、この仕様のメールが存在するか確認し続ける (存在したらコマンドを実行して、実行結果を返信する)
  36. 36. 作る
  37. 37. 作った def main(): # (1) メールサーバにラズパイ宛のメールが存在するか探す result = search_command() if result is False: return command, reply_address = result # (2) コマンドを実行する out, err = execute_command(command) # (3) コマンドの実行結果を返信する sendmail(command, out+err, reply_address) if __name__ == "__main__": import time while True: main() time.sleep(5*60) 詳細はGitHubGistに… https://gist.github.com/taketakeyyy/f5b80b1ae04bf24cbef1c076ee73ab09
  38. 38. def main(): # (1) メールサーバにラズパイ宛のメールが存在するか探す result = search_command() if result is False: return command, reply_address = result # (2) コマンドを実行する out, err = execute_command(command) # (3) コマンドの実行結果を返信する sendmail(command, out+err, reply_address) if __name__ == "__main__": import time while True: main() time.sleep(5*60) search_command()
  39. 39. search_command() (1) IMAP4サーバにアクセスする (2) 適当個数メールをフェッチする(3個程度) (3) フェッチしたメールの中にラズパイ宛のメールが存在するか確認する (a) Subjectヘッダが#raspiか? (b) payload部の1行目が認証用文字列と一致するか? (4) ラズパイ宛のメールが存在するならば、 (a) payload部の2行目を返す(実行するコマンド) (b) 返信先用に、Fromヘッダのメールアドレスも返す (c) サーバ上からこのメールを削除する(次回実行時同じことをしないように) (5) ラズパイ宛のメールが存在しないならば、 (a) Falseを返して終了
  40. 40. execute_command() def main(): # (1) メールサーバにラズパイ宛のメールが存在するか探す result = search_command() if result is False: return command, reply_address = result # (2) コマンドを実行する out, err = execute_command(command) # (3) コマンドの実行結果を返信する sendmail(command, out+err, reply_address) if __name__ == "__main__": import time while True: main() time.sleep(5*60)
  41. 41. import subprocess def execute_command(command): """ コマンドを実行する""" p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'LANG':'C'}, shell=True ) return p.stdout.decode("utf-8", "ignore"), p.stderr.decode("utf-8", "ignore") subprocess.run()はシェルコマンドを実行するモジュール 実行結果と実行時のエラーを返している execute_command()
  42. 42. sendmail(command, out+err, reply_address) def main(): # (1) メールサーバにラズパイ宛のメールが存在するか探す result = search_command() if result is False: return command, reply_address = result # (2) コマンドを実行する out, err = execute_command(command) # (3) コマンドの実行結果を返信する sendmail(command, out+err, reply_address) if __name__ == "__main__": import time while True: main() time.sleep(5*60)
  43. 43. (1) commandは実行したコマンド ○ 件名が「commandの実行結果」になるようにする (2) out+errは実行結果または実行時エラーメッセージ ○ 本文に実行結果を記載する (3) reply_addressは返信先アドレス ○ ラズパイはreply_address宛にメールを送信する sendmail(command, out+err, reply_address)
  44. 44. デモ動画なくてごめんなさい 実行例
  45. 45. ラズパイへ送ったメール ラズパイからの返信メール systemctl status takey@gmail.com
  46. 46. ラズパイへ送ったメール ラズパイからの返信メール ifconfig takey@gmail.com
  47. 47. ラズパイへ送ったメール ラズパイからの返信メール gpio readall takey@gmail.com
  48. 48. できた…
  49. 49. メリット ● 無料 ● かんたん デメリット ● 即応性がない ○ 5分間隔でサーバにアクセス →応答に最大5分かかる ● コマンド一回一回打つのしんどい ○ あらかじめバッチ処理を用意しとけばある 程度使い物になる予感 メリット/デメリット
  50. 50. Thanks!!

×