使用 Arduino Uno 和 IBM IoT Foundation 构建云就绪的温度传感器,第 2部分: 编写 sketch 并连接到 IBM IoT Foundation Quickstart

在这个由 4 部分组成的教程系列的 第 1 部分 中,我讨论了一个监视我的配线箱中的温度的项目的设计,该项目是使用 Arduino Uno 和 Virtuabotix DHT11温度传感器来构建的。我展示了该项目的电路构造,演示了 Arduino IDE 的安装和如何使用不同的 Arduino 示例 sketch 测试项目的各个组件。您现在已准备好了解将IoT 项目部署到云中的 sketch 设计,以及实现远程实时监视温度和湿度数据的步骤。但是,首先我需要讨论一下用来与 IBM IoT Foundation进行通信的协议:MQTT。

什么是 MQTT?
MQTT(以前称为 Message Queueing Telemetry Transport)是一个轻量型、快速的通信协议,专为物联网而设计。它起源于 IBM(最初由 AndyStanford-Clark 在这里开发),自那时起就提交给了 Organization for the Advancement of Structured InformationStandards (OASIS) 来进行标准化,该协议标准的最新版本为 3.1 版。MQTT V3.1协议规范 规定,它的用途是用作一个 “轻量型、基于代理的发布/订阅消息协议,它的设计开放、简单、轻量且容易实现。”自它引入后,“容易实现”部分已经过实践检验,因为已经开发了多个实现 MQTT 客户端的不同库。您可以在 Eclipse Paho项目页面 上找到几乎所有这些库的链接。

MQTT 非常适合用在嵌入式设备中,因为它:
1.是异步的,具有多种不同的服务质量水平,这在 Internet 连接不可靠时很重要。
2.发送较短、紧凑的消息,这使得它很适合用于低带宽情形。
3.不需要太多软件来实现客户端,这使它非常适合像 Arduino 这样具有有限的内存的设备。

MQTT 是 IBM IoT Foundation QuickStart 设计获取输入时所依据的协议。

下载并安装 Arduino MQTT 库
为 Arduino 安装针对 MQTT 的客户端库很简单,就像查找和安装您已在 第 1 部分 中看到的针对特定硬件设备的库一样。用于您的项目的特定的库可以在 Arduino Client for MQTT网站上找到,该网站介绍了该库,链接到相关文档,并提供了一个额外的 GitHub 链接,从该链接也可以下载它。在我的示例中,我使用了来自 GitHub 链接的 V1.9.1 客户端。下载 ZIP 或 TAR 文件,然后将该归档文件中的 PubSubClient 目录解压到您的 ArduinoIDE 目录的 libraries 子目录。然后重新启动 ArduinoIDE。PubSubClient>mqtt_auth、PubSubClient>mqtt_basic和 PubSubClient>mqtt_publish_in_callback 菜单项现在应该可供使用,如图 1 所示。

图 1. PubSubClient 菜单
PubSubClient 菜单选项的屏幕截图

在本地测试 MQTT目前位置在本教程系列中采用的流程是,向解决方案中引入一个新组件或技术,然后独立地测试它,以确保它能正常运行。在下载并安装 MQTT客户端软件后,我们将对它执行同样的操作。但是,仍然需要一个额外的软件来测试您的客户端。

MQTT是一个基于代理的协议:客户端连接到一个协调它们之间的通信的代理。事实上,这个流程非常简单。一组客户端向该代理注册它们感兴趣的一个主题。另一个客户端向该主题发布一条消息,该代理将该消息转发到订阅客户端。

您将用于本地测试的代理是另一个叫做 Mosquitto 的开源产品。可将它安装在您用于编写 Arduino 程序的本地 PC,然后测试 Arduino 能否与代理进行通信。

Mosquitto
您可以从 Mosquitto 网站 下载 Mosquitto。它可用于 Windows®、Mac和大多数 Linux® 变体。安装它很简单:对于 Linux,只需要安装一个新包;对于 Windows,可采用 Windows服务或一个单独的可执行文件的形式来安装该系统。如果使用 Windows,一定要清除安装为服务的复选框。从命令行运行它更容易,因为可更轻松地查看它记录的调试信息。

安装 Mosquitto 后,运行以下命令来从(任何平台上的)命令行启动它:

