2013年3月22日 星期五

[PIR] 使用人體紅外線感應 (PIR) 模組,製作家中安全防護及警報系統


在這篇文章中,將介紹使用 Python 與 C 語言接收人體紅外線感應 ( PIR) 模組觸發訊號後,發送預警電子郵件給指定的收件人的程式寫作方法。若你是使用智慧型手機的話,開啟收信軟體更能即時的收到預警郵件通知。

最後延伸使用以配合繼電器模組的方式,另外控制照明設備的開或關,若以警報裝置做取代的話,更能達到嚇阻效果!
賣場中的 PIR 模組附有三條杜邦線,依照上圖連接到樹莓派上,完成後就像下圖一樣
PIR 模組與樹莓派的連接

PIR 模組測試:

一開始先簡單的測試 PIR 模組受感應時的觸發訊號,是否可正確的讓樹莓派接收,如能正確輸出就會在命令提示視窗下看到輸出。

所以一開始先將程式碼下載到你的樹莓派的 Home 目錄下。如果您的 Home 目錄下沒有 codes 資料夾請自行建立,等一下檔案會直接下載並自行解開到所在目錄下,在程式說明時可以再另一視窗開啟程式內容做為比較。

請輸入下面指令取得程式原始碼:
pi@raspberrypi ~ $ cd codes
pi@raspberrypi ~/codes $ ls -l
pi@raspberrypi ~/codes $ mkdir PIR
pi@raspberrypi ~/codes $ cd PIR
pi@raspberrypi ~/codes/PIR $  wget -O - http://goo.gl/9C5n3 | tar -xvf -
--2013-03-21 12:46:43--  http://goo.gl/9C5n3
正在查找主機 goo.gl (goo.gl)... 74.125.31.101, 74.125.31.102, 74.125.31.113, ...
正在連接 goo.gl (goo.gl)|74.125.31.101|:80... 連上了。
已送出 HTTP 要求,正在等候回應... 301 Moved Permanently
位置: http://dl.dropbox.com/s/ig42n7n599x5y6k/PIR_Codes_RPi.tar [跟隨至新的 URL]
--2013-03-21 12:46:43--  http://dl.dropbox.com/s/ig42n7n599x5y6k/PIR_Codes_RPi.tar
正在查找主機 dl.dropbox.com (dl.dropbox.com)... 54.243.103.51
正在連接 dl.dropbox.com (dl.dropbox.com)|54.243.103.51|:80... 連上了。
已送出 HTTP 要求,正在等候回應... 200 OK
長度: 61440 (60K) [application/x-tar]
Saving to: `STDOUT'

 0% [                                                             ] 0           --.-K/s              PIR_Relay_sendemail.c
PIR_Relay_sendemail.py
25% [==============>                                              ] 15,555      64.7K/s              PIR_sendemail.c
PIR_sendemail.py
PIR_test
100%[============================================================>] 61,440       125K/s   in 0.5s

2013-03-21 12:46:45 (125 KB/s) - written to stdout [61440/61440]

PIR_test.c
PIR_test.py

pi@raspberrypi ~/codes/PIR $ ls -l
總計 68
-rwxr-xr-x 1 pi pi  6832  3月 21 12:42 PIR_Relay_sendemail.c
-rwxr-xr-x 1 pi pi  2411  3月 21 12:42 PIR_Relay_sendemail.py
-rwxr-xr-x 1 pi pi  6329  3月 21 12:42 PIR_sendemail.c
-rwxr-xr-x 1 pi pi  2004  3月 21 12:42 PIR_sendemail.py
-rwxr-xr-x 1 pi pi 34455  3月 21 12:42 PIR_test
-rwxr-xr-x 1 pi pi  2347  3月 21 12:42 PIR_test.c
-rwxr-xr-x 1 pi pi   922  3月 21 12:42 PIR_test.py
pi@raspberrypi ~/codes/PIR $

~/codes/PIR 目錄下有七個檔案,沒有附檔名的檔案是 *.c 檔案編譯之後的執行檔;但其中兩個 *.c 檔案未做編譯,這要先設定郵件信箱資料資料才能做編譯,下一段會說到。

在列出程式碼之前,先做 Python 與 C 語言程式碼的測試,確認 PIR 能夠正常動作。

輸入下面指令,先測試 C 編譯後執行檔
pi@raspberrypi ~/codes/PIR $ sudo ./PIR_test

      Waitting for PIR to stable, 1 sec

  READY ...

PIR sensor get ready for movement detection

程式會開始倒數 3、2、1 秒,完成之後就會開始以迴圈的方式一直讀取 PIR 模組的觸發輸出訊號。這時用手在 PIR 前方晃晃,PIR 模組就會觸發,當樹莓派接收訊號時,上面最後一行的文字就會變成 "THE PIR SENSOR DETECTED MOVEMENT"
pi@raspberrypi ~/codes/PIR $ sudo ./PIR_test

      Waitting for PIR to stable, 1 sec

  READY ...

THE PIR SENSOR DETECTED MOVEMENT

要結束程式請按下 "Ctrl + C"

程式一開始的延遲時間是為了讓 PIR 模組穩定用的,這段穩定時間只用在 PIR 模組一通上電後的 20 ~ 60 秒 (一般設 20 秒就夠了),但因為 PIR 模組在這個例子插上後不會馬上使用,所以這段穩定時間可有可無,設 3 秒是意思意思,主要是為了讓閱讀程式碼的人能夠了解 PIR 模組使用上有這個限制!

不過一旦你使用接腳來控制 PIR 模組電源,每一次開啟就必須要設這段穩定時間,沒有這段穩定時間會造成一開始的接收訊號不正確,造成誤動作,這很重要的!

接著,輸入下面指令執行 Python 程式碼的測試:
pi@raspberrypi ~/codes/PIR $ sudo python PIR_test.py

      Waitting for PIR to stable, 1 sec

  READY ...

PIR sensor get ready for movement detection

程式執行的結果跟 C 語言的一樣。同樣的也用手在 PIR 模組前面晃晃,最後一行的文字就會產生變化。要結束就按下 "Ctrl + C" ,比較不同的是,會另外出現 "Quit!" 的文字,告訴你離開程式了。
pi@raspberrypi ~/codes/PIR $ sudo python PIR_test.py

      Waitting for PIR to stable, 1 sec

  READY ...

PIR sensor get ready for movement detection

  Quit!

pi@raspberrypi ~/codes/PIR $

上面就是兩個不同程式語言測試的結果。

兩個程式原始碼如下:

C 語言
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>  // printf, scanf
#include <stdlib.h>
#include <bcm2835.h>

#define     PIN_PIR     18
#define     STB_TIME    3


int main(int argc, char** argv)
{    
    // bcm2835 函式庫初始化
    if(!bcm2835_init())
        return 1;

    // IO 初始化與初始狀態設定
    bcm2835_gpio_fsel(PIN_PIR, BCM2835_GPIO_FSEL_INPT);
    // pull-down the control pin of PIR
    bcm2835_gpio_set_pud(PIN_PIR, BCM2835_GPIO_PUD_DOWN);
    
    // PIR 接上電源後須要等待 20 秒才會穩定,穩定時間內會有不穩定的輸出,
    // 因此不需要偵測此時的 PIR 狀態
    printf("\n");
    for(int i = 0; i < STB_TIME; i++)
    {
        bcm2835_delay(1000);
        printf("      Waitting for PIR to stable, %d sec\r", STB_TIME - i);
        fflush(stdout);
    }
    
    printf("\n\n  READY ...\n\n");
    
    while(1)
    {
        // 現在可以用手或是身體在 PIR 前面晃動
        if(bcm2835_gpio_lev(PIN_PIR))
        {
            printf("THE PIR SENSOR DETECTED MOVEMENT           \r");
            fflush(stdout);
            bcm2835_delay(3000);    // 此時間的設定必須要大於 PIR 的延遲時間
        }
        else
            printf("PIR sensor get ready for movement detection\r");    
    }
    
    bcm2835_close();
    return 0;
}

Line 05 - 06:使用樹莓派 GPIO #18 做為 PIR 模組的輸入接腳
Line 15 - 18:設定 GPIO #18 為輸入,並設為下拉 (與 GND 接在一起);也就是 GPIO #18 無輸入訊號時所讀取到的輸入為 0
Line 23 - 28:PIR 模組啟用暖機時間倒數
Line 32 - 43:PIR 模組暖機完成,樹莓派可以開始接收輸入訊號。函數 fflush(stdout) 是將 stdout 裡的東西強制輸出緩衝並清空,沒有這一行,文字 " THE PIR SENSOR DETECTED MOVEMENT" 是不會被輸出的,你可以試試!

上面那兩個程式如果有做修改需要重新編譯,就輸入下面指令:
sudo gcc PIR_test.c -l rt -l bcm2835 -std=gnu99 -o PIR_test


Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/python
#!-*- coding: utf-8 -*-

import time
import sys
import RPi.GPIO as GPIO

# 使用 BCM GPIO 取代實際接腳號碼
GPIO.setmode(GPIO.BCM)
# 定義樹莓派連接到 PIR 的接腳
PIN_PIR = 18
STB_TIME = 3

try:
    
    GPIO.setup(PIN_PIR, GPIO.IN)
    print "\n",
    for i in range(0, STB_TIME):
        print "      Waitting for PIR to stable, %d sec\r" %(STB_TIME - i),
        sys.stdout.flush()
        time.sleep(1)
    
    print "\n"
    print "  READY ...",
    print "\n"
    
    # 按下 Ctrl + C 離開
    while True:
        if(GPIO.input(PIN_PIR)):
            print "THE PIR SENSOR DETECTED MOVEMENT           \r",
            sys.stdout.flush()
            time.sleep(3)   # 此時間的設定必須要大於 PIR 的延遲時間
        else:
            print "PIR sensor get ready for movement detection\r",            
    
except KeyboardInterrupt:    
    print "\n\n  Quit!  \n"

Python 的程式碼就不再講了,因為兩個程式架構幾乎一樣,所不同的是:當您按下 "Ctrl + C" 時的中斷訊號會被程式碼的 "KeyboardInterrupt" 截取到,因此會輸出 "Quit!" 文字。


加入電子郵件傳送功能:

發送電子郵件必須先知道要使用哪個電子郵件伺服器發送,因此郵件伺服器的網址以及所用的 PORT 號碼也必須先知道,另外就是需不需要使用者的名稱與密碼才能登入 ?

延續上面兩個程式,各在其中加入傳送電子郵件功能,所使用的郵件伺服器是 GMail,如果你之後要使用其他郵件伺服器的話,就必須自己再做其他修改。不過如果你使用智慧型手機 ( Android ) 又需要即時的郵件通知,那真的建議你使用 GMail 做郵件伺服器。

接下來,我們就以 GMail 郵件伺服器做例子 ( 其他郵件伺服器設定也大同小異 ),先看看 GMail 郵件伺服器連線的提示:在郵件用戶端內設定 POP

內送郵件 (POP3) 伺服器 - 需要安全資料傳輸層 (SSL):pop.gmail.com
使用安全資料傳輸層 (SSL):是
通訊埠:995
外寄郵件 (SMTP) 伺服器 - 需要 TLS 或安全資料傳輸層 (SSL):smtp.gmail.com
使用驗證:是
TLS/STARTTLS 通訊埠:587
安全資料傳輸層 (SSL) 通訊埠:465
伺服器逾時1 分鐘以上,建議設定為 5 分鐘
姓名或顯示名稱:[您的姓名]
帳戶名稱或使用者名稱:您的完整電子郵件地址 (包含 @Gmail.com 或 @<您的網域>.com )
電子郵件地址:您的電子郵件地址 (使用者名稱@gmail.com 或使用者名稱@您的網域.com)
密碼:您的 Gmail 密碼

上面表格比較重要的是第二個欄位,使用需驗證的外部郵件伺服器 smtp.gmail.com,TLS/STARTTLS 通訊埠號碼為 586,這兩個就是 GMail 外送郵件伺服器連線的資訊,請記下來,下面就會用到。GMail 使用者帳號與密碼就是只有你才會知道的資料,等一下也請一併輸入。

我們先假設一個虛擬的帳號與密碼,讓說明方便一些。實際使用上,除了伺服器的資訊之外,帳號與密碼請輸入你自己的來做取代。

外送郵件伺服器                       :smtp.gmail.com
TLS/STARTTLS 通訊埠            :587
使用者名稱 ( GMAIL_USER ):proteus
使用者密碼 ( GMAIL_PASS ) :1234
寄給誰         ( TO )                    :someone@mail.com
郵件主旨     ( SUBJECT )       :Intrusion!!
郵件內文     ( TEXT )               :Your PIR sensor detected movement

有了上面的資訊之後,只要將發送電子郵件的程式碼寫好,做成 send_email() 副程式來呼叫就可以了。


Python  - 傳送電子郵件

樹莓派本身已有安裝 Python 以及 smtplib 函式庫,因此在使用時只要在檔案開頭處將其引入之後就可以開始使用
import smtplib

接著將電子郵件傳送的相關資料定義在程式開頭處
TO = 'someone@mail.com'
GMAIL_USER = 'proteus'
GMAIL_PASS = '1234'
SUBJECT = 'Intrusion!!'
TEXT = 'Your PIR sensor detected movement'

傳送電子郵件有一定的傳送格式 ( 請參閱 RFC 5322 - Internet Message Format,或電子郵件標頭解析 ),需要將上面的資料放在一塊後做成郵件資料再一起發送。

連線伺服器 --> 登入 --> 傳送郵件資料 --> 離線伺服器

使用 Python 撰寫比較直覺,程式用不了幾行
 1 def send_email():
 2    print "\n"
 3    print "Sending Email"
 4    smtpserver = smtplib.SMTP("smtp.gmail.com",587) # 不是使用 GMail 的要修改這裡
 5    smtpserver.ehlo()
 6    smtpserver.starttls()
 7    smtpserver.ehlo
 8    smtpserver.login(GMAIL_USER, GMAIL_PASS)
 9     header = 'To:' + TO + '\n' + 'From: ' + GMAIL_USER
10    header = header + '\n' + 'Subject:' + SUBJECT + '\n'
11    print header
12    msg = header + '\n' + TEXT + ' \n\n'
13    smtpserver.sendmail(GMAIL_USER, TO, msg)
14    smtpserver.close()

Line 04:GMail 外送郵件伺服器的位址與 PORT 的號碼,這裡是分開輸入的

主程式的地方,只要加入 send_email() 這行在 time.sleep(3) 上方就完成增加傳送電子郵件的功能
    while True:
        if(GPIO.input(PIN_PIR)):
            print "THE PIR SENSOR DETECTED MOVEMENT           \r",
            sys.stdout.flush()
            # 如果要傳送電子郵件的話,就將下一行前面的 # 拿掉
            send_email()
            time.sleep(3)   # 此時間的設定必須要大於 PIR 的延遲時間
        else:
            print "PIR sensor get ready for movement detection\r", 

一旦程式接收到 PIR 模組的觸發訊號,就會開始傳送電子郵件
pi@raspberrypi ~/codes/PIR $ sudo python PIR_sendmail.py

      Waitting >for PIR to stable, 1 sec

  READY ...

THE PIR SENSOR DETECTED MOVEMENT

Sending Email
To:someone@mail.com
From: proteus
Subject:Intrusion!!

PIR sensor get ready for movement detection

  Quit!

pi@raspberrypi ~/codes/PIR $


C  - 傳送電子郵件

使用 C 語言傳送電子郵件,我所選擇的函式庫是 libcurl,原因是看起來比較直覺,設定上與 Python 方式差不多,即便是程式碼撰寫 ( 跟 Python 比 ) 長了一點,但比較其他多個函式庫後,這個是最容易使用且功能很強的。

輸入下面指令進行函式庫的安裝 ( 如果您超過一星期未更新過系統了,第一行就一定要做 ),順便也更新一下 Python 的 RPi.GPIO 函式庫 ( 版本 0.5.1a,這一版支援中斷,請看 RasPi.TV 的 說明 "How to use interrupts with Python on the Raspberry Pi and RPi.GPIO",若是使用中斷的方式,可以省下很多的 CPU 時間,而不用使用像本文所使用的輪詢方式,效率會更好!這部分就請看倌自己弄了!)。請進行相關套件的安裝:
pi@raspberrypi ~ $ sudo apt-get update && sudo apt-get upgrade -y
... << 中間省略 >> ...
pi@raspberrypi ~ $ sudo apt-get install python-rpi.gpio
正在讀取套件清單... 完成
正在重建相依關係
正在讀取狀態資料... 完成
The following packages were automatically installed and are no longer required:
  icelib libblas3gf libdns81 libisc83 liblapack3gf libqt4-dbus libtinfo-dev qdbus
Use 'apt-get autoremove' to remove them.
下列【新】套件將會被安裝:
  python-rpi.gpio
升級 0 個,新安裝 1 個,移除 0 個,有 0 個未被升級。
需要下載 28.2 kB 的套件檔。
此操作完成之後,會多佔用 158 kB 的磁碟空間。
下載:1 http://archive.raspberrypi.org/debian/ wheezy/main python-rpi.gpio armhf 0.5.0a-1 [28.2 kB]
取得 28.2 kB 用了 1s (15.6 kB/s)
選取了原先未選的套件 python-rpi.gpio。
(讀取資料庫 ... 目前共安裝了 63447 個檔案和目錄。)
解開 python-rpi.gpio(從 .../python-rpi.gpio_0.5.0a-1_armhf.deb)...
設定 python-rpi.gpio (0.5.0a-1) ...
pi@raspberrypi ~ $ sudo apt-get install libcurl4-openssl-dev
正在讀取套件清單... 完成
正在重建相依關係
正在讀取狀態資料... 完成
The following packages were automatically installed and are no longer required:
  icelib libblas3gf libdns81 libisc83 liblapack3gf libqt4-dbus libtinfo-dev qdbus
Use 'apt-get autoremove' to remove them.
下列的額外套件將被安裝:
  comerr-dev krb5-multidev libgcrypt11-dev libgnutls-dev libgnutls-openssl27 libgnutlsxx27
  libgpg-error-dev libgssrpc4 libidn11-dev libkadm5clnt-mit8 libkadm5srv-mit8 libkdb5-6
  libkrb5-dev libldap2-dev libp11-kit-dev librtmp-dev libssh2-1-dev libtasn1-3-dev
建議套件:
  doc-base krb5-doc libcurl3-dbg libgcrypt11-doc gnutls26-doc krb5-user
下列【新】套件將會被安裝:
  comerr-dev krb5-multidev libcurl4-openssl-dev libgcrypt11-dev libgnutls-dev
  libgnutls-openssl27 libgnutlsxx27 libgpg-error-dev libgssrpc4 libidn11-dev libkadm5clnt-mit8
  libkadm5srv-mit8 libkdb5-6 libkrb5-dev libldap2-dev libp11-kit-dev librtmp-dev libssh2-1-dev
  libtasn1-3-dev
升級 0 個,新安裝 19 個,移除 0 個,有 0 個未被升級。
需要下載 5,323 kB 的套件檔。
此操作完成之後,會多佔用 11.2 MB 的磁碟空間。
是否繼續進行 [Y/n]?Y
... << 中間省略 >> ...
pi@raspberrypi ~ $

libcurl 的功能以及範例程式很多,在它的官網 libcurl - small example snippets 可以看到,網頁右邊列出所有的範例程式,請點擊 smtp_.tls.c 並看一下它裡面的內容,我們將改寫這個程式成為 send_email() 副程式。

在 PIR_test.c 程式的標頭檔定義的地方,加入
#include <curl/curl.h>  // libcurl 標頭檔

同樣的,在程式最前面定義幾個會用到的資料,比 Python 定義的多出一個 CC
#define GMAIL_USER  "proteus"             // GMail 帳號
#define GMAIL_PASS  "1234"                // GMail 密碼
#define TO          "<someone@mail.com>"  // GMail 信箱
// 傳送給何人
// define CC          "<another@mail.com>"      // 如果有副本寄件人的話就把前面的雙斜線拿掉
#define SUBJECT     "Intrusion!!"
#define TEXT        "Your PIR sensor detected movement"

main() 的上方輸入下面程式碼,這是結合上面資料的郵件訊息內容,有些內容因為沒用到我加上了雙斜線取消掉,不過你可以對照一下 smtp_tls.c 原始程式內容,就會知道有哪裡不同
static const char *payload_text[]={
  "To: " TO "\n",
  "From: " GMAIL_USER "\n",
  //"Cc: " CC "\n",     // 如果有副本寄件人的話就把前面的雙斜線拿掉
  "Subject: " SUBJECT "\n",
  "\n", /* empty line to divide headers from body, see RFC5322 */
  // 下面開始就是文件內文  
  //"The body of the message starts here.\n",
  //"\n",
  //"It could be a lot of lines, could be MIME encoded, whatever.\n",
  //"Check RFC5322.\n",
  TEXT,
  NULL    
};

下面是兩個 send_email() 裡會用到的副程式,主要是 payload_text 陣列裡的字數計算
struct upload_status {
  int lines_read;
};
 
static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp)
{
  struct upload_status *upload_ctx = (struct upload_status *)userp;
  const char *data;
 
  if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) {
    return 0;
  }
 
  data = payload_text[upload_ctx->lines_read];
 
  if (data) {
    size_t len = strlen(data);
    memcpy(ptr, data, len);
    upload_ctx->lines_read ++;
    return len;
  }
  return 0;
}

下面就是 send_email() 的程式碼,需要修改的地方在程式碼下方做說明
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void send_email(void)
{
    CURL *curl;
    CURLcode res;
    struct curl_slist *recipients = NULL;
    struct upload_status upload_ctx;
    
    upload_ctx.lines_read = 0;
    
    curl = curl_easy_init();
    if (curl) {
        /* 設定郵件伺服器,請注意到網址後面要加上 587 取代 SMTP 正常使用的 PORT 25
         * ,Port 587 是常用於安全郵件的提交, 但是這裡需視所使用的郵件伺服器加以設定
         *  以符合自己的情形 */ 
        curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.gmail.com:587");
        
        /* In this example, we'll start with a plain text connection, and upgrade
         * to Transport Layer Security (TLS) using the STARTTLS command. Be careful
         * of using CURLUSESSL_TRY here, because if TLS upgrade fails, the transfer
         * will continue anyway - see the security discussion in the libcurl
         * tutorial for more details. */ 
        curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
        
        /* 因為是使用 Gmail 伺服器所以要登入之後才能使用*/ 
        curl_easy_setopt(curl, CURLOPT_USERNAME, GMAIL_USER);   // GMAIL_USER
        curl_easy_setopt(curl, CURLOPT_PASSWORD, GMAIL_PASS);   // GMAIL_PASS
        
        /* value for envelope reverse-path */ 
        curl_easy_setopt(curl, CURLOPT_MAIL_FROM, GMAIL_USER);  // GMAIL_USER
        /* Add two recipients, in this particular case they correspond to the
         * To: and Cc: addressees in the header, but they could be any kind of
         * recipient. */ 
        recipients = curl_slist_append(recipients, TO);
        //recipients = curl_slist_append(recipients, CC);   // 如果有副本寄件人的話就把前面的雙斜線拿掉
        curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
        
        /* In this case, we're using a callback function to specify the data. You
         * could just use the CURLOPT_READDATA option to specify a FILE pointer to
         * read from.
         */ 
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
        curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
        
        /* Since the traffic will be encrypted, it is very useful to turn on debug
         * information within libcurl to see what is happening during the transfer.
         */ 
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        
        /* send the message (including headers) */ 
        res = curl_easy_perform(curl);
        /* Check for errors */ 
        if(res != CURLE_OK)
          fprintf(stderr, "curl_easy_perform() failed: %s\n",
                  curl_easy_strerror(res));
        
        /* free the list of recipients and clean up */ 
        curl_slist_free_all(recipients);
        curl_easy_cleanup(curl);
    }
    printf("\n\n");
}

Line 15:GMail 外送郵件伺服器的位址和 PORT 號碼,這裡兩個是寫在一起的,因此要輸入 smtp://smtp.gmail.com:587,PORT 號碼接在網址後面要在前面加上冒號 (:)
Line 25 - 26:登入 GMail 郵件伺服器的使用者資訊,這裡使用我們自己定義的使用者名稱 ( GMAIL_USER ) 與密碼 ( GMAIL_PASS )
Line 29:輸入寄件者的名稱,也就是回信的位址。因為是使用 GMail,所以只要輸入使用者名稱 ( GMAIL_USER ) 即可
Line 34:這一行主要是用在寄送郵件副本的,如果有使用到就將前方的雙斜線移除掉,就可在發送郵件時另外寄送副本給指定的郵件帳戶。

主程式的部分,也是增加 send_email() 在時間函數 bcm2835_delay(3000) 的上方就可以了
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    while(1)
    {
        // 現在可以用手或是身體在 PIR 前面晃動
        if(bcm2835_gpio_lev(PIN_PIR))
        {
            printf("THE PIR SENSOR DETECTED MOVEMENT           \r\n\n");            
            fflush(stdout);
            send_email();
            bcm2835_delay(3000);            // 此時間的設定必須要大於 PIR 的延遲時間
        }
        else            
            printf("PIR sensor get ready for movement detection\r");        
    }

完成程式修改之後,記得這程式使用 libcurl 函式庫,因此原本的編譯方式變成
sudo gcc PIR_sendemail.c -l rt -l bcm2835 -l curl -std=gnu99 -o PIR_sendemail

編譯好之後就可以做測試,一旦 PIR 模組觸發,就會開始與外寄郵件伺服器連線,並開始在視窗中輸出兩方通訊訊息。要結束就 "Ctrl + C"
pi@raspberrypi ~/codes/PIR $ sudo ./PIR_sendemail

      Waitting for PIR to stable, 1 sec

  READY ...

THE PIR SENSOR DETECTED MOVEMENT

* About to connect() to smtp.gmail.com port 587 (#0)
*   Trying 74.125.142.108...
* connected
* Connected to smtp.gmail.com (74.125.142.108) port 587 (#0)
* SMTP 0x1f52218 state change from STOP to SERVERGREET
< 220 mx.google.com ESMTP s8sm14378176igs.0 - gsmtp
> EHLO raspberrypi
* SMTP 0x1f52218 state change from SERVERGREET to EHLO
< 250-mx.google.com at your service, [36.239.198.208]

* Closing connection #0

... << 中間省略 >> ...

PIR sensor get ready for movement

pi@raspberrypi ~/codes/PIR $


加入繼電器開關功能:

最後一個部份就是加入了繼電器的功能。繼電器可以外接不同電壓的設備,只要在其額定限制之下都可以 (例如,蜂鳴器、馬達和電燈等),可以依自己的需求加入所要控制的設備。

使用 PIR 模組的觸發訊號,除了上一節中加入了發送電子郵件的功能,這一節就是加入繼電器來控制交流電源的開或關,交流電源與電燈接在一起。詳細的電路接線圖如下:
使用樹莓派連接 PIR 模組與繼電器控制電燈開或關的線路圖

根據上面的線路圖,先將繼電器與 PIR 模組接到樹莓派上去;除非你有把握能一次將線接好,不然接好之後確認沒問題之後,再插上網路線與電源開機
PIR 模組與繼電器模組與樹莓派的接線

好了之後就可以開始測試加入了繼電器功能的程式了!

C - 繼電器控制電燈開或關

繼續上一節的程式,加入繼電器部分所要加入的程式碼不多,可以開啟 PIR_Relay_sendemail.c 做對照。

首先,在 STD_TIME 的下方輸入繼電器控制接腳的定義
#define STB_TIME    3
#define PIN_RELAY   23  // 定義樹莓派控制繼電器模組的接腳

mail() 主程式開頭處,將接腳 GPIO #23 設為輸出且初始狀態為 HIGH ( 還記得賣場的繼電器模組是低準位觸發嗎 ? )
    // IO 初始化與初始狀態設定
    bcm2835_gpio_fsel(PIN_PIR, BCM2835_GPIO_FSEL_INPT);
    bcm2835_gpio_fsel(PIN_RELAY, BCM2835_GPIO_FSEL_OUTP);   //定義連接繼電器的接腳為輸出
    // pull-down the control pin of PIR
    bcm2835_gpio_set_pud(PIN_PIR, BCM2835_GPIO_PUD_DOWN);
    bcm2835_gpio_write(PIN_RELAY, HIGH);                     // 設定繼電器初始狀態

接著在無窮迴圈 while(1) 裡加入控制繼電器模組開或關的指令 (也就是下面的 Line 9 和 Line 14),並加入 fflush (stdio) 在 Line 16,加入繼電器的功能就算完成。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    while(1)
    {
        // 現在可以用手或是身體在 PIR 前面晃動
        if(bcm2835_gpio_lev(PIN_PIR))
        {
            printf("THE PIR SENSOR DETECTED MOVEMENT           \r\n\n");            
            fflush(stdout);
            send_email();
            bcm2835_gpio_write(PIN_RELAY, LOW); // 繼電器輸出
            bcm2835_delay(3000);            // 此時間的設定必須要大於 PIR 的延遲時間
        }
        else
        {
            bcm2835_gpio_write(PIN_RELAY, HIGH);  // 繼電器輸出
            printf("PIR sensor get ready for movement detection\r");
            fflush(stdout);
        }            
    }

程式的編譯請在程式所在目錄下輸入下面指令
sudo gcc PIR_sendemail.c -l rt -l bcm2835 -l curl -std=gnu99 -o PIR_sendemail

程式碼的測試結果與上一節的一樣,所以就不再列出,你可以自行執行之後看看繼電器的變化,加上電燈的測試,等 Python 程式說明完了之後再一起做。

Python - 繼電器控制電燈開或關

繼續上一節的程式,加入繼電器部分所要加入的程式碼不多,可以開啟 PIR_Relay_sendemail.py 做對照。

在 Python 前方 STM_TIME 下面定義繼電器的接腳
STB_TIME = 3
PIN_RELAY = 23  # 定義樹莓派控制繼電器模組的接腳

try 區段中,初始化 PIN_RELAY 接腳為輸出,初始狀態為 HIGH
try:
    
    GPIO.setup(PIN_PIR, GPIO.IN)
    GPIO.setup(PIN_RELAY, GPIO.OUT, GPIO.HIGH) # 定義連接繼電器的接腳為輸出,初始狀態為 HIGH

繼續在 try 區塊的無窮迴圈 while 裡,time.sleep(3) 上面加入輸出高準位的指令 GPIO.output(PIN_RELAY, HIGH),並在 else 下輸入一行低準位輸出的指令 GPIO.output(PIN_RELAY, LOW)
    while True:
        if(GPIO.input(PIN_PIR)):
            print "THE PIR SENSOR DETECTED MOVEMENT           \r",
            sys.stdout.flush()
            send_email()
            GPIO.output(PIN_RELAY, GPIO.LOW)   # 繼電器輸出
            time.sleep(3)                       # 此時間的設定必須要大於 PIR 的延遲時間
        else:
            GPIO.output(PIN_RELAY, GPIO.HIGH)    # 繼電器輸出
            print "PIR sensor get ready for movement detection\r",

依照上面的說明,Python 程式碼就算完成了,接下來就是測試。

測試:

程式碼的執行,可參考上一兩節的說明,現在要做的就是將電燈的電源線與繼電器相連接。

交流電很危險,若對接線沒把握,請有經驗的人幫您做確認,確認沒問題之後再插上插座通電 !!

繼電器與電燈的接線如下圖所示,白色線是同一條線切斷之後接上端子在接到繼電器端子座的 K2-NO 與 K2-COM 處
繼電器與 AC 電源接線近觀

當你開始執行程式的時候,一旦 PIR 模組被觸發,電燈就會像下圖一樣亮起一段時間之後再熄滅

以上就是樹莓派與 PIR 模組使用方式與範例說明,那你是怎麼玩的呢 ?


返回 "人體紅外線 (PIR) 感應模組系列文章" 看其他單元


<<樹莓派編輯環境設置系列文章>>

5 則留言:

  1. 請問程式執行後出現下面這個錯誤訊息該麼解決?
    Traceback (most recent call last):
    File "/home/pi/Desktop/PIR_test.py", line 41, in
    GPIO.setup(PIN_RELAY, GPIO.OUT, GPIO.HIGH) # 定義連接繼電器的接腳為輸出,初始狀態為 HIGH
    ValueError: pull_up_down parameter is not valid for outputs

    回覆刪除
    回覆
    1. 網頁中的 python 程式或許會因為版本的更新讓函式使用有所不同,但是可以先查閱一下該函式庫的使用說明網頁 https://sourceforge.net/p/raspberry-gpio-python/wiki/BasicUsage/ 就可以知道不同的地方。
      試試改成:GPIO.setup(PIN_RELAY, GPIO.OUT, initial=GPIO.HIGH)

      刪除
    2. 大大您好:
      我是最近剛學 Arduino 的新手, 請問您 樹莓派的主板 和 Arduino有一樣嗎? 因為我也想用Arduino的主板和PIR Sensor模組 做一個 警報發送郵件 電路 和LED都有感應作動 但我不會寫發送郵件的程式, 想說如果有一樣的話, 可以照抄你的程式碼到Arduino編譯上傳嗎

      刪除
    3. 兩者是不一樣的! 如果要單獨用 Arduino 發送電子郵件的話,可以外掛網路模組或是 ESP8266 來做!

      刪除

留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !