公開日:2021/11/20
title.png

18th Season


#18-9 Moscovium+ESP32⇒メール送信

 今回は、FPGAボード(Sipeed Lichee Tang Primer)に書き込んだMoscoviumマイコンESP32を接続して、Eメールを送信してみようと思います。
 ESP32が近くのアクセスポイント(AP)に接続していさえすれば、Moscoviumに接続したセンサ(今回はDHT11、気温・湿度センサを使用)から読み取ったデータをEメールで送信して、世界中のどこに居ても、PCからでもスマホからでも、受信することが出来ます。

ESP32の準備

 MoscoviumとESP32を非同期シリアル(UART)接続して、ATコマンドで制御します。従って、ESP32にATコマンドファームウェアが書き込まれている必要があります。ESP32工場出荷時のデフォルトです。

 一度でもArduino IDEでプログラム(スケッチ)を書き込んでしまうと、ATコマンドファームウェアは消えてしまっています。ATコマンドファームウェアの書き戻し方法については、こちらをご参照ください。

 ATコマンドファームウェアのバージョンにも、ご注意ください。バージョンが変わって応答メッセージが変化するATコマンドが有りました。本ページで使用したATコマンドファームウェアのバージョンはver.2.2.0です。

  【AT+GMR】
  AT version:2.2.0.0(c6fa6bf - ESP32 - Jul 2 2021 06:44:05)
  SDK version:v4.2.2-76-gefa6eca
  compile time(3a696ba):Jul 2 2021 11:54:43
  Bin version:2.2.0(WROOM-32)
  OK

 他のバージョンでは、正常動作しない場合があります。該当するバージョンのATコマンドファームウェアを書き込むか、本ページのプログラム(smtp_mail.asm)の方を合わせて修正するかしてください。

 ATコマンドファームウェアの準備が出来たら、ESP32起動時、自動的にアクセスポイント(AP)へ接続するように設定してください。設定方法についてはこちらをご参照ください。

 以上でESP32側の準備は完了です。

メール送信(SMTP)

 SMTPと言うのは、Simple Mail Transfer Protocolの頭文字を取ったもので、インターネットでEメールを送信するためのプロトコルです。

 SMTPは、IETF (Internet Engineering Task Force)が管理するRFC (Request for Comments)と言うドキュメントで規定されています。RFCは随時更新されて行くので、昔RFC-821で規定されていたSMTPですが、それはもう古くて、執筆時点での最新はRFC-5321です。

※各RFCのヘッダを見て、「Obsoleted by xxxx」となっていた場合、そのRFCは古くなっていて、新しいドキュメントはRFC-xxxxとなっています。「Updated by yyyy」の場合、そのRFCに対して、更新された部分がRFC-yyyyで規定されているので、RFC-yyyyも参照する必要があります。「Obsoletes zzzz」は、このRFCによってRFC-zzzzが古くなったという意味なので、紛らわしいですが間違わないように。

 RFCを初めて読む方は、「SMTPについて知りたい」と言ってRFC-5321だけを読んでも、多分意味が通じませんw(1^。^)w。SMTPの基本となる下位のプロトコルや定義が他のRFCで規定されているので、それらを次々と参照して行く必要があるからです。
 このページでは、RFC-5321(と、そこから波及する全ドキュメント)の内容をカバーする程の解説はできないので、不足部分は各自該当するRFCを読んで補填してくださいm(1__)m。

ESP32からSMTPサーバへ接続する

必要な情報(準備)

 ESP32を使ってメールを送信するに当たり、以下の情報が必要となります。各自、ご自身のプロバイダなどから提供される情報を参照して、調べてください。

 上の情報を調べたら、各自「smtp_mail.incl」ファイルを作って、以下の様な記述を作成してください。

  # file: smtp_mail.incl  ================================
  def smtp_host,"smtp.xxxx.com"  // smtp: server name
  def smtp_port,"465"  // smtp: port name
  def smtp_upwd,"AHVzZXIAcHdk"  // smtp: base64("\0user\0pwd")
  def smtp_from,"from.addr@xxxx.com"  // smtp: "From:" mail address
  def smtp_toad,"to.addr@xxxx.com"  // smtp: "To:" mail address


 smtp_upwdに設定する文字列は後ほど解説します。