mosquitto -v
-v 标志表示 “详细” 日志,它表示可以查看所建立的连接的信息以及接收或发送的消息。开始将消息发送到本地代理后,在一分钟内就可以看到结果。

下载示例 sketch
下一步是下载将所有部分衔接在一起的示例 sketch(参见 下载)。您将通过 MQTT 将一条消息发布到一个 MQTT代理(首先发送到本地代理,然后将它发送到 IoT Foundation QuickStart 中包含的代理)。要让 IoT Foundation QuickStart最终解析和显示传感器数据,必须将它发布到一个名为 iot-2/evt/status/fmt/json的主题。类似地,还必须以某种特定的方式格式化该数据。如果在 IoT Foundation QuickStart 文档中查找 “将我的设备连接到QuickStart” 秘诀,就会看到数据必须具有以下 JavaScript 对象表示法 (JSON) 格式:

{
    "d": {
         "name1": "stringvalue",
         "name2": intvalue,
         ...
    }
}

示例 sketch 是一个简单的程序。从我在 第 1 部分 中指出了其他 Arduino 示例,可以看到所有 Arduino 程序都具有相同的结构。它们包含一个标准的函数集(名为setup()loop()),以及一些可选的变量声明和您在 setup()loop() 函数中使用的任何实用程序函数的声明。首先,需要在代码的开头对其中一些变量声明执行一些更改,以便可以在本地测试该程序。

对示例 sketch 的更改
与所有其他 Arduino sketch 一样,最顶部的程序包含对您在 sketch 中使用的库的引用:

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <dht11.h>

您可以看到,该 sketch 使用了以太网库来驱动以太网扩展卡,使用 DHT11 库来实现对来自 DHT11 的读数的访问,就像在 第 1 部分 中看到的示例那样。但是,第一次还要使用 PubSubClient库。要利用这些特性,需要对代码执行两处更改,以便针对本地服务器测试它:

// Update this to either the MAC address found on the sticker on your Ethernet shield (newer shields)
// or a different random hexadecimal value (change at least the last four bytes)
byte mac[]    = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
char macstr[] = "deedbafefeed";
// Note this next value is only used if you intend to test against a local MQTT server
byte localserver[] = {192, 168, 1, 98 };
// Update this value to an appropriate open IP on your local network
byte ip[]     = {192, 168, 1, 20 };

从第一条注释中可以看出,需要执行的第一处更改是更新 macmacstr 变量,使之与以太网扩展卡的 MAC地址相匹配。较新的以太网扩展卡已将此信息打印在该卡的标签上。如果使用较旧的卡,可以将最后 4 位十六进制字符更改为其他任何有效的十六进制值。接下来,将 ip变量更改为本地网络上一个开放的 IP 地址。以太网库能够使用 DHCP,如果感兴趣的话,您可以了解一下如何做,但尽可能地使用固定 IP 地址会更简单。接下来,将
localserver 变量更新为您在其上使用 Mosquitto 服务器的计算机的 IP 地址。最后,更新 sketch 以连接到您的本地 MQTT服务器,而不是 IBM IoT Foundation QuickStart 服务器。在代码中找到下面这一节:

// Uncomment this next line and comment out the line after it to test against a local MQTT server
//PubSubClient client(localserver, 1883, 0, ethClient);
PubSubClient client(servername, 1883, callback, ethClient);

