Particle 与 AWS 制作 Serverless IoT 物联网系统

物联网(简称 IoT),在过去一年里看到了大量的活动和兴趣。从企业到医疗保健提供者,甚至汽车公司,互联世界的理念正在越来越多地吸引日常的消费者和企业。这种围绕着我们周围事物的构造转变,打开了大量令人兴奋的新产品的大门,并且将诸如亚马逊网络服务(AWS)等云服务平台,放在了对这些新技术的可扩展支持的最前沿。

在去年某个时候,我拿起一个 Particle Photon;一个微型、低功耗的 WiFi芯片,旨在用于快速开发原型物联网项目。 橡皮擦大小的小型电路板,能利用基于云的开发工具集,用您自定义的 C++ 代码更新设备的“固件”。该设备的 SDK 使开发人员能够访问多个引脚,从而实现广泛的功能和增强功能。

在这篇文章中,我概述了创建一个简单的数据捕获设备所需的步骤,在这种情况下,它是一个睡眠传感器,它将收集的信息发送给 AWS 进行处理和存储。这项技术可以用于许多入门级物联网项目,所有包括在这个写作中的工具都是“现成的”,可供日常消费者使用。

Particle Photon + AWS

Particle 和 AWS 实战

Particle 平台更令人印象深刻的功能之一是,它内置了 webhook 集成。无线连接的电路板,能够通过 Particle 服务向 webhook 设置发送数据和接收数据。这一功能为许多有趣的解决方案打开了大门,包括与 IFTTT 的开箱即用集成

这个 webhook 集成,还允许与自定义 API 结点进行交互,专门开发用于处理由 Particle 生成的数据。我着手创建一个睡眠传感器,利用加速度计与 Particle 和 AWS 结合来处理整夜睡眠的“质量”,每分钟发送一次关于我的动作的数据以供云处理。以下部分将解释这是如何工作的,以及必须利用哪些服务来实现这一点。

步骤1:AWS 设置

对于这个项目,我使用了三个 AWS 服务:

  • 一个用于存储捕获信息的 DynamoDB 表
  • 一个用于处理来自 Particle 的数据并将其写入表的API函数
  • 一个 API Gateway 来暴露 Lambda 函数,以用于在允许写入数据之前执行一些验证

本节将介绍如何配置这些服务:

DynamoDB 配置

DynamoDB 是一个非常适合物联网项目的 NoSQL 存储服务。这是非常便宜,性能是太棒了。对于这个项目,我创建了一个名为 SleepData 的表,其中 deviceID 的主分区键(Primary Partition Key)和 published_at 的主分类键(Primary Sort Key)设置为一个 number。它将使我们能够,在特定的时间段扫描或查询我们的 DynamoDB 表,查找特定的 Photon 设备,以便读取数据。

在创建 DynamoDB 表后,我开始配置 Lambda 函数。该函数旨在读取通过 API Gateway 传递的 JSON 数据,对数据进行清理和规范化,并将其存储在 DynamoDB 表中。我还在函数中包含了一些逻辑,来捕获它何时应该记录这些信息,什么时候不应该记录,所以我只是在我睡着的时候才存储信息。

from __future__ import print_function
import logging
import boto3
from datetime import *
from boto3.dynamodb.conditions import Key, Attr

# enable basic logging to CloudWatch Logs
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# setup the DynamoDB table
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('SleepData')

# setup conversion to epoch
epoch = datetime.utcfromtimestamp(0)
now = datetime.now()

def lambda_handler(event, context):
    # determine if the user is asleep
    sleepState = sleepCheck()

    # if the user has triggered the buttons, perform some logic
    if event['data'] == "True":
        # if the user is awake, toggle sleep. If not, visa versa
        if sleepState == "awake":
            table.put_item(
                Item={
                   'event_name': event['name'],
                   'published_at': int(unix_time_millis(datetime.strptime(event['published_at'], "%Y-%m-%dT%H:%M:%S.%fZ"))),
                   'data': 'true',
                   'state': 'asleep',
                   'deviceID' : event['source']
                }
            )
        else:
            table.put_item(
                Item={
                   'event_name': event['name'],
                   'published_at': int(unix_time_millis(datetime.strptime(event['published_at'], "%Y-%m-%dT%H:%M:%S.%fZ"))),
                   'data': 'true',
                   'state' : 'awake',
                   'deviceID' : event['source']
                }
            )
    else:
        # if the user is a sleep, and the buttons weren't pressed
        # send the data to dynamodb
        if sleepState == "asleep":
            table.put_item(
                    Item={
                       'event_name': event['name'],
                       # convert the date/time to epoch for storage in the table
                       'published_at': int(unix_time_millis(datetime.strptime(event['published_at'], "%Y-%m-%dT%H:%M:%S.%fZ"))),
                       'data': int(event['data']),
                       'state': 'asleep',
                       'deviceID' : event['source']
                    }
                )
        else:
            print("Not asleep")
    print(event)
    return("Success!")

# a function to convert the time to epoch
def unix_time_millis(dt):
    return (dt - epoch).total_seconds() * 1000.0

# a function to check if the user is currernly "sleeping"
def sleepCheck():
    fe = Key('data').eq('true')
    pe = "published_at, deviceID, #da, #st"
    ean = { "#da": "data", "#st": "state"}
    esk = None

    response = table.scan(
        FilterExpression=fe,
        ProjectionExpression=pe,
        ExpressionAttributeNames=ean
        )

    x = len(response['Items'])
    y = 0
    for i in response['Items']:
        y = y + 1
        if y == x:
            return(str(i['state']))