接続する

 ここから以降、実際にESP32を使ってメールを送信するためのコマンドを順に叩きながら応答メッセージ(サンプル)を示し、その下に解説を加えて行きます。【 】内はこちらからの送信データです。

 なお、今回のプログラム(smtp_mail.asm)を使った動作確認では、Gmailのアカウントを使いました。以下サンプルもGmail SMTPサーバとの通信を例にしてあります。お使いのサーバによっては、応答が期待した物から逸脱していて、うまく通信できないことがあるかもしれません。

  【AT+CIPSTART="SSL","smtp.gmail.com",465】
  CONNECT
  OK
  +IPD,53:220 smtp.gmail.com ESMTP - gsmtp

 Gmail SMTPサーバ「smtp.gmail.com」のポート「465」にTLS(SSL)で接続しました。「+IPD」の行は、SMTPサーバからの応答です。サーバによって応答は異なります。

 今回のプログラム(smtp_mail.asm)では、前節で宣言した文字列定数smtp_hostとsmtp_portに設定したサーバに接続します。


  【AT+CIPMODE=1】
  OK

  【AT+CIPSEND】
  OK
  >

 【AT+CIPMODE=1】と【AT+CIPSEND】によって、パススルーモードに入りました。以降、ESP32をパススルー(透過)して、直接SMTPサーバと通信するモードとなります。

SMTPでメールを送信する

 前節に引き続き、SMTPサーバへのコマンドと応答サンプルを示してその下に解説を加えて行きます。

  【ehlo esp32】
  250-smtp.gmail.com at your service
  250-8BITMIME
  250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8

 SMTPサーバに接続したら、最初に実行すべきコマンドです。
 SMTPサーバからの応答は全て<応答コード3桁>で始まって、4桁目は、次行に応答が続く場合にはハイフン(−)、その行で応答が終わる場合は空白( )となります。その後ろには、人間が読んで解るメッセージが続きますが、最終行の<応答コード3桁>だけ判れば十分です。

 応答コード3桁の最上位桁は、以下の様に決まっています(RFC-5321)。

 上の応答例では、応答コード=250で肯定的応答なので、「OK/了解/エラーなし/問題なし」等と言う意味です。

 今回のプログラム(smtp_mail.asm)では、【ehlo】コマンドに未対応のサーバとは通信できません。新しい(最近の)SMTPサーバなら、対応しているはずです。


  【auth plain BASE64(\0user\0pwd)
  235 Accepted

 プロバイダから割り当てられたユーザ名とパスワードを使って、SMTPサーバにログインしました。
 「BASE64(\0user\0pwd)」の所は、そのままコピー&ペーストしてもダメです。ユーザ名「usr」とパスワード「pwd」を「\0」で区切って(先頭にも「\0」)、BASE64符号化した文字列を渡します。詳しくは後ほど解説します。

 今回のプログラム(smtp_mail.asm)では、文字列定数smtp_upwdに設定した文字列(BASE64)を使ってログインします。また、【auth plain】に未対応のサーバとは通信できません。……多分(RFCの読解が正しければ)、SMTPサーバの「MUST」要件(TLS(SSL)接続の場合)になっているので、どのサーバでも対応されているはずです。


  【mail from:<from.addr@gmail.com>】
  250 OK - gsmtp

 メールを送信しようとしているユーザ(自分自身)のメールアドレスを指定します。同時に、このコマンドを起点として、メールの送信プロトコルが始まります。

 今回のプログラム(smtp_mail.asm)では、文字列定数smtp_fromに設定したアドレスを使います。


  【rcpt to:<to.addr@xxx.mail.com>】
  250 OK - gsmtp

 メールを送信する宛先(送信先)アドレスを指定しました。
 複数の宛先に送信したい場合、【rcpt】コマンドを必要回数発行してください。

 以下、メール本体送信の中で「To:」や「Cc:」等のヘッダにアドレスを記載しても、ヘッダとして送信されるだけでメールは届きません。メールの送信先は全て、【rcpt】コマンドを使い、ここで指定しなければなりません。SMTPプロトコルでは、To:、Cc:、Bcc:等の区別は無い(※)ので、兎に角、送信したい宛先全てを【rcpt to:】コマンドで指定して行きます。