取消注释包含以 client(localserver 开头的构造函数的一行,并注释掉它之下的以 client(servername开头的一行。然后保存 sketch。执行必要的更改后,可以快速查看这个简单程序的剩余部分。在两个代码段之间,我们已看到一组变量声明:

char servername[]="messaging.quickstart.internetofthings.ibmcloud.com";
String clientName = String("d:quickstart:arduino:") + macstr;
String topicName = String("iot-2/evt/status/fmt/json");
dht11 DHT11;
float tempF = 0.0;
float tempC = 0.0;
float humidity = 0.0;
EthernetClient ethClient;
可以看到,拥有针对华氏和摄氏度的温度的变量,以及针对湿度的变量。还有一个变量将包含将由 PubSub 客户端使用的以太网客户端的实例。接下来是 setup() 函数的定义:
void setup()

{
// Start the Ethernet client, open up serial port for debugging, and attach the DHT11 sensor
Ethernet.begin(mac, ip);
Serial.begin(9600);
DHT11.attach(3);
}

此函数非常简单;它使用提供的 MAC 地址和以太网客户端运行时使用的静态 IP 地址来启动该客户端。它然后启动串行端口(用于出于调试用途而与串行监视器通信),最后在引脚 3 上打开与DHT11 的通信。下一个要检查的函数是 loop(),只要 Arduino 在运行,就会持续调用该函数:

void loop()
{
  char clientStr[34];
  clientName.toCharArray(clientStr,34);
  char topicStr[26];
  topicName.toCharArray(topicStr,26);
  getData();
  if (!client.connected()) {
    Serial.print("Trying to connect to: ");
    Serial.println(clientStr);
    client.connect(clientStr);
  }
  if (client.connected() ) {
    String json = buildJson();
    char jsonStr[200];
    json.toCharArray(jsonStr,200);
    boolean pubresult = client.publish(topicStr,jsonStr);
    Serial.print("attempt to send ");
    Serial.println(jsonStr);
    Serial.print("to ");
    Serial.println(topicStr);
    if (pubresult)
      Serial.println("successfully sent");
    else
      Serial.println("unsuccessfully sent");
  }
  delay(5000);
}
可以看到,此函数的大部分内容专门用于生成调试输出。但是,还有一些重要元素需要注意。靠近该函数顶部是对getData()函数(接下来将解释)的调用,用于获取来自 DHT11 的传感器数据。然后,loop() 函数会检查 PubSubClient客户端是否已连接。如果未连接,那么 loop() 函数会尝试连接到 MQTT 代理。如果已经连接,那么 loop() 函数会使用buildJson() 格式化一个 JSON 字符串,然后使用 PubSubClientpublish() 方法将该字符串发布到 MQTT 代理。最后,loop() 在函数底部等待 5,000 毫秒,之后Arduino 运行时周期返回并再次调用该函数。接下来看看 getData() 函数:
void getData() {
  int chk = DHT11.read();
  switch (chk)
  {
  case 0:
    Serial.println("Read OK");
    humidity = (float)DHT11.humidity;
    tempF = DHT11.fahrenheit();
    tempC = DHT11.temperature;
    break;
  case -1:
    Serial.println("Checksum error");
    break;
  case -2:
    Serial.println("Time out error");
    break;
  default:
    Serial.println("Unknown error");
    break;
  }
}

此函数检查来自 DHT11 库的数据的状态,然后读取已准备好的传感器数据值;否则,它在控制台上打印一条提供信息的消息。最后看看格式化 JSON 输出的函数:

String buildJson() {
  String data = "{";
  data+="\n";
  data+= "\"d\": {";
  data+="\n";
  data+="\"myName\": \"Arduino DHT11\",";
  data+="\n";
  data+="\"temperature (F)\": ";
  data+=(int)tempF;
  data+= ",";
  data+="\n";
  data+="\"temperature (C)\": ";
  data+=(int)tempC;
  data+= ",";
  data+="\n";
  data+="\"humidity\": ";
  data+=(int)humidity;
  data+="\n";
  data+="}";
  data+="\n";
  data+="}";
  return data;
}

在 Arduino 中实现的 Processing 的一个缺陷是,它缺乏用来处理字符串的良好工具。您可能已注意到,需要在String类实例与字符类型之间制定一些古怪的约定,以便调用需要一种或另一种实例的函数。类似地,由于缺乏很好的字符串格式化库,格式化一个像这样的简单 JSON 字符串都可能很难 —即使存在这样的函数来格式化控制台输出。

对一个本地 Mosquitto 代理执行测试
现在,您基本上完成第一部分了。接下来按照 第 1 部分 中讨论的上传过程,将修改的 sketch 上传到 Arduino。在 Arduino IDE 的状态栏中看到状态 “Done uploading.”后,立即按下 Ctrl-Shift-M 打开串行监视器。

验证您的输出
现在,假设一切正常,您应该在启动 Mosquitto 的终端窗口中看到以下类型的输出:1405807697:

mosquitto version 1.2.3 (build date 22/12/2013 13:36:32.54) starting
1405807697: Using default config.
1405807697: Opening ipv6 listen socket on port 1883.
1405807697: Opening ipv4 listen socket on port 1883.
1405807718: New connection from 192.168.1.20 on port 1883.
1405807718: New client connected from 192.168.1.20 as d:quickstart:arduino:deedb
afefeed (c2, k15).
1405807718: Sending CONNACK to d:quickstart:arduino:deedbafefeed (0)
1405807718: Received PUBLISH from d:quickstart:arduino:deedbafefeed (d0, q0, r0,
 m0, 'iot-2/evt/status/fmt/json', ... (100 bytes))
1405807723: Socket error on client d:quickstart:arduino:deedbafefeed, disconnect
ing.
1405807723: New connection from 192.168.1.20 on port 1883.
1405807723: New client connected from 192.168.1.20 as d:quickstart:arduino:deedb
afefeed (c2, k15).
1405807723: Sending CONNACK to d:quickstart:arduino:deedbafefeed (0)
1405807723: Received PUBLISH from d:quickstart:arduino:deedbafefeed (d0, q0, r0,
 m0, 'iot-2/evt/status/fmt/json', ... (100 bytes))
1405807729: Received PUBLISH from d:quickstart:arduino:deedbafefeed (d0, q0, r0,
 m0, 'iot-2/evt/status/fmt/json', ... (100 bytes))
1405807734: Received PUBLISH from d:quickstart:arduino:deedbafefeed (d0, q0, r0,
 m0, 'iot-2/evt/status/fmt/json', ... (100 bytes))

了解您正确完成了操作的关键是,您将在最后几行中看到 Received PUBLISH from...。这意味着 Arduino sketch 已成功连接到Mosquitto 代理。现在查看您的串行监视器;它应该包含类似以下信息的消息:

Read OK
Trying to connect to d:quickstart:arduino:deedbafefeed
attempt to send {
"d": {
"myName": "Arduino DHT11",
"temperature (F)": 71,
"temperature (C)": 22,
"humidity": 43
}
}
to iot-2/evt/status/fmt/json
successfully sent
将 Arduino 连接到 IoT Foundation Quickstart
所有功能都能在本地运行后,可以尝试对 IoT Foundation QuickStart 运行这些功能了。在浏览器中访问 IBM Internet of Things Foundation网站,如图 2 所示。

图 2. IBM IoT Foundation 主页

单击显示 Try it out with our Quickstart 的按钮。您将看到图 3 中所示的页面。

图 3. IBM IoT Foundation Quickstart 欢迎页面

在本教程中,您将使用 IBM IoT Foundation Quickstart 的内置特性描绘来自 Arduino 传感器的实时数据的图表。在 第 3部分 和 第 4部分 中,将编写一个应用程序来对您收到的数据执行一些更有趣的操作。

IoT Foundation 有一些针对其他设备的令人兴奋的方案。如果您有一个更加复杂的设备,比如 Intel Galileo 或 RaspberryPi,您可能希望尝试这些方案。对于目前,只需键入您的 Arduino 的 MAC 地址。但在单击 Go 之前,还需要在 sketch中执行一项更改。

更改 sketch
您是否还记得之前您将 sketch 更改为指向本地 MQTT 服务器,而不是 IoT Foundation 上的 MQTT服务器?现在是时候更改回来了。注释和取消注释您在原始代码中更改的行:

// Uncomment this next line and comment out the line after it to test against a local MQTT server
//PubSubClient client(localserver, 1883, 0, ethClient);
PubSubClient client(servername, 1883, callback, ethClient);

执行此更改后,保存 sketch 并将它上传到 Arduino。如果想要重新启动串行监视器,可以这么做,但在您查看 IoT Foundation时才会看到结果如何。返回到浏览器中,单击 Mac 地址页面上的 Go 按钮,然后等待几分钟。

描绘数据的图表
如果各项功能运行正常,您会看到一个页面显示了您的温度读数的图表,如图 4 中所示。

图 4. 数据曲线图

大功告成!您现在已经构建了一个可作为全球 IoT的一部分的设备。对于我的这个具体的简单情形,这是我需要的全部功能:一个曲线图,我可以观察其中的几小时数据,以检查配线箱内部的温度。坏消息是,甚至在热天的中午,预计的温度也绝不会高于73 华氏度,所以我又不得不返回到制图板上来查找我的问题。而您学习如何利用 IoT 的旅程才刚刚开始。

结束语
在本系列的接下来两部教程中,您将编写自己的应用程序(在 IBM® Bluemix 上运行™),它可保留从 Arduino
发送的数据,支持显示和对比当前数据与历史数据。

下载

MQTT_IOT_SENSORS.ino

标签: arduino uno, 温度传感器, ibm iot foundation, mosquitto