最后,我配置了 API 结点(Endpoint)以将数据发送到 Lambda 函数。 我想限制谁能够发送信息,所以我还要求所有请求包含 API 密钥。您可以通过导航到 AWS 控制台中的 Lambda函 数的 API 结点部分,将您的 Lambda 函数链接到 API Gateway。然后,您可以创建一个新的 API Gateway 并相应地进行设置。在 AWS 文档中有关于 API Gateway 和 Lambda 的很好的教程。

添加 API 结点

对于这个API网关,我们要确保将方法设置为 PUT,将安全性设置为使用访问密钥打开。这将确保我们发送 API Gateway 的数据是安全的,除了具有访问密钥的人员以外,没有人可以将数据发送到我们的端点。

这就总结了 AWS 方面正在发生的事情。 在将来的项目中,我将探讨处理存储在 DynamoDB 表中的数据。 目前,我们只关心捕获它以备将来使用。

步骤2:配置 Particle Photon

对于这个项目,我利用了 Internet Button 的扩展板,其中包括一个加速度计、四个按钮和一些LED。这使我能够与 API 进行交互,打开和关闭数据记录,并在设备上触发某个动作(例如将其设置为睡眠)时给予一些视觉反馈。

在 Particle 世界中,webhook 是一个已发布的端点,附加到您的帐户,其中包含将数据发送到外部 API 所需的信息。 设置这个过程可能会有一些挑战,因为你需要通过 CLI 来执行操作,并且在某些设置错误的情况下,没有太多反馈的形式。 以下步骤可用于使用 AWS API 网关配置 Particle webhook:

{
  "event": "sendSleep",
  "url": "https://[yourendpoint].execute-api.us-east-1.amazonaws.com/prod/ParticleSleepV1",
  "requestType": "Post",
  "headers": {
        "x-api-key" : "[yourapikey]"
   },
   "json": {
        "name": "{{SPARK_EVENT_NAME}}",
        "data": "{{SPARK_EVENT_VALUE}}",
        "source": "{{SPARK_CORE_ID}}",
        "published_at": "{{SPARK_PUBLISHED_AT}}"
    },
  "mydevices": true,
  "noDefaults": true
}

你需要做的第一件事就是,安装 Particle CLI 并登录。幸运的是,Particle 拥有了一个很好的资源来设置它。 获取 CLI 配置后,您需要为 webhook 创建一个 JSON 定义文件。该文件包含在 header 中发送的 API 密钥,正在发送的数据的模板以及 API 结点的 URL。 我有一个上面这个文件的示例。 最后,从 CLI 中,你需要添加 webhook API:

particle webhook create SleepAPI.json

它会将 API 与 JSON 文件中的名称关联起来。要将数据发送到 API,您将使用 API 代码中的 API 文字名作为 Particle.publish() 函数的第一个属性。有关 Particles 的 Webhooks 实现的更多信息可以在他们的文档中找到。

用于 “Particle” 的代码每 100 毫秒循环一次,并捕获发生的任何移动。我正在使用一个特殊的功能,特定于 Internet Button,在每个循环中返回 “lowest LED”。如果自上一个循环以来“lowest LED”发生了变化,我将其记录为单个动作。循环完成一分钟后,总数将通过 Particle webhook 函数发送到我的 AWS API 网关。

// Make sure to include the spcial library for the internet button
#include "InternetButton/InternetButton.h"

// Create a Button named b. It will be your friend, and you two will spend lots of time together.
InternetButton b = InternetButton();

int ledOldPos = 0;
char ledPosTrust[5];
int moveCount = 0;
int loopCount =0;

// The code in setup() runs once when the device is powered on or reset. Used for setting up states, modes, etc
void setup() {
    // Tell b to get everything ready to go
    // Use b.begin(1); if you have the original SparkButton, which does not have a buzzer or a plastic enclosure
    // to use, just add a '1' between the parentheses in the code below.
    b.begin();
}

/* loop(), in contrast to setup(), runs all the time. Over and over again.
Remember this particularly if there are things you DON'T want to run a lot. Like Spark.publish() */
void loop() {
    // Load up the special "lowestLed" object
    int ledPos = b.lowestLed();

    // Turn the LEDs off so they don't all end up on
    b.allLedsOff();

    // I'm movin', incriment the counter up
    if (ledOldPos != ledPos){
      sprintf(ledPosTrust,"%d",ledPos);
      moveCount++;
    }
    //The button's have been triggered! Record this!
    if ((b.buttonOn(2) and b.buttonOn(4)) or (b.buttonOn(1) and b.buttonOn(3))) {
        b.ledOn(3, 0, 255, 0); // Green
        b.ledOn(9, 0, 255, 0); // Green

        Particle.publish("sendSleep", "True", 60, PRIVATE);
        delay(500);
    }

    // if we've looped through x times, fire off the webhook
    if (loopCount >= 600){
        Particle.publish("sendSleep", String(moveCount), 60, PRIVATE);
        moveCount = 0;
        loopCount = 0;
    }

    loopCount++;
    ledOldPos = ledPos;
    // Wait a mo'
    delay(100);
}

结果

将所有这些服务捆绑在一起的最终结果是一个非常强大,低成本的物联网平台。我能够在几个小时内创建一个原型,使用现成的产品和一些连接代码。虽然我的例子是一个移动的 “sleep” 跟踪器,很容易看到这种类型的物联网设计,如何在许多应用程序中使用。

睡眠数据表

上面的图表是在夜晚睡眠之后,从 DynamoDB 表中提取的数据示例。所捕获的数据是完整的,并在创建我的自己的睡眠跟踪器的一个令人兴奋的第一步。 在处理信息方面还有很多工作要做,但最初的结果是鼓舞人心的。Serverless 体系结构正在迅速成为可行的现实。

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

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