※【rcpt cc:】とか、【rcpt bcc:】とか言うコマンドはありません。

 今回のプログラム(smtp_mail.asm)では、文字列定数smtp_toadに設定したアドレスを使います。送信先アドレスは1個しか指定できません。


  【data】
  354 Go ahead - gsmtp

 「ここからメール本体を送信しますよ」と宣言するコマンドです。肯定的中間応答(300番台)なので、以下、メール本体を送信して行きます。

  【Date: Sat, 23 Oct 2021 10:54:04 +0900 (JST)】
  【From: <from.addr@gmail.com>】
  【To: <to.addr@xxx.mail.com>】
  【Subject: DHT11 data】
  【】
  【DHT11 measurement data was below.】
  【OK: dht11=26.6[degC] 19[%RH]】
  【.】
  250 OK - gsmtp

 メール本体=メールヘッダ+空行+メール本文の形式です。
 メールヘッダには「Date:」と「From:」が必須で、その他はオプション(省略可能)となっています。
 空行(CR+LFのみを送信)を挟んでメール本文が続き、ピリオド【.】でメール本体の送信が終了します。送信終了は、「CR+LF+ピリオド【.】+CR+LF」と規定されています(RFC-5321)。

 今回のプログラム(smtp_mail.asm)では、「Date:」と「From:」、「To:」のヘッダは、予め登録された情報等から自動的に生成します。そこから後ろは、プログラム(smtp_mail.asm)に埋め込んだdats疑似命令の文字列(アドレスラベル名:str_smtp_mail)を送信した後で、最後の行にDHT11の計測データを追加します。

 ピリオド【.】を送ったところで、【data】〜【.】間の送信データに対する応答が返ってきました。上の例では応答コード=250、肯定的応答ですので、この時点でメールは送信されています。続けて別なメールを送信したい場合には、【mail】コマンドに戻って繰り返します。

 なお、メール本体中にピリオド(.)で始まる行がある場合には、行頭にピリオド(.)を1文字追加して送るルール(RFC-5321)となっています。その他、メールヘッダやメール本文の「お作法」に関しては、他のRFCで規定されているので、そちらを参照してください。

※まずはMIME(RFC-2045〜2049)かな?その他、RFC-822なんてのが登場することがあるかもしれないけど、これはobsolete(古い)で、執筆時点の最新はRFC-5322でした。


  【quit】
  221 closing connection - gsmtp

 すべてのメールを送り終えたら、【quit】コマンドを送って、SMTPサーバとの通信を終了します。

 以下、再びESP32のATコマンドに戻って、後処理(後始末)を実行します。

再びESP32、SMTPサーバから切断する

 まず、SMTPサーバとのパススルーモード通信状態を解除しなければなりません。

  【+++】

 パススルーモード(【AT+CIPSEND】コマンド)から抜けるコマンドです。
 単体の【+++】パケットを送る必要があり、他の文字列(改行コードを含む)が前後に付いていると、コマンドとして認識されません。従ってプログラム(smtp_mail.asm)では、直前のデータ送信と隔離するため、【+++】送信前に500ms間何もせずに待機しています。
 更に【+++】送信後にも、次のATコマンドを送信する前に1秒以上の時間を空けるよう、ATコマンドリファレンスマニュアルが要求しています。


  【AT+CIPCLOSE】
  CLOSED
  OK

 SMTPプロトコルの最後に【quit】コマンドを送って、SMTPサーバとの通信は終了していますが、まだ接続されたままだったので、上のコマンドでSMTPサーバとの接続を切断しました。

 以上で、メール送信完了です。お疲れさまでした。

