CouchDB 与 ESP8266 搭建 Serverless IoT 物联网系统

最近我沉浸在物联网世界中,并被亚马逊,IBM 和 微软等公司的配置所淹没。他们为大型企业项目提供了很好的解决方案,但似乎需要大量的配置和知识来设置所有的项目。凭借我最近在 CouchDB 和 Serverless 应用程序方面的经验,我决定尝试新的东西:Serverless IoT。只需要运行 CouchDB 数据库的实例,就可以捕获传感器数据,并绘制图表上的数据?

步骤1:硬件

用于此项目的硬件是带有 WEMOS DHT 扩展板的 WEMOS D1 mini Pro。D1 mini Pro 基于 ESP-8266EX,DHT 扩展板基于 DHT11(温湿度传感器)。为使器件能够从深度睡眠唤醒,GP16 引脚(WEMOS 器件上的 D0)连接到 RST 引脚。最后一块是连接到设备的电池,所以一切都可以在无线下工作。

用于此设备的固件是 NodeMCU。可以使用 NodeMCU 自定义构建工具 来构建固件映像。我在构建中包含以下模块:

dht, file, http, net, node, tmr, wifi, tls

在构建器中使用 TLS 选项可以包含 TLS。对于某些 CouchDB 提供商来说是非常有必要,如 Cloudant。

ESP8266 可以使用 esptool.py 烧录。我使用以下命令来做到这一点:

esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash

或者

esptool.py --port /dev/cu.SLAB_USBtoUART write_flash --flash_mode dio --flash_size 16m 0x00000 ../builds/nodemcu-master-8-modules-2017-06-11-12-54-27-integer.bin

使用 esplorer ,我们现在可以将脚本上传到 ESP8266。我准备了一个具有以下流程的 Lua 脚本(在文末):

  • 连接到WiFi
  • 如果成功,读取 DHT 传感器的值
  • 如果成功,将传感器数据进行 HTTP POST 请求到服务器
  • 休眠

脚本没有 while 循环或重复流。发送数据后结束。醒来后,EPS8266 将被重置,我们在脚本开始时自动启动。

步骤2:CouchDB

要存储数据,我们需要一个 CouchDB 实例。我喜欢 Cloudant,因为我更喜欢基于云的解决方案,但也可以从 Docker 容器运行自己的 CouchDB 实例。

为了安全,要尽可能使用 HTTPS,并创建三个用户帐户:

  • admin, 用于管理目的(写入和读取权限)
  • sensor,用于存储传感器数据(仅限写入权限)
  • client,用于读取传感器数据(仅读取权限,不一定需要密码)

创建名为 measurement 的数据库。您可能会认为,保存传感器数据是相当容易的:在 ESP8266 的 HTTP POST 请求中,直接发送到该数据库,但确实会有一些问题。

问题是我们需要每个传感器值的时间戳。EPS8266 可以连接到 NTP 服务器以获得当前时间,但是对于电池供电的设备来说,这将是非常昂贵的操作。另一种方法是,使用更新处理程序,并让 CouchDB 向插入数据库的每个文档追加时间戳。

为此,我们向 measurement 数据库添加带有更新功能(Update Function)的设计文档。我准备了一个脚本:在请求上添加时间戳记,并将文档保存到数据库中。

{
  "_id": "_design/update_handlers",
  "updates": {
    "add_with_timestamp": "function (doc, req) { var measurement = JSON.parse(req.body); measurement._id = new Date(); return [measurement, 'created']; }"
  }
}

通过对以下 URL 执行 HTTP POST 请求,我们可以添加一个文档:

https://couchdb/measurement/_design/update_handlers/_update/add_with_timestamp

以下是一个 post 请求的示例文档:

{
    "node": "living room",
    "temperature": 22,
    "humidity": 47
}

步骤3:客户端

对于客户端,我准备了一个 HTML/JavaScript 页面,它使用 PouchDB 来查询远程的 CouchDB 实例,并使用 Chart.js 在基于时间的折线图上绘制它。我们可以使用 PouchDB 直接连接到远程 CouchDB 实例,因为我们不需要任何复制功能。这样做,如果传感器发布数据,我们仍然会将数据推送到我们的客户端。在这个例子中,我们将数据集限制为最近的 200 个文档(读数),但是我们也可以编写一个 map 函数来获取过去 12 个小时的所有数据。

Chart.js 示例

如上所述,我只想运行一个 CouchDB 实例来管理该项目。幸运的是,我们可以通过将 HTML 页面作为附件,插入到 CouchDB 来为客户端提供服务。是的,从数据库直接提供 HTML!正如诺兰·劳森所说:

