# Nginx+Mariadb+PHP+Python

雖然透過ThingSpeak呈現溫濕度圖表非常的方便，只要註冊申請帳號，透過api傳送資料到ThingSpeak，就會自動把圖畫出來，但在一些特殊狀況下，如沒有對外的網路，那就沒有辦法把資料上傳到ThingSpeak，所以此篇在Raspberry Pi上建立Nginx+Mariadb，透過Python讀取DHT22溫濕度後把資料寫到Mariadb資料庫，然後讀取資料庫中的歷史資料，用python + bokeh + pandas產生圖表網頁，讓使用者可以連到本機的網站觀看目前Raspberry Pi的溫濕度狀況，如下圖…\
![](/files/-LdydigzViGh8FYSe-eH)

目前把整個溫濕度所需要安裝的套件，設定的內容及程式原始碼，透過Ansible去處理，使用方式和moodlebox一樣，使用[moodlebox](https://github.com/moodlebox/moodlebox)的原始程式，修改成go38box，整個ansible的資料夾架構及設定方式與moodlebox相同，安裝方式請參考

* [Building the Go38Box](/raspberry-pi-temperature/build-go38box.md)
* [Mac 安裝 Ansible](/raspberry-pi-temperature/mac_install_ansible.md)
* [透過Ansible安裝Go38Box](/raspberry-pi-temperature/ansible_playbook.md)

## 網站架構

溫濕度網站的架構非常的簡單，就只是一般的網頁，此網頁樣版來自於<https://templated.co/azure>，只做小部分修改，在溫濕度圖的地方使用兩個iFrame把溫度與濕度的html嵌入到首頁中，然後固定時間會重新載入溫濕度的html檔，所以在整個網站顯示的部分沒什麼特別的程式，也沒有使用到php，會安裝php主要是因為使用phpmyadmin去管理設定資料庫

* 溫濕度網址：<http://go38box.local>
* PhpMyAdmin網址：<http://go38box.local/phpmyadmin>

### 相關檔案

* 網站路徑：`/var/www/html/temperature`
* 首頁：index.html&#x20;
* 溫度：db\_temp.html&#x20;
* 濕度：db\_humidity.html

這邊要注意的部分為db\_temp.html及db\_humidity.html兩個檔案，此兩個檔案為link，來源為`/home/go38box/DHT22/db_temp.html`\
及`/home/go38box/DHT22/db_humidity.html`，另外phpmyadmin也是用link的方式建立，來源為`/usr/share/phpmyadmin`，檔案清單如下：

```
go38box@go38box:/var/www/html/temperature $ ls -al
total 52
drwxrwxr-x 6 www-data www-data  4096 Apr 26 00:33 .
drwxr-xr-x 3 root     root      4096 Apr 26 00:33 ..
drwxrwxr-x 3 www-data www-data  4096 Apr 26 00:33 css
lrwxrwxrwx 1 root     root        36 Apr 26 00:33 db_humidity.html -> /home/go38box/DHT22/db_humidity.html
lrwxrwxrwx 1 root     root        32 Apr 26 00:33 db_temp.html -> /home/go38box/DHT22/db_temp.html
drwxrwxr-x 8 www-data www-data  4096 Apr 26 00:33 .git
drwxrwxr-x 2 www-data www-data  4096 Apr 26 00:33 images
-rw-rw-r-- 1 www-data www-data  2199 Apr 26 00:33 index.html
drwxrwxr-x 2 www-data www-data  4096 Apr 26 00:33 js
-rw-rw-r-- 1 www-data www-data 17128 Apr 26 00:33 LICENSE.txt
lrwxrwxrwx 1 root     root        21 Apr 26 00:33 phpmyadmin -> /usr/share/phpmyadmin
-rw-rw-r-- 1 www-data www-data    12 Apr 26 00:33 README.md
go38box@go38box:/var/www/html/temperature $
```

#### index.html HEAD

下方內容放在`<head>`內，主要功能為固定時間讓iframe重新載入來更新溫濕度，setInterval為設定多久執行一次，設定方式為(秒\*1000)，如下(60秒\*1000)，就是每分鐘重新載入一次

```
<script language="javascript">
    one=function(){
            iftemp.location.reload()
            ifhumidity.location.reload()
    }
    setInterval("one()",60000)
</script>
```

#### index.html BODY

下方內容放在\<body>內，主要顯示溫濕度的html檔，有兩個\<iframe>，name分別為iftemp及ifhumidity，這個名稱需要和\<head>的設定相同

```
<section class="content">
    <h2>溫濕度計</h2>
    <iframe name="iftemp" src="../db_temp.html" width="550px" height="260px" frameborder="0" scrolling="no"></iframe>
    <iframe name="ifhumidity" src="../db_humidity.html" width="550px" height="260px" frameborder="0" scrolling="no"></iframe>
</section>
```

### Nginx設定

設定檔「`/etc/nginx/sites-available/default`」，內容如下，主要修改了root，預設的網站根目錄為「/var/www/html」目前修改成「`/var/www/html/temperature`」，用ansible安裝是不需要再手動設定

```
#
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html/temperature;

        index index.php index.html index.htm index.nginx-debian.html;

        server_name go38box;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ [^/]\.php(/|$) {
                include fastcgi_params;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_read_timeout    300;
                fastcgi_pass    unix:/var/run/php/php7.0-fpm.sock;
                fastcgi_index   index.php;
                fastcgi_param   PATH_INFO       $fastcgi_path_info;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param   PHP_VALUE "max_execution_time=300\n upload_max_filesize=50M\n post_max_size=50M";
                client_max_body_size    50M;
        }

}
```

## 資料庫

DHT22所讀取的溫濕度會寫到Raspberry PI本機中建立的mariadb資料庫中，可以透過下方的網址及帳號密碼登入觀看及設定

* 網址：<http://go38box.local/phpmyadmin>&#x20;
* 帳號：go38box
* 密碼：Go38box4$
* 資料庫：temperature

目前建立一個名稱為temperature的資料庫，裡面有3張資料表，欄位說明及結構如下

### 資料表 temp

temp儲存溫濕度的記錄，欄位說明如下：

* datetime：資料的日期時間
* temp：溫度，取到小數點第一位
* humidity 濕度，取到小數點第一位

資料結構如下圖：

![](/files/-Ldydih6m7eCloNxXbTd)

### 資料表 smtp

smtp資料表，存放有關寄送溫濕度異常E-MAIL的設定，此表中只會有一筆設定資料，如果要修改參數可以直接修改欄位內容即可

* id：此id為key值，因為此資料表為設定用，所以只需要一筆資料，程式會讀取id=0的該筆資料當做設定值
* hostname：網站網址，目前預設為 `go38box.local`
* from\_addr：寄件者E-MAIL，目前使用gmail當作smtp來寄送mail
* to\_addr：收件者E-MAIL
* smtp\_addr：gmail的smtp網址為 `smtp.gmail.com`
* smtp\_port：gmail的smtp port為 `587`
* smtp\_password：smtp的密碼，使用gmail當作smtp寄送mail，所以此密碼為`from_addr` 的E-MAIL密碼，目前此欄位有加密
* send\_time：最後一次寄送E-Mail的時間(程式會自動更新此欄位)
* send\_status：目前溫濕度狀態，1為異常、0為正常，此欄位請不要修改(程式會自動更新此欄位)
* send\_wait\_time：每執行一次都會檢查是否異常，此欄位儲存單位是秒數，主要在溫濕度一直在異常狀態下不會重復的寄送，例如此欄位目前預設值為3600秒，也就是1小時，當送出一封異常E-MAIL後，會自動更新欄位中的send\_time最後送出異常E-Mail的時間，每次執行會用目前的時間和`send_time`相減，看是否超過此欄位所設定的時間(1小時)，如果超過則會再寄送一次異常E-MAIL
* send\_check\_time：每執行一次都會檢查是否正常，此欄位儲存單位是秒數，如果上一次寄送異常E-MAIL的時間和目前時間相比是否大於所設定的`send_check_time`，大於的話才會切換為正常，必免溫濕度剛好在設定的邊界值跳動會一直送出異常與正常的E-MAIL，例如目前設定660，代表11分鐘內，不會寄送恢復正常的E-MAIL，如果超過，則會更新`send_time`及`send_status`設為0

![](/files/-Ldydih8Ff9eHOVRI6-G)

### 資料表 config

* id：此id為key值，因為此資料表為設定用，所以只需要一筆資料，程式會讀取id=0的該筆資料當做設定值
* send\_mail：此欄位主要設定是否要寄送E-MAIL，0(不寄送)，1(寄送)
* temp\_max：設定溫度正常範圍的最高溫度，目前預設為30度
* temp\_min：設定溫度正常範圍的最低溫度，目前預設為10度
* humidity\_max：設定濕度正常範圍的最高濕度，目前預設為70%
* humidity\_min：設定濕度正常範圍的最低濕度，目前預設為40%
* time\_offset：當程式取得新的溫濕度後，會先抓資料庫中最後一筆資料回來做參考比對，檢查與最後一筆資料的時間是否超過`time_offset`的設定值，目前預設為100秒，如果與上一筆資料時間相差超過100秒，則會直接寫入資料庫，如果沒有超過，則會再比對目前溫濕度是否和最後一筆的溫濕度相差超過`difference`的設定值，如果超過也是不寫入資料庫中，主要就是去DHT22元件的異常值
* difference：溫濕度的正負差，目前設定為5
* thingspeak\_key：如果有申請thingspeak，也想把資料上傳到thingspeak的話，註冊後會取得一組`thingspeak_key`，要上傳的話把key填入此欄位，如果不想上傳的話就清空此欄位，請參考文內的[ThingSpeak ](/raspberry-pi-temperature/thingspeak.md)說明

![](/files/-LdydihAkiomkFpd1HaP)

## Python 主程式

在開始前，請先閱讀[開發環境及設備](/raspberry-pi-temperature/chapter1.md) 及 [取得溫濕度](/raspberry-pi-temperature/chapter2.md) 這兩個章節，確認溫濕度元件已接上RaspberryPi並且可以讀取到溫濕度

```python
#!/usr/bin/python
# coding:utf-8

import sys, os, logging
import Adafruit_DHT
import httplib, urllib
import time,datetime
import MySQLdb
import base64
import smtplib
import pandas as pd
from bokeh.models import BoxAnnotation
from bokeh.plotting import figure, output_file, show
from bokeh.models import HoverTool
from Crypto.Cipher import DES
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText

# 程式檔案路徑
fPath = "/home/go38box/DHT22/"

# log 參數設定
FORMAT = '%(asctime)s - %(message)s'
logpath = fPath + "tempLogs"
logging.basicConfig(format = FORMAT, filename = logpath)
logger = logging.getLogger('tcpserver')
"""
console = logging.StreamHandler()
aformat = logging.Formatter('%(asctime)s - %(message)s')
console.setFormatter(aformat)
logger.addHandler(console)
"""

def main():
    writeToDb = False
    errId = -1

    # 連接資料庫
    db = db_connect()
    # 取得最後一筆資料
    lastRecord = get_last_record(db)
    # 取得config資料表中的設定
    [dbSendMail,dbTempMax,dbTempMin,dbHumidityMax,dbHumidityMin,dbTimeOffset,dbDifference,dbThingSpeakApiKey] = get_config(db)
    #取得目前時間
    dtnow = datetime.datetime.now()
    #取得溫濕度
    [temp,humidity] = dht22() 
    # 目前時間和最後一筆資料的時間相差幾秒
    timeoffset = (dtnow - lastRecord[0]).seconds

    # 送溫濕度到ThingSpeak，如果設定值為空的話，就不傳送
    if dbThingSpeakApiKey!='':
        params = urllib.urlencode({'field1': '{0:0.1f}'.format(temp), 'field2': '{0:0.1f}'.format(humidity), 'key': dbThingSpeakApiKey})
        post_to_thingspeak2(params)

    #溫濕度在0~100間才處理
    if (humidity >0 and humidity <= 100) and (temp >0 and temp<=100):
        if timeoffset > dbTimeOffset: 
            #目前時間和資料庫中最後一筆資料的時間超過X秒就直接寫入資料庫
            logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'timeoffset > 60')
            writeToDb = True
            errId=2
        else: 
            #目前時間和資料庫在X秒內，就檢查和上一筆的溫濕度不可超過所設定的範圍
            #超過則不寫入資料庫
            #溫度+-5度 ,濕度+-5度
            #正負值由資料庫中設定
            if (temp > lastRecord[1]+dbDifference) or (temp < lastRecord[1]-dbDifference): 
                logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'(temp > lastRecord[1]+'+str(dbDifference)+') or (temp < lastRecord[1]-'+str(dbDifference)+')')
                writeToDb = False
                errId=3
            elif (humidity > lastRecord[2]+dbDifference) or (humidity < lastRecord[2]-dbDifference):
                logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'(humidity > lastRecord[2]+'+str(dbDifference)+') or (humidity < lastRecord[2]-'+str(dbDifference)+')')
                writeToDb = False
                errId=4
            else:
                writeToDb = True
    else:
        # 溫濕度不在0~100間
        logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'(humidity >0 and humidity <= 100) and (temp >0 and temp<=100)')    
        writeToDb = False
        errId=1

    # 依上方的判斷(writeToDb)，決定是否要寫入資料庫
    if writeToDb:    
        #logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'OK')
        # 將目前的溫濕度寫入資料庫
        insert_to_mysql(db,dtnow,temp,humidity);
        # 取得目前時間
        dataframe = select_data(db,1)
        lastRecordNew = get_last_record(db)
        build_temp(dataframe[0],lastRecordNew,dbTempMax,dbTempMin)
        build_humidity(dataframe[1],lastRecordNew,dbHumidityMax,dbHumidityMin)
    # 依資料表config中欄位「send_mail」的設定，判斷是否要寄送E-Mail    
    if dbSendMail==1 :
        if writeToDb == True and (lastRecordNew[1]>=dbTempMin and lastRecordNew[1]<=dbTempMax and lastRecordNew[2]>=dbHumidityMin and lastRecordNew[2]<=dbHumidityMax):
            send_email(db,lastRecord[0],lastRecord[1],lastRecord[2],lastRecordNew[0],lastRecordNew[1],lastRecordNew[2],dbTempMax,dbTempMin,dbHumidityMax,dbHumidityMin,dbTimeOffset,dbDifference,timeoffset,0)    
        elif writeToDb == False:
            # print 'writeToDb=false'
            send_email(db,lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,dbTempMax,dbTempMin,dbHumidityMax,dbHumidityMin,dbTimeOffset,dbDifference,timeoffset,errId)
        elif lastRecordNew[1]<dbTempMin or lastRecordNew[1]>dbTempMax or lastRecordNew[2]<dbHumidityMin or lastRecordNew[2]>dbHumidityMax :
            # print 'maxmine'
            errId=5
            logger.warning('Msg =>last(%s Temp:%s Humidity:%s) New(%s Temp:%6.2f Humidity:%6.2f) event:%s', lastRecord[0],lastRecord[1],lastRecord[2],dtnow,temp,humidity,'溫濕度異常，不在所設定的範圍內！')    
            send_email(db,lastRecord[0],lastRecord[1],lastRecord[2],lastRecordNew[0],lastRecordNew[1],lastRecordNew[2],dbTempMax,dbTempMin,dbHumidityMax,dbHumidityMin,dbTimeOffset,dbDifference,timeoffset,errId)

    db.close()

# 遲接資料庫
def db_connect():
    db = MySQLdb.connect(host="localhost",user="go38box", passwd="Go38box4$",db="temperature", charset="utf8")
    return db

# 取得溫濕度
def dht22():
    humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302,4)

    if humidity is not None and temperature is not None:
        print('Temp={0:0.1f}* Humidity={1:0.1f}%'.format(temperature,humidity))
        return(temperature,humidity)
    else:
        print('Failed to get reading. Try again!')
        return(0,0)

# 將溫濕度送到ThingSpeak上
def post_to_thingspeak2(payload):
        headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
        not_connected = 1
        while (not_connected):
            try:
                conn = httplib.HTTPConnection("api.thingspeak.com",80,True,10)
                conn.connect()
                not_connected = 0
            except (httplib.HTTPException, socket.error) as ex:
                # logger.warning('Error: %s',ex)
                print "Error: %s" % ex

        conn.request("POST", "/update", payload, headers)
        response = conn.getresponse()
        print( response.status, response.reason, payload, time.strftime("%c"))
        if response.status!=200 and response.reason!='OK':
            logger.warning('ThingSpeak Error: status:%s  reason:%s. payload:%s time:%s' ,response.status, response.reason, payload, time.strftime("%c"))

        data = response.read()
        conn.close()

# 將溫濕度寫入資料庫中 
def insert_to_mysql(db,dtnow,temp,humidity):
    cursor = db.cursor()
    strnow = dtnow
    strtemp = "%.1f" %(temp)
    strhumidity = "%.1f" %(humidity)
    insert = ("INSERT INTO temp (datetime, temp, humidity) VALUES (%s, %s, %s)")
    data = (strnow,strtemp,strhumidity)
    cursor.execute(insert,data)
    db.commit()

# 從資料庫中取得最後一筆溫濕度記錄
def get_last_record(db):
    cursor = db.cursor()
    cursor.execute("select * from temp order by datetime desc limit 1")
    record = cursor.fetchone()
    lastTime = record[0]
    lastTemp = record[1]
    lastHumidity = record[2]
    # print "Last Datetime = %s  Temp = %s  Humidity = %s" %(lastTime,lastTemp,lastHumidity)
    return (lastTime,lastTemp,lastHumidity)

# 從資料庫查詢從目前時間往前x天的溫濕度資料，畫圖用
def select_data(db,sDay):
    cursor = db.cursor()
    cursor.execute("select * from temp where datetime BETWEEN DATE_SUB(NOW(),INTERVAL " + str(sDay)  +" DAY) AND NOW()")
    rows = cursor.fetchall()
    #for record in rows:
    #    print "%s %s %s" % (record[0],record[1],record[2])

    df = pd.DataFrame([[ij for ij in i] for i in rows])
    df.rename(columns={0: 'DateTime', 1: 'Temp', 2: 'Humidity'}, inplace=True);
    df_temp = df.sort_values(['DateTime'], ascending=[1]);
    df_humidity = df.sort_values(['DateTime'], ascending=[2]);
    return (df_temp,df_humidity)

# 產生溫度的html檔
def build_temp(df,lastRecord,dbTempMax,dbTempMin):
    hover = HoverTool(tooltips=[
        ("時間", "@x{%F %H:%M:%S}"),
        ("溫度", "@y{%0.2f} (°C)"),
    ],
    formatters={
        'x' : 'datetime', 
        'y' : 'printf',
    },)

    TOOLS = ["pan,wheel_zoom,box_zoom,reset,save",hover]
    p = figure(plot_width=540,plot_height=250,x_axis_type="datetime",y_range=(0, 40), tools=TOOLS,toolbar_location = "above", title="溫度  ("+str(lastRecord[0])+"  "+str(lastRecord[1])+"°C)")
    p.xgrid.grid_line_color=None
    p.ygrid.grid_line_alpha=0.5
    p.xaxis.axis_label = '時間'
    p.yaxis.axis_label = '攝氏(°C)'

    p.line(df['DateTime'], df['Temp'], color='navy', alpha=0.5)
    #p.line(df['DateTime'], df['Temp'], line_dash="4 4", line_width = 1,color='navy', alpha=0.5)
    #p.circle(df['DateTime'],df['Temp'], size=10, color="navy", alpha=0.5)
    p.add_layout(BoxAnnotation(top=dbTempMin, fill_alpha=0.1, fill_color='red'))
    p.add_layout(BoxAnnotation(bottom=dbTempMin, top=dbTempMax, fill_alpha=0.1, line_color='olive', fill_color='olive'))
    p.add_layout(BoxAnnotation(bottom=dbTempMax, fill_alpha=0.1, fill_color='red'))

    output_file(fPath+"db_temp.html")
    show(p)

# 產生濕度的html檔
def build_humidity(df,lastRecord,dbHumidityMax,dbHumidityMin):
    hover = HoverTool(tooltips=[
        ("時間", "@x{%F %H:%M:%S}"),
        ("濕度", "@y{%0.2f} (%)"),
    ],
    formatters={
        'x' : 'datetime', 
        'y' : 'printf',
    },)

    TOOLS = ["pan,wheel_zoom,box_zoom,reset,save",hover]
    p = figure(plot_width=540,plot_height=250,x_axis_type="datetime",y_range=(20,105), tools=TOOLS,toolbar_location = "above", title="濕度  ("+str(lastRecord[0])+"  "+str(lastRecord[2])+"%)")
    p.xgrid.grid_line_color=None
    p.ygrid.grid_line_alpha=0.5
    p.xaxis.axis_label = '時間'
    p.yaxis.axis_label = '百分比(%)'

    p.line(df['DateTime'], df['Humidity'], color='navy', alpha=0.5)
    p.add_layout(BoxAnnotation(top=dbHumidityMin, fill_alpha=0.1, fill_color='red'))
    p.add_layout(BoxAnnotation(bottom=dbHumidityMin, top=dbHumidityMax, fill_alpha=0.1, line_color='olive', fill_color='olive'))
    p.add_layout(BoxAnnotation(bottom=dbHumidityMax, fill_alpha=0.1, fill_color='red'))

    output_file(fPath+"db_humidity.html")
    show(p)

# 讀取資料庫中config資料表中的設定，id=0
def get_config(db):
    cursor = db.cursor()
    cursor.execute("select * from config where id=0")
    record = cursor.fetchone()
    return(record[1],record[2],record[3],record[4],record[5],record[6],record[7],record[8])

# 傳送email
def send_email(db,lastDatetime,lastTemp,lastHuminidy,newDatetime,newTemp,newHuminidy,dbTempMax,dbTempMin,dbHumidityMax,dbHumidityMin,dbTimeOffset,dbDifference,timeoffset,errId):
    cursor = db.cursor()
    cursor.execute("select * from smtp where id=0")
    record = cursor.fetchone()

    fromaddr = record[2]
    toaddr = record[3]
    pwd = decrypt_des(record[6])

    sendTime = record[7]
    sendStatus = record[8]
    sendWaitTime = record[9]
    sendCheckTime = record[10]

    sendTimeOffset = (newDatetime - sendTime).seconds
    goSendMail = False

    print 'errId=' +str(errId) 
    print 'sendTimeOffset=' +str(sendTimeOffset) 

    if errId==0:
        #errId=0代表溫濕度正常，但還要加入檢查，如果上一次寄送異常mail的時間和目前時間相比是否大於所設定的sendCheckTime，大於的話才切換為正常
        if sendStatus==1 and (sendTimeOffset > sendCheckTime):
            update_smtp_send_status(db,newDatetime,0)
            #傳送回復正常的mail
            msg = MIMEMultipart()
            msg['From'] = fromaddr
            msg['To'] = toaddr
            msg['Subject'] = "Go38 溫濕度正常"

            bodytable =  """<font color="#00A800">溫濕度正常！</font><br>"""

            bodytable += """
            <br><font color="blue">*溫度正常範圍：""" + str(dbTempMin) + """ ~ """ + str(dbTempMax) + """ (°C)</font><br>
            <font color="blue">*濕度正常範圍：""" + str(dbHumidityMin) + """ ~ """ + str(dbHumidityMax) + """ (%)</font><br>
            """

            bodytable += """
            <table align="left" border="1" cellpadding="1" cellspacing="1" style="width:500px">
                <tbody>
                    <tr>
                        <td style="text-align:center"><a href="http://"""+str(record[1])+""" " target="_blank">"""+str(record[1])+"""</a></td>
                        <td style="text-align:center">日期</td>
                        <td style="text-align:center">溫度(°C)</td>
                        <td style="text-align:center">濕度(%)</td>
                    </tr>
                    <tr>
                        <td style="text-align:center">上一筆資料</td>
                        <td style="text-align:center">"""+ str(lastDatetime) +"""</td>
                        <td style="text-align:center">"""+ str(lastTemp) +"""</td>
                        <td style="text-align:center">"""+ str(lastHuminidy) +"""</td>
                    </tr>
                    <tr>
                        <td style="text-align:center">最新資料</td>
                        <td style="text-align:center">"""+ str(newDatetime) +"""</td>
                        <td style="text-align:center">"""+ str(newTemp) +"""</td>
                        <td style="text-align:center">"""+ str(newHuminidy) +"""</td>
                    </tr>
                </tbody>
            </table>
            """
            msg.attach(MIMEText(bodytable, 'html', 'utf-8') )
            server = smtplib.SMTP(record[4], record[5])
            server.starttls()
            server.login(fromaddr, pwd)
            text = msg.as_string()
            server.sendmail(fromaddr, toaddr, text)
            server.quit()

    else:    
        #最後傳送狀態，0：傳送正常，1：傳送異常
        if sendStatus == 0:
            goSendMail = True
        elif sendStatus == 1:
            #最後一次送mail的時間和目前時間相減，相差的秒數大於所設定的sendWaitTime，則再傳送一次異常mail
            if sendTimeOffset > sendWaitTime:
                goSendMail = True

        ##傳送溫濕度異常的mail
        if goSendMail==True:
            update_smtp_send_status(db,newDatetime,1)
            msg = MIMEMultipart()
            msg['From'] = fromaddr
            msg['To'] = toaddr
            msg['Subject'] = "Go38 溫濕度異常"

            #body = str(record[1]) + "MAIL測試"


            if errId==1:
                bodytable =  """<font color="red">偵測器回傳的溫濕度不在 0~100 之間，未寫入資料庫！</font><br>"""
            elif errId==2:
                bodytable =  """<font color="red">目前時間和資料庫中最後一筆資料的時間超過"""+str(dbTimeOffset)+"""秒就直接寫入資料庫，不比對正負值！</font><br>"""
            elif errId==3:
                bodytable =  """<font color="red">目前溫度與最後一筆資料相比，在"""+str(timeoffset)+"""秒內超過所設定的正負值"""+str(dbDifference)+"""！</font><br>"""
            elif errId==4:
                bodytable =  """<font color="red">目前濕度與最後一筆資料相比，在"""+str(timeoffset)+"""秒內超過所設定的正負值"""+str(dbDifference)+"""！</font><br>"""
            elif errId==5:
                bodytable =  """<font color="red">目前溫度或濕度不在所設定的正常範圍內！</font><br>"""
            else:
                bodytable =  """<font color="red">沒有此errId<br>"""

            bodytable += """
            <br><font color="blue">*溫度正常範圍：""" + str(dbTempMin) + """ ~ """ + str(dbTempMax) + """ (°C)</font><br>
            <font color="blue">*濕度正常範圍：""" + str(dbHumidityMin) + """ ~ """ + str(dbHumidityMax) + """ (%)</font><br>
            """

            bodytable += """
            <table align="left" border="1" cellpadding="1" cellspacing="1" style="width:500px">
                <tbody>
                    <tr>
                        <td style="text-align:center"><a href="http://"""+str(record[1])+""" " target="_blank">"""+str(record[1])+"""</a></td>
                        <td style="text-align:center">日期</td>
                        <td style="text-align:center">溫度(°C)</td>
                        <td style="text-align:center">濕度(%)</td>
                    </tr>
                    <tr>
                        <td style="text-align:center">上一筆資料</td>
                        <td style="text-align:center">"""+ str(lastDatetime) +"""</td>
                        <td style="text-align:center">"""+ str(lastTemp) +"""</td>
                        <td style="text-align:center">"""+ str(lastHuminidy) +"""</td>
                    </tr>
                    <tr>
                        <td style="text-align:center">最新資料</td>
                        <td style="text-align:center">"""+ str(newDatetime) +"""</td>
                        <td style="text-align:center">"""+ str(newTemp) +"""</td>
                        <td style="text-align:center">"""+ str(newHuminidy) +"""</td>
                    </tr>
                </tbody>
            </table>
            """

            # msg.attach(MIMEText(body, 'plain'))
            msg.attach(MIMEText(bodytable, 'html', 'utf-8') )
            # msg = MIMEText(mail_msg, 'html', 'utf-8') 
            server = smtplib.SMTP(record[4], record[5])
            server.starttls()
            server.login(fromaddr, pwd)
            text = msg.as_string()
            server.sendmail(fromaddr, toaddr, text)
            server.quit()

# 更新資料表smtp中的SendTime與SendStatus
def update_smtp_send_status(db,dSendTime,dSendStatus):
    cursor = db.cursor()
    cursor.execute("update smtp set send_time='"+ str(dSendTime) +"' , send_status='"+str(dSendStatus)+"' where id=0")
    db.commit()

# 解密，主要用於資料表中smtp的smtp_password欄位，加密過程還加了一組key，目前設定為"@Go38Box"
def decrypt_des(encrypted):
    if encrypted is None:
        return ""
    try:
        key = '@Go38Box'
        # ECB方式
        generator = DES.new(key, DES.MODE_ECB)
        # 解碼
        crypted_str = base64.b64decode(encrypted)
        # 解密
        result = generator.decrypt(crypted_str)
        # 替換非空白字元
        result = result.strip("")
        #print "encrypted : "+str(encrypted)+"  cipher :"+result
        return result
    except Exception, e:
        print Exception, ":", e
        return ""

# end main
if __name__ == "__main__":
    sys.exit(main())
```

## 加解密程式

以下的程式主要是用在smtp資料表欄位「smtp\_password」，注意裡面有一個變數key，下方測試是使用「1234A#CD」，而溫濕度程式在使用的是「@Go38Box」

```python
#!/usr/bin/python
# coding:utf-8

from Crypto.Cipher import DES
import base64

def encrypt_des(cipher):
    if cipher is None:
        return ""
    try:
        key = '1234A#CD'
        # ECB方式
        generator = DES.new(key, DES.MODE_ECB)
        # 未滿8位數補空白
        pad = 8 - len(cipher) % 8
        pad_str = ""
        for i in range(pad):
            pad_str = pad_str + chr(pad)
        # 加密
        encrypted = generator.encrypt(cipher + pad_str)
        # 編碼
        result = base64.b64encode(encrypted)
        print "cipher : "+str(cipher)+"  encrypted : "+result
        # decrypt_des(result)
        return result
    except Exception, e:
        print Exception, ":", e
        return ""

def decrypt_des(encrypted):
    if encrypted is None:
        return ""
    try:
        key = '1234A#CD'
        # ECB方式
        generator = DES.new(key, DES.MODE_ECB)
        # 解碼
        crypted_str = base64.b64decode(encrypted)
        # 解密
        result = generator.decrypt(crypted_str)
        # 替換非空白字元
        result = result.strip("")
        print "encrypted : "+str(encrypted)+"  cipher :"+result
        return result
    except Exception, e:
        print Exception, ":", e
        return ""

encrypt_des('123345678')
decrypt_des('y6xH9zUHiHXjh8Y5CxVMjg==')
```

以上程式執行結果

```
cipher : 123345678  encrypted : y6xH9zUHiHXjh8Y5CxVMjg==
encrypted : y6xH9zUHiHXjh8Y5CxVMjg==  cipher :123345678
>>>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gxiangco.gitbook.io/raspberry-pi-temperature/nginxmysql.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
