

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 教程：使用 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 OS](https://www.raspberrypi.com/software/operating-systems/) 的 [Raspberry Pi 3B 或 4B](https://www.raspberrypi.com/products/)。有关安装说明，请参阅 Raspberry Pi 网站上的[安装操作系统](https://www.raspberrypi.com/documentation/computers/getting-started.html#installing-the-operating-system)。
+ 用于 Raspberry Pi 的显示器、键盘、鼠标和 Wi-Fi 网络或以太网连接。
+ 与 Raspberry Pi 兼容的含水量传感器。本教程中使用的传感器是 [Adafruit 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，您需要创建物联网事物、设备证书和 AWS IoT 策略。
+ 一 AWS IoT 件事。

  事物代表物理设备（在本例中为 Raspberry 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)中，如果显示**开始使用**按钮，请选择该按钮。否则，请在导航窗格中展开 **Secure**（安全），然后选择 **Policies**（策略）。

1. 如果显示**您还没有任何策略**对话框，请选择**创建策略**。否则，选择**创建**。

1. 输入 AWS IoT 策略的名称（例如，**MoistureSensorPolicy**）。

1. 在**添加声明**部分中，将现有策略替换为以下 JSON。*account*用你*region*的 and 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)的导航窗格中，依次选择**管理**和**事物**。

1. 如果显示**您还没有任何事物**对话框，请选择**注册事物**。否则，选择**创建**。

1. 在**创建 AWS IoT 事物**页面上，选择**创建单个事物**。

1. 在**将您的设备添加到设备注册表** 页面上，输入您的物联网事物的名称（例如 **RaspberryPi**），然后选择**下一步**。您无法在创建事物后更改其名称。要更改事物的名称，您必须创建一个新事物，为其指定新名称，然后删除旧事物。

1. 在**添加事物的证书**页面上，选择**创建证书**。

1. 选择**下载**链接以下载证书、私有密钥和根 CA 证书。
**重要**  
这是您下载证书和私有密钥的唯一机会。

1. 选择 **Activate**（激活）来激活您的证书。证书必须处于活动状态，设备才能连接到 AWS IoT。

1. 选择**附加策略**。

1. 在 “**为您的事物添加策略**” 中，选择 **MoistureSensorPolicy**，然后选择 “**注册事物**”。

# 步骤 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. 对于**端点**，输入您的电子邮件地址。

1. 选择**创建订阅**。

1. 打开您的电子邮件客户端，查找主题为 **MoistureSensorTopic** 的消息。打开这封电子邮件，然后单击**确认订阅**链接。
**重要**  
在确认订阅之前，您不会收到来自该 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` 上发布消息。这是设备和 Thing Shadow 服务使用的内部 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)中，选择**消息路由**，然后选择**规则**。如果显示**您还没有任何规则**对话框，请选择**创建规则**。否则，请选择**创建规则**。

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**，选择 **Simple Notification Service**。此规则操作的描述为**将消息作为 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>



将 Micro SD 卡插入 Raspberry Pi 中，连接显示器、键盘、鼠标以及以太网电缆（如果您未使用 Wi-Fi）。先不要连接电源线。

将 JST 跳线连接到含水量传感器。跳线的另一端有四根电线：
+ 绿色：I2C SCL
+ 白色：I2C SDA
+ 红色：电源 (3.5 V)
+ 黑色：接地

握住 Raspberry Pi（以太网插孔位于右侧）。以该方向握持时，可看到顶部有两排 GPIO 引脚。按照以下顺序将含水量传感器的电线连接到下排引脚。从最左侧的引脚开始，连接红色（电源）、白色（SDA）和绿色（SCL）电线。跳过一个引脚，然后连接黑色（接地）电线。有关更多信息，请参阅 [Python 计算机接线](https://learn.adafruit.com/adafruit-stemma-soil-sensor-i2c-capacitive-moisture-sensor/python-circuitpython-test)。

将电源线连接到 Raspberry Pi，然后将另一端插入墙壁插座以将其打开。

**配置您的 Raspberry Pi**

1. 在**欢迎使用 Raspberry Pi**页面上，选择**下一步**。

1. 选择您的国家/地区、语言、时区和键盘布局。选择**下一步**。

1. 输入 Raspberry Pi 的密码，然后选择**下一步**。

1. 选择您的 Wi-Fi 网络，然后选择**下一步**。如果不使用 Wi-Fi 网络，则选择**跳过**。

1. 选择**下一步**检查软件更新。完成更新后，选择**重启**以重启您的 Raspberry Pi。

在 Raspberry Pi 启动后，启用 I2C 接口。

1. 在 Raspbian 桌面的左上角，单击 Raspberry 图标，选择**首选项**，然后选择 **Raspberry Pi 配置**。

1. 在**接口**选项卡上，对于 **I2C**，选择**启用**。

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 查询语句中指定的值时，会向 Amazon SNS 主题 AWS IoT 发布一条消息。您应会收到一封包含含水量和温度数据的电子邮件。

确认收到来自 Amazon SNS 的电子邮件后，按 **CTRL \$1 C** 停止 Python 程序。该 Python 程序发送的消息应该不足以产生费用，但完成后最好将其停止。