HIROBIRO

HIROBIRO

金銭的、精神的自由を目指すブログ。

炊飯器の残量をメールで通知してくれる機能を作った(詳細説明編)

f:id:hirokun1735:20180430155501j:plain


前回の記事では炊飯器の残量カウンターにメール送信機能を追加したことを紹介しました。
そこでもう少し詳細な内容に踏み込んで、製作中に悩んだことなども書きます。


前回の記事はこちらです。
www.hirobiro-life.com

Arduinoとラズパイ3の通信について

Arduinoとラズパイ3間の通信にはI2Cを用いることにしました。
理由は配線が2本と少なく、電圧レベルの変換が必要ないためです。
I2Cはクロック線(SCL)とデータ線(SDA)を繋げるだけでよいです。


Arduinoの電圧レベルは5Vでラズパイ3は3.3Vなので本来は電圧レベルの変換が必要です。
しかしI2CはSCL, SDA信号に接続されたプルアップ抵抗の電圧で動作することになっており、ラズパイ側のI2Cの端子でプルアップ抵抗が内部接続されているため大丈夫なのです。
下の図はそのイメージ図で、ラズパイ3のSDA, SCLの端子がプルアップ抵抗を介して電源電圧3.3Vと接続されています。


f:id:hirokun1735:20180502083546p:plain


またI2Cはどちらをマスタ、スレーブにするか選択しなければなりません。
マスタ側からスレーブに通信リクエストを送信することで通信を開始します。


本当はArduinoをマスタとし、1時間に1度アラームを鳴らすために起きるタイミングのどこかで通信を開始したいと考えました。
しかしラズパイ3をスレーブとした場合のサンプルプログラムが見つからなかったため、今回はラズパイ3をマスタとしラズパイ3から通信を開始します。

Arduinoの起こし方

炊飯器の蓋を開け閉めしていない普段の状態ではArduinoはスリープ状態です。
最も消費電力の小さいパワーダウンモードでスリープさせており、この状態のArduinoを起こせる方法は以下の3通りです。


①外部割込み(デジタルピン D2, D3、シリアル通信)
②ウォッチドッグタイマ
③RESET信号


この3通りの中でラズパイ3から実行できるのは①です。
③も一応できますが、Arduinoをリセットするとカウント数も初期化されるので意味が無いです。


外部割込みに使用できるデジタルピンのうちD2は炊飯器の蓋の開け閉めを検知する傾斜センサと繋げています。
余っているD3にラズパイ3のGPIOピンから割込み信号を送ります。


f:id:hirokun1735:20180502084416p:plain


割込みのLOW信号を送信した後はArduinoが数十秒の間は起動状態になるため、その間にラズパイ3からI2Cのリクエストを送信して通信を開始します。
米残量のデータを読み出した後は自動的にArduinoはスリープモードに戻ります。

割込み入力で不具合発生

ラズパイ3からArduinoに割込み信号を送信してスリープ状態から起こす際に不具合が発生しました。
割込み信号を送信する前に勝手にArduinoが目覚めてしまうのです。


Arduinoが目覚めてしまう条件が分かるまで時間がかかりましたが、割込み信号を入力する配線に手を近づけるとArduinoが目覚めることが分かりました。
これは割込みを入れる配線にプルアップ抵抗を入れていないため電圧レベルが不安定になっていたのです。
そのため手を近づけるなどして周囲の静電気や電磁誘導の影響を受けるとHIGH/LOWの状態が切り替わり、ArduinoのD3ピンに割込み入力が入ってしまうのです。


Arduinoは内部抵抗を用いてピンをプルアップすることができます。
Arduinoのsetup関数内でピンの状態の設定をする際に、pinMode(3, INPUT_PULLUP); とします。
こうすることで通常時は5VにプルアップされるためD3ピンに勝手にLOW信号が送られることがありません。

  pinMode(3, INPUT_PULLUP); //割込み用ピンD3をinputに設定

スピーカーが鳴らなくなる

ラズパイ3からの割込み信号を入力するArduinoのD3ピンには元々スピーカーを接続していました。
そのためスピーカーを接続する端子を変更する必要があります。
最初スピーカーを鳴らすのに必要となるPWM信号を出力できるD5ピンに接続しましたが、何故か音が鳴りませんでした。


スピーカーを鳴らすのに使用しているのはtone関数です。

   #define BEAT 250   // 音の長さを指定 (8分音符)
   #define SOUND 11   // 圧電スピーカを接続したピン番号(D11)

   tone(SOUND,330,BEAT*1.5) ;  // ミ
   delay(BEAT*1.5) ;
   tone(SOUND,294,BEAT/2) ;  // レ
   delay(BEAT/2) ;

