Serverless Framework 与 AWS IoT 构建 Serverless 的花园监控系统

在过去的几个星期里,我一直在写关于如何解决我在花园里遇到的问题—— 我经常忘记给我的植物浇水,所以我创建了一个仪表板和通知系统。

如果我不能将水分读数发送到 AWS,我的仪表板和通知系统没有太多的用处。因此,这个项目的下一步是创建 IoT 服务。

AWS IoT

正如我在之前的文章中(Slack Webhooks with the Serverless Framework)写道,AWS IoT 服务在这个系统中做了很大的努力。AWS IoT 提供设备认证、通信以及与其他 AWS 服务的集成。为了建立我的花园监控系统,我已经利用了三个 AWS IoT 服务。

Device Gateway

Device Gateway(设备网关)是 AWS IoT 服务的核心。设备风头是设备和 AWS 服务之间的消息代理(Message Broker)。设备网关支持三种协议:

  • MQTT,一个轻松级的发布/订阅消息协议。
  • HTTP,允许设备通过 POST 将消息推送到网关
  • 基于 WebSockets 的 MQTT,允许浏览器和移动设备与网关通讯

MQTT 专为电源和带宽受限的设备而设计。这使得我想要用它,为我的花园建立土壤水分传感器。

Authentication

设备网关需要对接收到的所有消息进行身份验证。虽然有几个选项来验证消息。 但是,由于我使用 MQTT,我必须使用 X.509 证书验证消息。

设备网关使用相互的 TLS 身份验证来验证客户端证书。当使用 X.509 证书时,设备风头需要使用 TLS 1.2。正如我发现,在为您的项目选择硬件之前,了解这一点很重要。

Rules Engine

如果设备网关是 AWS IoT 的核心,那么规则引擎(Rules Engine)是大脑。Rules Engine 对传入的 IoT 消息进行处理、过滤、转换和处理。

每个 IoT 规则都包含一个 SQL 语句。SQL语句支持以下关键字:

  • FROM,要监听的 MQTT 主题
  • SELECT,转换传入消息中的数据
  • WHERE(可选),决定是否执行动作

规则引擎可以使用每个消息的转换数据来调用操作。我为我的项目使用的动作是:

  • DynamoDB,写入数据到 DynamoDB
  • Lambda,使用转换后的数据调用 Lambda 函数

更多规则相关的操作,可以查看官方文档:AWS IoT Rule Actions

这些理论已经差不多了。接下来,让我们来看看:如何使用 Serverless Framework 进行设置。

代码

serverless.yml 配置

在 Serverless Framework 中,serverless.yml 是 serverless 服务的主要配置文件。serverless.yml 配置文件定义了服务中的功能和资源。资源是使用 YAML 而不是 JSON 编写的 CloudFormation 资源类型。

在这个例子里,我把我的项目的配置文件分散到三个 gists 文件上。

service: garden-aid-iot-hub
provider:
  name: aws
  runtime: nodejs4.3
  # custom variable syntax is needed to avoid conflits with aws cloudformation functions
  variableSyntax: '\${{([\s\S]+?)}}'


# load custom variables from a file
custom: ${{file(./vars-${{opt:stage}}.yml)}}

# define the check moisture level function
functions:
  checkMoistureLevel:
    handler: index.checkMoistureLevel

resources:
  Resources:
    # DynamoDB - see https://gist.github.com/johncmckim/9bec3ee3ed07722eb1bd4335bcd80910
    # IoT - see https://gist.github.com/johncmckim/5d149fb2416f38957c2d0e30f56c6aba

    LambdaInvokePermission:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName: { Fn::GetAtt: [ checkMoistureLevel, Arn ] }
        Action: lambda:InvokeFunction
        Principal: iot.amazonaws.com

要注意的两个关键部分是 variableSyntax 属性和 LambdaInvokePermission 资源。

variableSyntax 属性允许开发人员更改在 serverless.yml 中引用变量的语法。我改变了变量语法,以允许使用 IoT Sql 函数,例如 ${timestamp()}

LambdaInvokePermission 资源授予 AWS IoT 服务访问 checkMoistureLevel 功能。不要忘了添加这个,否则你会遇到一个糟糕的时刻。

serverless.yml 配置之 DynamoDB 表

下一步是定义一个 DynamoDB 表来存储水分数据。我需要存储发送消息的设备、消息时间戳和湿度水平。

MoistureData:
  Type: AWS::DynamoDB::Table
  DeletionPolicy: Retain
  Properties:
    TableName: moisture-data-${{opt:stage}}
    AttributeDefinitions:
      -
        AttributeName: ClientId
        AttributeType: S
      -
        AttributeName: Timestamp
        AttributeType: S
    KeySchema:
      -
        AttributeName: ClientId
        KeyType: HASH
      -
        AttributeName: Timestamp
        KeyType: RANGE
    ProvisionedThroughput:
      ReadCapacityUnits: 1
      WriteCapacityUnits: 1

我使用设置 ID(ClientId)和时间戳作为此表的键。它允许我在 DynamoDB 中查询来自特定设备的消息。你可以阅读我之前的文章,《GraphQL with the Serverless Framework》来了解如何实现。