メール送信プログラム

 ここまでの節でメール送信の為に実行したATコマンドとSMTPコマンドを手動で実行すると、メールの送信は可能です。Moscoviumにメールを送信させるため、上の手順を自動的に実行する様にプログラムしました。完成したプログラムを以下にリンクしておきます。

  完成版プログラム:smtp_mail.asm

 リンクしたプログラム単体では不完全な状態です。アセンブルするには、こちらで各自調べて作成してもらったファイル(smtp_mail.incl)が必要となります。
 なお、今回のプログラムでとうとう、アセンブル後のコードサイズが4Kバイトを超えてしまいました。「`define MCOC_ROM_8K」以上を設定したデザインでないと、ROMサイズが足りません。今回使用するMCOC115EX4808には、ROMが8Kバイト搭載されているので、実行可能です。


 以下、Moscoviumのシリアルモニタで受け付けるコマンド仕様を示します。

  シリアルモニタコマンド仕様:

BASE64符号化

 SMTPもそうですが、インターネットはテキスト通信が基本になっています。しかし、メールの添付ファイルなど、バイナリデータを送受信したいケースもあって、そんな時、バイナリデータをBASE64と言う方法で7ビットのASCIIテキストデータに変換して送受信する場合があります。

  BASE64について検索する(別ウインドウが開きます)

 多分、上のリンクで検索した方が、解り易いページに辿り着けると思います(1^^;)が、BASE64もRFCで規定されています。執筆時点の最新はRFC-4648でした。


 拙作Olive+で、BASE64符号化するスクリプトを作成しましたので以下にリンクしておきます。

  BASE64符号化スクリプト:base64_enc.olv

 スクリプト(.olv)の実行には、Windowsに拙作Olive+のインストールが必要です。

auth plain

 SMTPサーバにログインするため、【auth plain】コマンドを実行しました。
 この時パラメータとして渡す文字列は、ユーザ名(user)とパスワード(pwd)を以下の様に連結して、それをBASE64符号化した文字列だと決まっています。

  \0user\0pwd

 ここで、文字列「\0」はバックスラッシュ(\)と数字ゼロ(0)の二文字ではなくて、バイナリデータの0x00を表しています。
 各自調べていただいたユーザ名を「user」位置に、パスワードを「pwd」位置に記載して、スクリプト(base64_enc.olv)実行時のウインドウに「\0user\0pwd」と打ち込んでください。

  \0user\0pwd
    ↓
  AHVzZXIAcHdk

 スクリプト(base64_enc.olv)は、文字列「\0」を見つけると、その位置にバイナリデータ0x00を埋め込む様にプログラムしてあります。副作用として、「\0」と言う文字列の符号化が出来なくなっていますm(1^^;)m。汎用のBASE64符号化スクリプトではなく、【auth plain】コマンド用の文字列を符号化することに特化してあると、ご理解ください(故意犯です)。

 ウインドウを「OK」ボタンで閉じると、変換後の文字列(上例では「AHVzZXIAcHdk」)が記載されたウインドウが開きます。これが元の文字列(上例では「\0user\0pwd」)をBASE64符号化した文字列です。
 この文字列をコピーして、smtp_upwdに貼り付けてください。

  def smtp_upwd,"AHVzZXIAcHdk"  // smtp: base64("\0user\0pwd")


 ここで要注意事項です!BASE64符号化によって、人間が読んでも意味不明な文字の羅列に変換されましたが、暗号化されたわけではありません

 BASE64という仕組みで符号化されただけなので、BASE64を知っている第三者は容易に元の文字列を読み取ることが出来ます。smtp_upwdに設定した文字列の中には、Eメールのユーザ名とパスワードが暗号化されずに記載されているので、この文字列が他人の目に触れないように細心の注意を払ってください。
 アセンブル後のファイル中にも存在しているので、うっかりMIFファイルなどを他人に見せたり渡したりしないこと!
 【smtp】コマンドのパラメータとして渡す時も同様です。画面に打ち込む時、他人に見られないように注意して下さい。

 流出させると、簡単にアカウントを乗っ取られたり、悪用されたりします。ご自身の責任でしっかり管理してください。

※プログラム(smtp_mail.asm)の先頭付近で、デバッグ出力を有効(「def d,""」)に設定してあると、シリアルモニタに表示されるログにも、smtp_upwd設定文字列が出力されることにも留意してください。デフォルトはデバッグ出力有効になっています。

デザイン

 最後にFPGAボードの準備を行います。
 今回のアプリケーションは、FTDIなどのUSB⇔UART変換モジュールを使ってシリアルモニタ(UART)を使用します。更に、ESP32とMoscoviumを接続するのにもUARTを使うので、UART8N1ユニットが2個必要となります。現在、UART8N1ユニットが2個搭載されているのはMCOC115EX4808だけですので、このデザインをFPGAボードに書き込んでください。

 以下リンクより、MoscoviumマイコンMCOC115EX4808のデザインをダウンロードできます。FPGAボードへの書き込み手順はこちらを参照してください。
 FPGAボード書き込み時のI/O制約ファイルはMCOC_full.adcです。プログラム(smtp_mail.asm)をアセンブルして作成したMIFファイルを所定のディレクトリに格納しておいてください。

btn_moscovium.png

 こちらの記事でUART8N1ユニットにハードウェアフロウ制御端子(RTS,CTS)を追加したのですが、今回は使いません。HTTPとか、大量のデータを受信する場合には必要になるかもしれませんが、SMTP程度のデータ量なら十分処理できると踏んだためです。

実験したら?

 実験です。Moscoviumマイコンを書き込んだFPGAボード、USB⇔UART変換モジュール(FTDI等)、ESP32、気温・湿度センサDHT11を、以下接続表の通りに接続してください。

FTDI 接続 FPGAボード 接続 ESP32
GND GND GND
RXD T7(uart_txd)  
TXD R7(uart_rxd)
  N5(uar1_txd) RXD2
P5(uar1_rxd) TXD2
DHT11 接続 FPGAボード  
VCC 3.3V
GND GND
SDA C15(port_iop[7])

※今回も、DHT11(5V定格)を3.3Vで駆動しています。スペック下限ギリギリですので、少しでも電圧降下があると、動作しない場合があります。その場合には、(1)DHT11に5Vを供給、(2)DHT11のSDA端子とFPGAボードの接続にレベルシフタを挟んでください。


 以下図は、実験中の様子です。

a189_expr.jpg


 Moscoviumに繋いだシリアルモニタから、実験を進めます。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【dht11】
  OK: dht11=28.9[degC] 28[%RH]

 DHT11が接続されて正常動作していれば、上の様に気温と湿度の測定結果が表示されます。


  【smtp】
    :
  OK: SMTP send mail

 デバッグ出力が有効(「def d,""」)になっている場合、途中の通信状態が表示されます。最後に「OK」が表示されれば、メールが送信できました。こちらで設定した送信先のメールボックスをPCなどのメーラで開いて、Moscvovium+ESP32からのメールが届いたかどうか、ご確認ください。

エラーチェックが不完全

 ひとまず、ESP32が期待した通りの応答を返す限り、正しくメールを送信できるプログラムになっています。ただし、エラーチェックや、期待しない応答がESP32から返ってきた場合の処置などが不完全です。

 自立(独立動作)して、自動送信し続けるプログラムにするには、もう少し対策が必要でしょう。


 まずは、Moscovium+ESP32でEメール📧を送信できたという実績がGoodですo(1^−^)o。


後日のアップデート

 本文中でサンプルとして使ったGmailですが、何やら、非公式アプリからのアクセスができなくなってしまったみたいです。💩Googleですね。

 検索ページを開くと、毎回の様に「Chromeをインストールしよう」みたいなこと言って来てウザいし、💩Googleのメールアカウントなんて、メインで使う気はサラサラありません。……検索エンジンもブラウザもしかり。
 こういう組み込み用途で使えるメールアカウントが欲しかっただけなのに、使えなくなってしまっては、もはや存在意義ゼロです。

 本ページ文中では、サンプルとして使っただけで、他のメールアカウントでも条件を満たせば同様にしてメールの送信はできると思いますので、チャレンジしてみてください。


btn_arduino.png

btn_prev.png btn_next.png

home