この関数について調べたところD3とD11のPWM出力に影響を与える恐れがあるということが分かりました。
そこでD11にスピーカーを接続したところ音が鳴るようになりました。
何故D3とD11しか使えないのかはまだ分かりませんが、あまり深入りせずにこのまま製作を進めました。

日本語のメール送信でエラー

ラズパイ3からSMTP(Simple Mail Transfer Protocol)を用いてメール送信する際のエラーです。
その前にまずラズパイ3上で日本語を入力するところからつまづきました。
デフォルトでは日本語入力ができないため、日本語入力機能をインストールする必要があるようです。


下記のサイトを参考にさせていただきました。
このやり方に従っておけば無事に日本語入力できるようになりました。
Raspberry Piの日本語設定(Pi3用) | Lazurite


さて問題はここからです。
下記のようなコードでメール送信しようとしたところエラーが発生しました。

import RPi.GPIO as GPIO
import time
import smbus
import smtp

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)

# This must match in the Arduino Sketch
SLAVE_ADDRESS = 0x04

GPIO.output(18, False)
GPIO.cleanup()
time.sleep(0.5)

reading = int(bus.read_byte(SLAVE_ADDRESS))
print(reading)
if reading <= 2:
    print('send')
    title = 'オコメ.. ナイヨ..'
    text = 'オコメ.. タリナイヨ..\nノコリ.. '\
			 + str(reading) + 'ハイ ダヨ..'
    smtp.send_email('@gmail.com', title, text) #送信先のアドレスを入力

メールのtitleやtextで日本語を使用しています。
特にtextの中身が日本語とstr型の変数を繋げたものになっており、これがエラーの原因になってしまいました。
このままメール送信しようとすると下記のようにエンコードに関するエラーが発生しました。
エラーの内容はASCIIにエンコードしようとしたけれど、失敗したという内容です。

UnicodeEncodeError: 'ascii' codec can't encode characters in position 59-61: ordinal not in range(128)

もう少し詳しく見ていきます。
上で書いたコードでは送信先のアドレス、メールのタイトル、本文をsmtp.py内のsend_emailという関数に渡しています。
smtp.pyの中身は下記で、SMTPを用いたメール送信を行っています。
下に書いたコードはエラー修正後のコードです。

#smtp.py

import sys
sys.path.append("/home/users/mymodule")
import smtplib_utf8

GMAIL_USER = '@gmail.com' #送信元アドレス入力
GMAIL_PASS = '' gmailパスワード入力
SMTP_SERVER = 'smtp.gmail.com'
SMTP_PORT = 587

def send_email(recipient, subject, text):
    smtpserver = smtplib_utf8.SMTP(SMTP_SERVER, SMTP_PORT)
    smtpserver.ehlo()
    smtpserver.starttls()
    smtpserver.ehlo
    smtpserver.login(GMAIL_USER, GMAIL_PASS)
    header = 'To:' + recipient + '\n' + 'From: ' + GMAIL_USER
    header = header + '\n' + 'Subject:' + subject + '\n'
    msg = header + '\n' + text + '\n\n'
    smtpserver.sendmail(GMAIL_USER, recipient, msg)
    smtpserver.close()

このプログラムの最後の方でmsgという変数にメールのタイトルや本文を代入しています。
そしてさらにsmtplib.pyというファイルのsendmail関数に渡しています。
(上のコードは修正後なのでsmtplib_utf8.pyのsendmailに渡している)
ASCIIコードに関するエラーはsmtplib.py内で発生していました。


smtplib.pyというファイルは1000行以上ある大きいファイルですが、この850行目あたりにmsgをASCIIとしてエンコードしろというコードがあります。
しかし実際は変数msgの中身がASCIIで無かったため、エンコードできませんというエラーが発生していたのです。

msg = _fix_eols(msg).encode('ascii')

メール本文が日本語とstr型を連結したものになっているのが原因ですが、これをASCII型にするにはどうすれば良いかよくわかりませんでした。
文字コードはなかなか奥が深いようで、まだまだ勉強しないといけませんね。
仕方ないのでsmtplib.pyの中身の方を書き換えることにしました。

msg = _fix_eols(msg).encode('utf-8')

そしてファイルの名前をsmtplib_utf8.pyとして別名で保存し、こちらのファイルをインポートして使用することにしました。
自分で新たにモジュールを自作してインポートする場合はパスを設定する必要があるようです。
下記のようなコードでインポート可能になりました。

import sys
sys.path.append("/home/users/mymodule")
import smtplib_utf8

まとめ

米残量カウンターのメール送信に関する詳細についてまとめました。
割込みの不具合、スピーカーが鳴らない不具合、メール送信の不具合などがありました。
原因が深いところまで追求できていないものもありますが、とりあえずどの問題も解決しています。