

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 教學課程：使用 AWS IoT 和 Raspberry Pi 監控土壤濕度
<a name="iot-moisture-tutorial"></a>

本教學課程說明如何使用 [Raspberry Pi](https://www.raspberrypi.org/)、濕度感應器，以及 AWS IoT 監控房屋植物或花園的土壤濕度等級。Raspberry Pi 會執行程式碼，從感應器讀取濕度等級和溫度，然後將資料傳送至其中 AWS IoT。您可以在 中建立規則 AWS IoT ，當濕度等級低於閾值時，將電子郵件傳送至訂閱 Amazon SNS 主題的地址。

**注意**  
本教學課程可能不是最新版本。自本主題最初發佈後，某些參考文件可能已被取代。

**Contents**
+ [先決條件](#iot-moisture-prereqs)
+ [設定 AWS IoT](iot-moisture-setup.md)
  + [步驟 1：建立 AWS IoT 政策](iot-moisture-policy.md)
  + [步驟 2：建立 AWS IoT 物件、憑證和私有金鑰](iot-moisture-create-thing.md)
  + [步驟 3：建立 Amazon SNS 主題與訂閱。](iot-moisture-create-sns-topic.md)
  + [步驟 4：建立 AWS IoT 規則來傳送電子郵件](iot-moisture-create-rule.md)
+ [設定您的 Raspberry Pi 和濕度感應器](iot-moisture-raspi-setup.md)

## 先決條件
<a name="iot-moisture-prereqs"></a>

為完成此教學課程您需要：
+  AWS 帳戶。
+ 擁有管理員許可的 IAM 使用者。
+ 執行 Windows、macOS、Linux 或 Unix 的開發電腦，用來存取 [AWS IoT 主控台](https://console.aws.amazon.com/iot/home)。
+ 執行最新 [Raspberry Pi 作業系統的 Raspberry Pi 3B 或 4B](https://www.raspberrypi.com/products/)。 [https://www.raspberrypi.com/software/operating-systems/](https://www.raspberrypi.com/software/operating-systems/)如需安裝說明，請參閱 Raspberry Pi 網站上的[安裝作業系統](https://www.raspberrypi.com/documentation/computers/getting-started.html#installing-the-operating-system)。
+ 用於 Raspberry Pi 的顯示器、鍵盤、滑鼠和 Wi-Fi 網路或乙太網路連線。
+ 與 Raspberry Pi 相容的濕度感應器。本教學課程中使用的感應器是 [Adafure STEMMA I2C 電容式濕度感應器](https://www.adafruit.com/product/4026)搭配 [JST 4 針腳轉母頭插槽纜線接頭](https://www.adafruit.com/product/3950)。

# 設定 AWS IoT
<a name="iot-moisture-setup"></a>

若要完成此教學課程，您需要建立以下資源。若要將裝置連線至 AWS IoT，您可以建立 IoT 物件、裝置憑證和 AWS IoT 政策。
+ 實 AWS IoT 物。

  代表實體裝置 (在此案例中為 Rasberry Pi)，包含有關裝置的靜態中繼資料的物件。
+ 裝置憑證。

  所有裝置都必須擁有裝置憑證，才能連接至並向 AWS IoT驗證。
+  AWS IoT 政策。

  每個裝置憑證都有一或多個與其相關聯的 AWS IoT 政策。這些政策會決定裝置可存取 AWS IoT 的資源。
+  AWS IoT 根 CA 憑證。

  裝置和其他用戶端使用 AWS IoT 根 CA 憑證來驗證與其通訊的 AWS IoT 伺服器。如需詳細資訊，請參閱[伺服器驗證](server-authentication.md)。
+  AWS IoT 規則。

  規則包含查詢和一或多個規則動作。查詢會從裝置訊息擷取資料，以判斷是否應處理訊息資料。規則動作會指定當資料符合查詢時要採取的動作。
+ Amazon SNS 主題和主題訂閱。

  此規則會監聽來自 Raspberry Pi 的濕度資料。如果值低於閾值，它會將訊息傳送到 Amazon SNS 主題。Amazon SNS 會將該訊息傳送到訂閱該主題的所有電子郵件地址。

 



# 步驟 1：建立 AWS IoT 政策
<a name="iot-moisture-policy"></a>

建立允許 Raspberry Pi 連線和傳送訊息 AWS IoT 的政策 AWS IoT。

1. 在 [AWS IoT 主控台](https://console.aws.amazon.com/iot)中，如果 **Get started (開始)** 按鈕出現，請選擇它。否則，請在導覽窗格中展開 **Security** (安全性)，然後選擇 **Policies (政策)**。

1. 如果 **You don’t have any policies yet (您尚未有任何政策)** 對話方塊出現，請選擇 **Create a policy (建立政策)**。否則，請選擇 **Create** (建立)。

1. 輸入 AWS IoT 政策的名稱 （例如 **MoistureSensorPolicy**)。

1. 在 **Add statements (新增陳述式)** 區段中，將現有政策取代為下列 JSON。以您的 和 AWS 帳戶 號碼取代*區域* AWS 區域 和*帳戶*。  
****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": "iot:Connect",
               "Resource": "arn:aws:iot:us-east-1:123456789012:client/RaspberryPi"
           },
           {
               "Effect": "Allow",
               "Action": "iot:Publish",
               "Resource": [
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/update",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/delete",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/get"
               ]
           },
           {
               "Effect": "Allow",
               "Action": "iot:Receive",
               "Resource": [
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/update/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/delete/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/get/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/update/rejected",
                   "arn:aws:iot:us-east-1:123456789012:topic/$aws/things/RaspberryPi/shadow/delete/rejected"
               ]
           },
           {
               "Effect": "Allow",
               "Action": "iot:Subscribe",
               "Resource": [
                   "arn:aws:iot:us-east-1:123456789012:topicfilter/$aws/things/RaspberryPi/shadow/update/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topicfilter/$aws/things/RaspberryPi/shadow/delete/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topicfilter/$aws/things/RaspberryPi/shadow/get/accepted",
                   "arn:aws:iot:us-east-1:123456789012:topicfilter/$aws/things/RaspberryPi/shadow/update/rejected",
                   "arn:aws:iot:us-east-1:123456789012:topicfilter/$aws/things/RaspberryPi/shadow/delete/rejected"
               ]
           },
           {
               "Effect": "Allow",
               "Action": [
                   "iot:GetThingShadow",
                   "iot:UpdateThingShadow",
                   "iot:DeleteThingShadow"
               ],
               "Resource": "arn:aws:iot:us-east-1:123456789012:thing/RaspberryPi"
           }
       ]
   }
   ```

1. 選擇**建立**。

# 步驟 2：建立 AWS IoT 物件、憑證和私有金鑰
<a name="iot-moisture-create-thing"></a>

在 AWS IoT 登錄檔中建立物件以代表您的 Raspberry Pi。

1. 在 [AWS IoT 主控台](https://console.aws.amazon.com/iot/home)的導覽窗格中，依序選擇 **Manage** (管理) 和 **Things** (物件)。

1. 如果顯示 **You don’t have any things yet (尚無任何物件)** 對話方塊，請選擇 **Register a thing (註冊物件)**。否則，請選擇 **Create** (建立)。

1. 在**建立 AWS IoT 物件**頁面上，選擇**建立單一物件**。

1. 在 **Add your device to the device registry (將裝置新增至裝置登錄檔)** 頁面上，輸入您 IoT 物件的名稱 (例如 **RaspberryPi**)，然後選擇 **Next (下一步)**。您無法在建立之後變更物件的名稱。要變更物件的名稱，您必須建立一個新的物件並為它命名，然後刪除舊的物件。

1. 在 **Add a certificate for your thing (新增物件的憑證)** 頁面上，選擇 **Create certificate (建立憑證)**。

1. 選擇 **Download (下載)** 連結來下載憑證、私有金鑰和根憑證授權機構憑證。
**重要**  
這是您可以下載憑證和私有金鑰唯一機會。

1. 若要啟用憑證，請選擇 **Activate** (啟用)。憑證必須作用中，裝置才能連接到 AWS IoT。

1. 選擇 **Attach a policy (連接政策)**。

1. 針對 **Add a policy for your thing (新增您的物件的政策)**，選擇 **MoistureSensorPolicy**，然後選擇 **Register Thing (註冊物件)**。

# 步驟 3：建立 Amazon SNS 主題與訂閱。
<a name="iot-moisture-create-sns-topic"></a>

建立 Amazon SNS 主題與訂閱。

1. 在 [AWS SNS 主控台](https://console.aws.amazon.com/sns/home)的導覽窗格中，選擇 **Topics** (主題)，然後選擇 **Create topic** (建立主題)。

1. 選擇類型為**標準**，然後輸入主題的名稱 （例如 **MoistureSensorTopic**)。

1. 輸入主題的顯示名稱 (例如，**Moisture Sensor Topic**)。這是在 Amazon SNS 主控台中針對您的主題顯示的名稱。

1. 請選擇**建立主題**。

1. 在 Amazon SNS 主題詳細資訊頁面中，選擇 **Create subscription** (建立訂閱)。

1. 對於**通訊協定**，選擇**電子郵件**。

1. 針對 **Endpoint (端點)**，輸入電子郵件地址。

1. 選擇**建立訂閱**。

1. 開啟您的電子郵件用戶端，並尋找主旨為 **MoistureSensorTopic** 的訊息。開啟電子郵件，然後按一下 **Confirm subscription (確認訂閱)** 連結。
**重要**  
在您確認訂閱之前，不會收到來自此 Amazon SNS 主題的任何電子郵件提醒。

您應該會收到含有您輸入文字的電子郵件訊息。

# 步驟 4：建立 AWS IoT 規則來傳送電子郵件
<a name="iot-moisture-create-rule"></a>

 AWS IoT 規則會定義查詢，以及從裝置接收訊息時要採取的一或多個動作。 AWS IoT 規則引擎會監聽裝置傳送的訊息，並使用訊息中的資料來判斷是否應採取一些動作。如需詳細資訊，請參閱[的規則 AWS IoT](iot-rules.md)。

在此教學中，您的 Raspberry Pi 會將訊息發佈至 `aws/things/RaspberryPi/shadow/update`。這是內部的 MQTT 主題，供裝置和物件影子服務使用。Raspberry Pi 發佈的訊息會具備以下格式：

```
{
    "reported": {
        "moisture" : moisture-reading,
        "temp" : temperature-reading
    }
}
```

您會建立一個查詢，從傳入的訊息擷取濕度和溫度資料。您也可以建立 Amazon SNS 動作，在濕度讀數低於閾值時，取得資料並將其傳送給 Amazon SNS 主題訂閱者。

**建立 Amazon SNS 規則**

1. 在 [AWS IoT 主控台](https://console.aws.amazon.com/iot/home)中，選擇**訊息路由**，然後選擇**規則**。如果 **You don’t have any rules yet (您尚未有任何規則)** 對話方塊出現，請選擇 **Create a rule (建立規則)**。否則，請選擇**建立規則**。

1. 在**規則屬性**頁面中，輸入**規則名稱**，例如 **MoistureSensorRule**，並提供簡短的**規則描述**，例如 **Sends an alert when soil moisture level readings are too low**。

1. 選擇**下一步**並設定 SQL 陳述式。選擇 **SQL 版本**為 **2016-03-23**，然後輸入下列 AWS IoT SQL 查詢陳述式：

   ```
   SELECT * FROM '$aws/things/RaspberryPi/shadow/update/accepted' WHERE state.reported.moisture < 400
   ```

   此陳述式會在 `moisture` 讀數小於 `400` 時觸發規則動作。
**注意**  
您可能需要使用不同的值。在 Raspberry Pi 上執行程式碼之後，您可以碰觸感應器、將其放入水中或將其放入花盆中，以查看從感應器取得的值。

1. 選擇**下一步**並連接規則動作。針對**動作 1**，選擇**簡易通知服務**。此規則動作的描述是**傳送訊息做為 SNS 推送通知**。

1. 針對 **SNS 主題**，選擇您在 [步驟 3：建立 Amazon SNS 主題與訂閱。](iot-moisture-create-sns-topic.md)、**MoistureSensorTopic** 中建立的主題，並將**訊息格式**保留為 **RAW**。對於 **IAM 角色**，選擇**建立新角色**。輸入角色的名稱，例如 **LowMoistureTopicRole**，然後選擇**建立角色**。

1. 選擇**下一步**以檢閱，然後選擇**建立**以建立規則。

# 設定您的 Raspberry Pi 和濕度感應器
<a name="iot-moisture-raspi-setup"></a>



將 microSD 卡插入 Raspberry Pi，連接顯示器、鍵盤、滑鼠，以及乙太網路纜線 (如果未使用 Wi-Fi)。還不要連接電源線。

將 JST 跳線纜線連接至濕度感應器。跳線的另一端有四條配線：
+ 綠色：I2C SCL
+ 白色：I2C SDA
+ 紅色：功率 (3.5 V)
+ 黑色：接地

按住 Raspberry Pi 與右側的乙太網路插孔。在此方向中，上方有兩列 GPIO 接腳。按照以下順序，將濕度感應器的配線連接到接腳的下方排。從最左側接腳開始，連接紅色 (電源)、白色 (SDA) 和綠色 (SCL)。略過一個接腳，然後連接黑色 (接地) 配線。如需詳細資訊，請參閱 [Python Computer Wiring](https://learn.adafruit.com/adafruit-stemma-soil-sensor-i2c-capacitive-moisture-sensor/python-circuitpython-test)。

將電源線連接至 Raspberry Pi，並將另一端插入牆上插座，以將其開啟。

**設定您的 Raspberry Pi**

1. 在 **Welcome to Raspberry Pi (歡迎使用 Raspberry Pi)** 上，選擇 **Next (下一步)**。

1. 選擇您的國家/地區、語言、時區及鍵盤配置。選擇 **Next** (下一步)。

1. 輸入您的 Raspberry Pi 的密碼，然後選擇 **Next (下一步)**。

1. 選擇您的 Wi-Fi 網路，然後選擇 **Next (下一步)**。如果您不是使用 Wi-Fi 網路，請選擇 **Skip (略過)**。

1. 選擇 **Next (下一步)** 以檢查軟體更新。更新完成時，選擇 **Restart (重新啟動)** 以重新啟動您的 Raspberry Pi。

在 Raspberry Pi 啟動後，啟用 I2C 介面。

1. 在 Raspbian 桌面的左上角，按一下 Raspberry 圖示，選擇 **Preferences (偏好設定)**，然後選擇 **Raspberry Pi Configuration (Raspberry Pi 組態)**。

1. 在 **Interfaces (介面)** 標籤上，針對 **I2C**，選擇 **Enable (啟用)**。

1. 選擇**確定**。

Adafruit STEMMA 濕度感應器的程式庫是針對 CircuitPython 所撰寫。若要在 Raspberry Pi 上執行它們，您需要安裝最新版本的 Python 3。

1. 從命令提示執行下列命令，以更新您的 Raspberry Pi 軟體：

   `sudo apt-get update`

   `sudo apt-get upgrade`

1. 執行以下命令來更新您的 Python 3 安裝：

   `sudo pip3 install --upgrade setuptools`

1. 執行以下命令來安裝 Raspberry Pi GPIO 程式庫：

   `pip3 install RPI.GPIO`

1. 執行以下命令來安裝 Adafruit Blinka 程式庫：

   `pip3 install adafruit-blinka`

   如需詳細資訊，請參閱 [在 Raspberry Pi 上安裝 CircuitPython 程式庫](https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi)。

1. 執行以下命令來安裝 Adafruit Seesaw 程式庫：

   `sudo pip3 install adafruit-circuitpython-seesaw`

1. 執行下列命令來安裝適用於 Python 的 AWS IoT 裝置 SDK：

   `pip3 install AWSIoTPythonSDK`

您的 Raspberry Pi 現在擁有所有必要的程式庫。建立名為 **moistureSensor.py** 的檔案，並將下列 Python 程式碼複製到檔案：

```
from adafruit_seesaw.seesaw import Seesaw
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
from board import SCL, SDA

import logging
import time
import json
import argparse
import busio

# Shadow JSON schema:
#
# {
#   "state": {
#       "desired":{
#           "moisture":<INT VALUE>,
#           "temp":<INT VALUE>            
#       }
#   }
# }

# Function called when a shadow is updated
def customShadowCallback_Update(payload, responseStatus, token):

    # Display status and data from update request
    if responseStatus == "timeout":
        print("Update request " + token + " time out!")

    if responseStatus == "accepted":
        payloadDict = json.loads(payload)
        print("~~~~~~~~~~~~~~~~~~~~~~~")
        print("Update request with token: " + token + " accepted!")
        print("moisture: " + str(payloadDict["state"]["reported"]["moisture"]))
        print("temperature: " + str(payloadDict["state"]["reported"]["temp"]))
        print("~~~~~~~~~~~~~~~~~~~~~~~\n\n")

    if responseStatus == "rejected":
        print("Update request " + token + " rejected!")

# Function called when a shadow is deleted
def customShadowCallback_Delete(payload, responseStatus, token):

     # Display status and data from delete request
    if responseStatus == "timeout":
        print("Delete request " + token + " time out!")

    if responseStatus == "accepted":
        print("~~~~~~~~~~~~~~~~~~~~~~~")
        print("Delete request with token: " + token + " accepted!")
        print("~~~~~~~~~~~~~~~~~~~~~~~\n\n")

    if responseStatus == "rejected":
        print("Delete request " + token + " rejected!")


# Read in command-line parameters
def parseArgs():

    parser = argparse.ArgumentParser()
    parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your device data endpoint")
    parser.add_argument("-r", "--rootCA", action="store", required=True, dest="rootCAPath", help="Root CA file path")
    parser.add_argument("-c", "--cert", action="store", dest="certificatePath", help="Certificate file path")
    parser.add_argument("-k", "--key", action="store", dest="privateKeyPath", help="Private key file path")
    parser.add_argument("-p", "--port", action="store", dest="port", type=int, help="Port number override")
    parser.add_argument("-n", "--thingName", action="store", dest="thingName", default="Bot", help="Targeted thing name")
    parser.add_argument("-id", "--clientId", action="store", dest="clientId", default="basicShadowUpdater", help="Targeted client id")

    args = parser.parse_args()
    return args


# Configure logging
# AWSIoTMQTTShadowClient writes data to the log
def configureLogging():

    logger = logging.getLogger("AWSIoTPythonSDK.core")
    logger.setLevel(logging.DEBUG)
    streamHandler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    streamHandler.setFormatter(formatter)
    logger.addHandler(streamHandler)


# Parse command line arguments
args = parseArgs()

if not args.certificatePath or not args.privateKeyPath:
    parser.error("Missing credentials for authentication.")
    exit(2)

# If no --port argument is passed, default to 8883
if not args.port: 
    args.port = 8883


# Init AWSIoTMQTTShadowClient
myAWSIoTMQTTShadowClient = None
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient(args.clientId)
myAWSIoTMQTTShadowClient.configureEndpoint(args.host, args.port)
myAWSIoTMQTTShadowClient.configureCredentials(args.rootCAPath, args.privateKeyPath, args.certificatePath)

# AWSIoTMQTTShadowClient connection configuration
myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(10) # 10 sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(5) # 5 sec

# Initialize Raspberry Pi's I2C interface
i2c_bus = busio.I2C(SCL, SDA)

# Intialize SeeSaw, Adafruit's Circuit Python library
ss = Seesaw(i2c_bus, addr=0x36)

# Connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

# Create a device shadow handler, use this to update and delete shadow document
deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(args.thingName, True)

# Delete current shadow JSON doc
deviceShadowHandler.shadowDelete(customShadowCallback_Delete, 5)

# Read data from moisture sensor and update shadow
while True:

    # read moisture level through capacitive touch pad
    moistureLevel = ss.moisture_read()

    # read temperature from the temperature sensor
    temp = ss.get_temp()

    # Display moisture and temp readings
    print("Moisture Level: {}".format(moistureLevel))
    print("Temperature: {}".format(temp))
    
    # Create message payload
    payload = {"state":{"reported":{"moisture":str(moistureLevel),"temp":str(temp)}}}

    # Update shadow
    deviceShadowHandler.shadowUpdate(json.dumps(payload), customShadowCallback_Update, 5)
    time.sleep(1)
```

將檔案儲存到您可以找到的位置。從命令列搭配下列參數執行 `moistureSensor.py`：

端點  
您的自訂 AWS IoT 端點。如需詳細資訊，請參閱[Device Shadow REST API](device-shadow-rest-api.md)。

rootCA  
 AWS IoT 根 CA 憑證的完整路徑。

cert  
裝置 AWS IoT 憑證的完整路徑。

金鑰  
裝置 AWS IoT 憑證私有金鑰的完整路徑。

thingName  
您的物件名稱 (在此案例中為 `RaspberryPi`)。

clientId  
MQTT 用戶端 ID。請使用 `RaspberryPi`。

命令列看起來應該如下：

`python3 moistureSensor.py --endpoint your-endpoint --rootCA ~/certs/AmazonRootCA1.pem --cert ~/certs/raspberrypi-certificate.pem.crt --key ~/certs/raspberrypi-private.pem.key --thingName RaspberryPi --clientId RaspberryPi`

嘗試碰觸感應器、將其放入花盆中，或將其放入杯水中，以查看感應器如何回應各種濕度。如有需要，您可以在 `MoistureSensorRule` 中變更閾值。當濕度感應器讀數低於規則 SQL 查詢陳述式中指定的值時， 會將訊息 AWS IoT 發佈至 Amazon SNS 主題。您應該會收到包含濕度和溫度資料的電子郵件訊息。

在您驗證收到來自 Amazon SNS 的電子郵件訊息後，請按 **CTRL\$1C** 來停止 Python 程式。Python 程式傳送的訊息量不足以產生費用，但最佳實務是在完成時停止程式。