最近我沉浸在物联网世界中,并被亚马逊,IBM 和 微软等公司的配置所淹没。他们为大型企业项目提供了很好的解决方案,但似乎需要大量的配置和知识来设置所有的项目。凭借我最近在 CouchDB 和 Serverless 应用程序方面的经验,我决定尝试新的东西:Serverless IoT。只需要运行 CouchDB 数据库的实例,就可以捕获传感器数据,并绘制图表上的数据?
用于此项目的硬件是带有 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 脚本(在文末):
脚本没有 while 循环或重复流。发送数据后结束。醒来后,EPS8266 将被重置,我们在脚本开始时自动启动。
要存储数据,我们需要一个 CouchDB 实例。我喜欢 Cloudant,因为我更喜欢基于云的解决方案,但也可以从 Docker 容器运行自己的 CouchDB 实例。
为了安全,要尽可能使用 HTTPS,并创建三个用户帐户:
创建名为 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
}
对于客户端,我准备了一个 HTML/JavaScript 页面,它使用 PouchDB 来查询远程的 CouchDB 实例,并使用 Chart.js 在基于时间的折线图上绘制它。我们可以使用 PouchDB 直接连接到远程 CouchDB 实例,因为我们不需要任何复制功能。这样做,如果传感器发布数据,我们仍然会将数据推送到我们的客户端。在这个例子中,我们将数据集限制为最近的 200 个文档(读数),但是我们也可以编写一个 map 函数来获取过去 12 个小时的所有数据。
如上所述,我只想运行一个 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 记录。
现在客户端的地址是:https://subdomain.website.com
数据库 API 的地址是:https://subdomain.website.com/measurement
最终,我们得到了一个解决方案,它使用 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()
观光\评论区