> 但是,对于所有开发人员来说,CouchDB 是一项非常令人兴奋的技术。当你退一步想一下,它是一个大胆的、疯狂的命题。这是一个大胆的声明:如果我们可以让它成为网络开发的一部分,如何将对网页开发带来更好的体验。这是一个狂野的街头疯子,用肩膀抓住随机的人,用疯狂的紧急尖叫:“我们不再需要服务器了! 我们只需要数据库! 数据库是服务器!“

要从数据库以 HTML 方式为客户服务,请使用提供的 JSON 在 measurement 数据库中创建一个设计文档。创建文档后,进行编辑,并将客户端 HTML 文件作为额外的附件。

{
  "_id": "_design/client"
}

客户端现在可以从以下 URL 访问:

https://couchdb/measurement/_design/client/chart.html

我们需要的最后一件事是一个 rewrite 规则,使 URL 更漂亮。CouchDB 使用 HTTP Rewrite Handler 来重定向请求,以便最终用户更容易输入应用程序 的URL。编辑我们刚刚创建的设计文档,并添加 rerwrite 属性。您的最终文件应如下所示:

{
  "_id": "_design/client",
  "rewrites": [
    {
      "from": "",
      "to": "chart.html",
      "method": "GET",
      "query": {}
    },
    {
      "from": "/measurement/*",
      "to": "../../../measurement/*"
    }
  ],
  "_attachments": {
    "chart.html": {
      "content_type": "text/html",
      "revpos": 32,
      "digest": "md5-sEJsbTcqnZIUqoleTnfpqg==",
      "length": 5042,
      "stub": true
    }
  }
}

客户端现在可以使用了:

https://couchdb/measurement/_design/client/_rewrite

最终的 URL 仍然有点丑,所以要完成我们使用虚拟主机来完成剩下的内容。为上述的 URL 添加虚拟主机以转发请求。在这里,我使用了一个子域,并向我的 Cloudant 帐户添加了一个 CNAME 记录。

Cloudant CNAME 示例

现在客户端的地址是:https://subdomain.website.com

数据库 API 的地址是:https://subdomain.website.com/measurement

步骤4:组装

最终,我们得到了一个解决方案,它使用 CouchDB,并且 CouchDB 仅用于存储传感器数据,检索数据并将其显示在 HTML 页面上。我们使用设计文档(Design Documents),更新函数(Update Functions),HTTP rewrite 和虚拟主机来实现这些任务。而不是客户端 -> 应用服务器 -> 数据库服务器的路由,我们直接从客户端到数据库,并将业务逻辑放在其中。我们还从数据库而不是传统的 HTTP 服务器提供HTML。

这是一种不同的思维方式,非常适合某些项目,但并不适合所有人。

扩展阅读:

init.lua

-- Wifi Settings
wifi_config = {}
wifi_config.ssid = "networkname"
wifi_config.pwd = "networkpassword"
wifi_config.auto = false

-- HTTP Settings
http_url = "https://couchdb/measurement/_design/update_handlers/_update/add_with_timestamp"
http_auth = "base64_encoded_credentials_here"

-- Application settings
sleep_seconds = 300
node_name = "living room"
dhtPin = 4

function connectWifi ()
    wifi.setmode(wifi.STATION)
    wifi.sta.config(wifi_config)

    -- Read sensor after receiving an IP, sleep otherwise
    wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, function()
        readDHT()
    end)
    wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, function()
        sleep()
    end)

    -- Connect to wifi
    wifi.sta.connect()
end

function readDHT()
    status, temp, humi = dht.read11(dhtPin)

    -- Publish sensor value if sensor read was succesful, sleep otherwise
    if (status == dht.OK) then
        publishSensorValues(temp, humi)
    else
        sleep()
    end
end

function publishSensorValues(temperature, humidity)
    -- Sleep after two seconds, prevents requests taking too long
    tmr.alarm(1, 2000, tmr.ALARM_SINGLE, function()
        sleep();
    end)

    -- Post sensor data to HTTP server, sleep when finished
    http.post(http_url,
        "Content-Type: application/json\r\n" ..
        "Authorization: Basic " .. http_auth .. "\r\n",
        [[
        {
            "node": "]] .. node_name .. [[",
            "temperature": ]] .. temperature .. [[,
            "humidity": ]] .. humidity .. [[
        }
        ]],
        function(code, data)
            sleep()
        end
    )
end

function sleep()
    -- Disconnect from wifi
    wifi.sta.disconnect()

    -- Go to deep sleep for the specified time
    node.dsleep(sleep_seconds*1000000)
end

connectWifi()

原文链接:https://bsmulders.com/2017/06/serverless-iot/

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

观光\评论区

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