serverless.yml 配置之 AWS IoT

最后一步是定义 AWS IoT 服务。以下代码定义了:

  • 一种 IoT Thing 和策略 - 用于将设备连接到AWS
  • IoT 角色 - 定义 IoT 服务的权限
  • IoT 规则 - 将数据存储在 DynamoDB 中并调用 Lambda
SensorThing:
  Type: AWS::IoT::Thing
  Properties:
    AttributePayload:
       Attributes:
        SensorType: soil

SensorThingPolicy:
  Type: AWS::IoT::Policy
  Properties:
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Action: ["iot:Connect"]
          Resource: ["${{custom.sensorThingClientResource}}"]
        - Effect: "Allow"
          Action: ["iot:Publish"]
          Resource: ["${{custom.sensorThingSoilTopicResource}}"]

SensorPolicyPrincipalAttachmentCert:
  Type: AWS::IoT::PolicyPrincipalAttachment
  Properties:
    PolicyName: { Ref: SensorThingPolicy }
    Principal: ${{custom.iotCertificateArn}}

SensorThingPrincipalAttachmentCert:
  Type: "AWS::IoT::ThingPrincipalAttachment"
  Properties:
    ThingName: { Ref: SensorThing }
    Principal: ${{custom.iotCertificateArn}}

IoTRole:
 Type: AWS::IAM::Role
 Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        -
          Effect: Allow
          Principal:
            Service:
              - iot.amazonaws.com
          Action:
            - sts:AssumeRole

IoTRolePolicies:
  Type: AWS::IAM::Policy
  Properties:
    PolicyName: IoTRole_Policy
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        -
          Effect: Allow
          Action:
            - dynamodb:PutItem
          Resource: "*"
        -
          Effect: Allow
          Action:
            - lambda:InvokeFunction
          Resource: "*"
    Roles: [{ Ref: IoTRole }]


# AWS IoT SQL Reference
# http://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-functions.html
SensorThingRule:
  Type: AWS::IoT::TopicRule
  Properties:
    TopicRulePayload:
      RuleDisabled: false
      Sql: "SELECT Level FROM '${{opt:stage}}/garden/soil/moisture'"
      Actions:
        -
          DynamoDB:
            TableName: { Ref: MoistureData }
            HashKeyField: "ClientId"
            HashKeyValue: "${clientId()}"
            RangeKeyField: "Timestamp"
            RangeKeyValue: "${timestamp()}"
            PayloadField: "Data"
            RoleArn: { Fn::GetAtt: [ IoTRole, Arn ] }
        -
          Lambda:
            FunctionArn: { Fn::GetAtt: [ checkMoistureLevel, Arn ] }

正如我之前写的,规则引擎是这个项目的大脑。SensorThingRule 将所有湿度水平存储在 DynamoDB 中。它提供了水分仪表板的数据。SensorThingRule 还调用每个消息的 Lambda 函数。它将在潮湿水平太低时通知系统提醒我。

那就是云服务的内容。最后一步是查看设备。

设备

这是我必须作出供认的地方。 我没有能够完成这个项目,因为我有硬件问题。

NodeMCU

NodeMCU 是一个用于物联网项目的固件和开发板。我为这个项目购买了一个基于 NodeMCU 的开发板。

NodeMCU 固件允许您使用 Lua 编程语言来控制您的设备。固件包括用于联网和传感器的标准库。

开发板是基于 ESP8266 的板,具有大量用于传感器的 GPIO 引脚。该板大约 $8 美元,从中国交付。该开发板是一个很好的选择,但他们有一个致命的问题。

NodeMCU 不支持 TLS 1.2。没有 TLS 1.2 支持,我无法从 NodeMCU 发送消息到 AWS。我可以使用替代固件,esp-open-rtos。但是,我还没有尝试过。

使用 JavaScript 模拟

虽然我无法创建硬件设备,但我已经能够测试 IoT 服务。我创建了一个小型的 Node.js 程序,来连接到 AWS IoT 设备网关并发布假的值。

const awsIot = require('aws-iot-device-sdk');

const interval = 1000 * 60 * 1; // one minute

const device = awsIot.device({
  keyPath: './path/to/your/private.pem.key',
  certPath: './path/to/your/certificate.pem.crt',
  caPath: './path/to/your/verisign-ca.pem',
  clientId: 'garden-aid-client-test-js',
  region: 'ap-southeast-2'
});

device.on('connect', function() {
  console.log('connect');
  setInterval(function() {
    const level = 1.1; // or generate a random level

    const options = {};
    const message = JSON.stringify({
      Level: level
    });

    device.publish(topic, message, options, function(err, result) {
      console.log('Err: ', err);
      console.log('Result: ', result);
    });
  }, interval);
});

这使我能够继续开发系统,而无需使用工作的硬件设备。

下一步

我决心完成这个项目。 我最近购买了我希望用来完成这个项目的新硬件。 当我有一个工作的解决方案时,我将详细介绍这些设备。

我希望你喜欢阅读这个项目。 如果您喜欢这篇文章,请关注我在 Medium 或 Twitter 上获得更多精彩内容。

原文链接:https://serverless.zone/iot-with-the-serverless-framework-e228fae87be

1 人评价

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.