炊飯器の残量をメールで通知してくれる機能を作った
以前Arduinoを用いて、炊飯器の蓋の開閉回数から米の残量を調べる残量カウンターを作成しました。
今回はさらにRaspberry Piと連携させ、米が少なくなったことをメールで教えてくれるシステムを追加しました。
この記事は概要の説明に留め、詳細な内容は次回以降の記事に書きます。
Raspberry Pi 3でメール送信
炊飯器の残量カウンターの製作については以下の記事でまとめてあるので参考にしてください。
www.hirobiro-life.com
この残量カウンターではスピーカーでメロディが流れるようになっています。
米残量が少なくなると蓋を開け閉めしたタイミングと1時間に1回音を鳴らして教えてくれます。
しかしご飯を食べる直前まで外に出かけていたりすると当然音が聞けないため、米が少ないことに気付けません。
そういうわけでメール送信は必須の機能なのです。
残量カウンターで用いているArduino Unoだけではメール送信は実現できません。
何らかの方法でネットに接続する必要があります。
そこでRaspberry Pi 3を利用することにしました。
Raspberry Pi 3(以下、ラズパイ3)には標準でWifiが搭載されているのです。
全体のシステムを説明すると以下のようになります。
2. ラズパイ3とArduinoをI2Cで通信し、Arduinoのメモリ内にある米残量のデータを取得
3. 米残量が残り2杯分以下ならSMTPでメール送信
実際に送るメールの内容はこちらになります。
残り何杯かという数字のみ変化します。
システムの製作について
I2CやSMTPに関するプログラム作成は以下の本を参考にしました。
また私はラズパイを触るのが初めてだったため、初期設定なども参考にさせてもらいました。
多少ネットで探したこともありますが基本的な内容はこれ一冊で十分カバーされています。
Raspberry Pi クックブック 第2版 (Make:PROJECTS)
今回のシステムの配線図は以下のようになります。
実際にはArduinoには上からLCDシールドを装着しています。
S1は傾斜センサでこれを炊飯器の蓋部分に取りつけます。
以下が実際に配線した様子です。
傾斜センサは炊飯器に装着できるよう、針金で吸盤にくっつけています。
まずArduino側のプログラムですが、前回作ったプログラムを基本としています。
それにラズパイ3からの割込みを受け付けるコードやI2Cによる通信のコードを付け加えています。
少し長いですが全部のコードを書いておきます。
/* Rice_counter Ver.2 1.残り米量「○○杯分」を設定 2.蓋の開閉ごとにカウントダウン 3.残り米量2杯分になると一定時間ごとに音を鳴らす Ver.2にてメール送信機能追加 */ #include <LiquidCrystal.h> #include <LCDKeypad.h> #include <avr/sleep.h> #include <avr/wdt.h> #include <Wire.h> #define DEBUG 0 //4分音符=120 (duration=500) #define BEAT 250 // 音の長さを指定 (8分音符) #define SOUND 11 // 圧電スピーカを接続したピン番号(D11) // D11しか反応しない? //Pin assignments for SainSmart LCD Keypad Shield LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //--------------------------------------------- LCDKeypad keypad; //attachInterruptで指定した関数内で変化する変数にはvolatileをつける int localKey = -1; volatile int mode = 1; int before_mode = 1; int first_rice = 5; //米の量、初期設定 単位:合 volatile int open_flag = 0; //蓋開け閉めのフラグ int rice = 0; int sensor = 2; //センサ信号デジタル入力ピン int en_input1 = 1; //ボタン入力受付 int en_input2 = 0; //センサ入力受付 int sleepStatus = 0; // variable to store a request for sleep int count = 0; //sleep用カウンタ volatile int sleep_flg=0; volatile int counter; //wdt用カウンタ int count_max=15; //4second * 15 = 60 second int wait_minutes=60; //wait_time (minutes) int SLAVE_ADDRESS = 0x04; void setup() { lcd.begin(16, 2); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Rice Counter"); lcd.setCursor(0, 1); lcd.print("Start"); delay(2500); rice = first_rice; Serial.begin(9600); // 9600 bpsで通信 Serial.println("Start"); pinMode(sensor, INPUT); //デジタルピン2をinputに設定 pinMode(3, INPUT_PULLUP); //割込み用ピンD3をinputに設定 counter=0; //WDTカウンタ初期化 Wire.begin(SLAVE_ADDRESS); Wire.onRequest(sendAnalogReading); } /////////////////////////////////////////////////////////////////////////////////////////////// //Main Loop /////////////////////////////////////////////////////////////////////////////////////////////// void loop() { localKey = keypad.button(); #if DEBUG //Serial.println(localKey); #endif if(mode == 1){ //mode1:カウンタ #if DEBUG Serial.println("mode1"); #endif lcd.clear(); lcd.setCursor(0, 0); lcd.print("Rice Counter"); lcd.setCursor(0, 1); lcd.print(rice); if(en_input1 == 1){ if(localKey == 1) { //UPボタンで増やす rice += 1; en_input1 = 0; count = 0; }else if(localKey == 2) { //DOWNボタンで減らす #if DEBUG Serial.println("down"); #endif rice -= 1; en_input1 = 0; count = 0; } }else if(localKey == -1){ //UP,DOWNボタンから指を離すと次の入力受付 en_input1 = 1; } //デジタル入力、傾斜センサonでLOW if(en_input2 == 1){ if(digitalRead(sensor) == LOW){ #if DEBUG Serial.println("sensor_on"); #endif rice -= 1; en_input2 = 0; count = 0; delay(1500); if(rice <= 2){ //残り2杯分になると蓋を開け閉めしたときに音を鳴らす speaker(); } } }else if(digitalRead(sensor) == HIGH){ //傾きが戻るまでカウンタ停止 en_input2 = 1; } if(rice == 0) { Serial.println("rice=0"); mode = 2; //残り0で表示固定モードに移行 count = 0; } count++; Serial.println(count); if(count >= 100){ //0.1s*300=30s経過&蓋が閉まっているとsleep count = 0; if(digitalRead(sensor) == HIGH){ mode = 3; } } delay(100); }else if(mode == 2){ //mode2:米の量が0 #if DEBUG Serial.println("mode2"); #endif lcd.clear(); lcd.setCursor(0, 0); lcd.print("Rice Empty"); lcd.setCursor(0, 1); lcd.print(rice); delay(100); before_mode = 2; count++; if(count >= 300){ //30s経過でsleep count = 0; mode = 3; } delay(100); }else if(mode == 3){ //mode3:スリープモード Serial.println("sleep start"); delay(100); sleepNow(); // sleep function called here if(open_flag == 1){ //蓋が空いたので米残量をマイナス1 Serial.println("open_flag"); if(rice != 0){ rice -= 1; } en_input2 = 0; open_flag = 0; } if(rice <= 2){ //米残量2以下なら一定時間ごとに音を鳴らす speaker(); } } } /////////////////////////////////////////////////////////////////////////////////////////////// //Sleep /////////////////////////////////////////////////////////////////////////////////////////////// void sleepNow() //スリープモードの中身 { wdt_set(); //watch dog timer set sleep_flg=1; //enable on sleep flag set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here ADCSRA &= ~(1 << ADEN); // ADC_OFF sleep_enable(); // enables the sleep bit in the mcucr register // so sleep is possible. just a safety pin if(before_mode == 1){ //米残量0のときは傾斜センサで割込み入れない attachInterrupt(0,wakeUpNow1, LOW); // use interrupt 0 (pin 2) and run function // wakeUpNow when pin 2 gets FALLING } attachInterrupt(1,wakeUpNow2, FALLING); //PiのGPIOから割込み、米残量0でも発動 while(sleep_flg){ // BOD設定変更のため,BODSとBODSEに同時に1を出力. MCUCR |= (1 << BODSE)|(1 << BODS); // その後4クロック周期内にBODSに1,BODSEに0を出力してBODをOFFにする. MCUCR = (MCUCR & ~(1 << BODSE))|(1 << BODS); // その後3クロック周期内にスリープ状態へ移行. sleep_mode(); // here the device is actually put to sleep!! // THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP } sleep_disable(); // first thing after waking from sleep: // disable sleep... ADCSRA |= (1 << ADEN); // ADC_ON wdt_unset(); //watch dog timer unset detachInterrupt(0); // disables interrupt 0 on pin 2 so the // wakeUpNow code will not be executed // during normal running time. detachInterrupt(1); } void wakeUpNow1() // 外部割込み後の復帰 { Serial.println("1Wakeup"); //sleep end sleep_flg = 0; //counter reset counter = 0; open_flag = 1; //蓋が空いたので米残量をマイナス1する mode = before_mode; //前回のモードに復帰、何もなければ10s後にスリープ } void wakeUpNow2() // 外部割込み後の復帰(PiのGPIO) //米残量の変更は無し { Serial.println("2Wakeup"); //sleep end sleep_flg = 0; //counter reset counter = 0; mode = before_mode; //前回のモードに復帰、何もしなければ10s後にまたスリープ } /////////////////////////////////////////////////////////////////////////////////////////////// //Watch Dog Timer /////////////////////////////////////////////////////////////////////////////////////////////// //watch dog timer setup void wdt_set() { wdt_reset(); cli(); //disable interrupt MCUSR = 0; WDTCSR |= 0b00011000; //WDCE WDE set WDTCSR = 0b01000000 | 0b100000;//WDIE set |WDIF set scale 4 seconds sei(); //able interrupt } //watch dog timer unset void wdt_unset() { wdt_reset(); cli(); MCUSR = 0; WDTCSR |= 0b00011000; //WDCE WDE set WDTCSR = 0b00000000; //status clear sei(); } //WDTによる復帰 ISR(WDT_vect) { //wdt_reset(); if(sleep_flg == 1) { counter++; Serial.println("wdt"); if( counter >= (count_max * wait_minutes)) //sleepさせたい時間をカウント { //sleep end sleep_flg = 0; //counter reset counter = 0; mode = before_mode; //前回のモードに復帰、何もなければ10s後にスリープ } } else { } } /////////////////////////////////////////////////////////////////////////////////////////////// //Speaker /////////////////////////////////////////////////////////////////////////////////////////////// void speaker() { tone(SOUND,330,BEAT*1.5) ; // ミ delay(BEAT*1.5) ; tone(SOUND,294,BEAT/2) ; // レ delay(BEAT/2) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT*2) ; tone(SOUND,294,BEAT*2) ; // レ- delay(BEAT*4) ; tone(SOUND,330,BEAT*1.5) ; // ミ delay(BEAT*1.5) ; tone(SOUND,294,BEAT/2) ; // レ delay(BEAT/2) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT*2) ; tone(SOUND,294,BEAT*2) ; // レ- delay(BEAT*4) ; tone(SOUND,262,BEAT) ; // ド delay(BEAT) ; tone(SOUND,262,BEAT) ; // ド delay(BEAT) ; tone(SOUND,466,BEAT) ; // シb delay(BEAT) ; tone(SOUND,466,BEAT) ; // シb delay(BEAT) ; tone(SOUND,440,BEAT) ; // ラ delay(BEAT) ; tone(SOUND,440,BEAT) ; // ラ delay(BEAT) ; tone(SOUND,392,BEAT) ; // ソ delay(BEAT) ; tone(SOUND,392,BEAT) ; // ソ delay(BEAT) ; tone(SOUND,349,BEAT) ; // ファ delay(BEAT) ; tone(SOUND,349,BEAT) ; // ファ delay(BEAT) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT) ; tone(SOUND,294,BEAT) ; // レ delay(BEAT) ; tone(SOUND,294,BEAT) ; // レ delay(BEAT) ; tone(SOUND,311,BEAT) ; // レ# delay(BEAT) ; tone(SOUND,311,BEAT) ; // レ# delay(BEAT) ; tone(SOUND,330,BEAT*1.5) ; // ミ delay(BEAT*1.5) ; tone(SOUND,294,BEAT/2) ; // レ delay(BEAT/2) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT*2) ; tone(SOUND,294,BEAT*2) ; // レ- delay(BEAT*4) ; tone(SOUND,330,BEAT*1.5) ; // ミ delay(BEAT*1.5) ; tone(SOUND,294,BEAT/2) ; // レ delay(BEAT/2) ; tone(SOUND,330,BEAT) ; // ミ delay(BEAT*2) ; tone(SOUND,294,BEAT*2) ; // レ- delay(BEAT*4) ; tone(SOUND,262,BEAT) ; // ド delay(BEAT) ; tone(SOUND,262,BEAT) ; // ド delay(BEAT) ; tone(SOUND,466,BEAT) ; // シb delay(BEAT) ; tone(SOUND,466,BEAT) ; // シb delay(BEAT) ; tone(SOUND,440,BEAT) ; // ラ delay(BEAT) ; tone(SOUND,440,BEAT) ; // ラ delay(BEAT) ; tone(SOUND,392,BEAT) ; // ソ delay(BEAT) ; tone(SOUND,392,BEAT) ; // ソ delay(BEAT) ; tone(SOUND,349,BEAT*3) ; // ファ- delay(BEAT*3) ; delay(3000) ; // 3秒後に繰り返す } /////////////////////////////////////////////////////////////////////////////////////////////// //I2C Communication /////////////////////////////////////////////////////////////////////////////////////////////// void sendAnalogReading() { Wire.write(rice); }
次にラズパイ3側のプログラムです。
プログラムを起動するとGPIO18ピンを叩いてArduinoを起動し、起動後のタイミングを狙ってArduinoにI2C通信のリクエストを送信します。
その後Arduinoから米残量のデータを取得し、残量が2以下なら登録したアドレスにメールを送信します。
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) #送信先のアドレスを入力
下記のプログラムはSMTP(Simple Mail Transfer Protocol)によってメールを送信するプログラムです。
上のプログラムのsmtp.send_emaiはこのプログラム内に書いています。
gmailを使ってメールを送信しており、アドレスやパスワードを入力する必要があります。
#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()
まとめ
前回作った炊飯器の残量カウンターにメール送信機能を追加しました。
ラズパイ3と組み合わせ、SMTPでメールを送信します。
今回のシステムにおいての詳細は次回以降の記事にまとめます。