<br><strong>简介</strong> </br>
本文我们将介绍如何将这些传感器与致动器连接至所构建的Mosquitto MQTT服务器,以将这些传感器变成真正的物联网传感器。
<strong>使用 MQTT</strong>
<strong>基本MQTT发布/订阅</strong>
MQTT 是构建在 TCP/IP 协议顶层的发布/订阅协议。 MQTT 是一种常用于 M2M(机对 机)通信的协议。 MQTT 主要由两种组件构成:MQTT 客户端和 MQTT 代理。 MQTT 客户 端向特定主题发布消息,或订阅和收听特定主题的消息。 MQTT 代理接收 MQTT 发布程 序发布的所有消息,并将相关消息传输给所有 MQTT 订阅程序。 发布程序和订阅程序之 间无需进行交互,只有主题和消息之间具备相关性。 为保持正常通信,发布程序和订阅程序需同意使用常见的主题名称和消息格式。
发布或订阅 MQTT 主题时,需使用 MQTT 客户端程序。 在 Mosquitto MQTT 分发版中 ,发布客户端称为 ‘mosquitto_pub’,订阅客户端称为 ‘mosquitto_sub’。
下列截屏显示了我们即将介绍的活动序列。 红色窗口显示 ‘mosquitto_sub’ 命令输出,黑色窗口显示 ‘mosquitto_pub’ 命令。 命令均带有标记以便我们引用。
<center><img src="http://intel.eetrend.com/files/2016-03/wen_zhang_/100001224-908-capture…; alt=""></center>
MQTT 客户端所需的参数至少包括:代理 IP 地址、主题名称、发布客户端以及消息。 假设我们已有运行于 127.0.0.1(本地主机)的 MQTT 代理,那么下列命令将收听所有发 布至主题 ‘edison/hello’的消息。
1 sub 3> mosquitto_sub –h localhost –t edison/hello
发布主题 ‘edison/hello’:
1 pub 3> mosquitto_pub –h localhost –t edison/hello –m “Message#1: Hello World!”
当 MQTT 代理收到将信息 ‘Message#1: Hello World’ 从发布 客户端 (pub3)发布至主题 ‘edison/hello’,它要扫描正收听该 主题 ‘edison/hello’ 的订阅程序。 如果发现该订阅客户端 (sub3) 正收听相同的主题,MQTT 代理会将把该消息传输至 (sub3)。 当订阅客 户端 (sub3) 接收到该消息,将向终端窗口重复消息内容。 在 (sub 3) 和 (pub 3) 命令中,默认 MQTT 端口编号隐式假定为 1883,且不在命 令行中指定。 (sub 4) 和 (pub 4) 明确提供端口编号。 在 (sub 5) 和 (pub 5) 命令中,选项 ‘-v’ 添加至 订阅参数,以便输入主题名称和消息。 这有助于使用通配符订阅多个主题。 订阅客户端 可持久运行,以便看到发布给命令 (pub 6) 中主题 ‘edison/hello’ 的其他消息。
一旦消息发送至订阅客户端,MQTT 代理行为默认立即丢弃该消息。 如果客户端 (pub 7) 发布新主题 ‘edison/hello1’,订阅客户端 (sub 5)正收听主题 ‘edison/hello’,该订阅客户端则 无法知晓该新主题。 然后,订阅客户端订阅新主题(sub 6),之前发布给主题 ‘edison/hello1’ 的消息则已被丢弃。 它将接收发布的后续主题 ‘edison/hello1’,如命令 (pub 8)所示。 其他的选项 可告知 MQTT 代理保留最后一条发布至主题的消息,以及关于代理应如何传递消息的其他 服务质量 (QoS) 指令。
根据端口配置的不同,还可能需要使用其他参数。 我们稍后将予以讨论。
<strong>订阅多个 MQTT 主题</strong>
通常有从发布程序订阅多个主题的 MQTT 客户端,然后根据接收的消息执行某项操作 。 此时需要详细介绍如何组织 MQTT 主题。
MQTT 主题通过用于表示子主题的字符 ‘/’ 以目录结构的形式进行组织 。 下面为不同主题的示例:
1 <a href="<a href=" target="_blank" rel="nofollow">Temperature/Building1/Room1/Location1
2 Temperature/Building1/Room1/Location2
3 Temperature/Building2/Room2
4 Temperature/Outdoor/West
5 Temperature/Outdoor/East
6 Temperature/Average</a>
订阅程序可订阅单个主题,比如 ‘Temperature/Outdoor/West’,也可使 用通配符订阅多个主题。 MQTT 中包含两个通配符:‘+’ 和 ‘#’。 通配符 ‘+’ 用于订阅层级相同的主题。 通配符 ‘#’ 用于订阅特定主题下的所有子主题。 例如,如果订阅主题 ‘Temperature/#’,只要上述列表中的主题有新消息,就会返回结果。 如果 订阅 ‘Temperature/+’,将仅返回 ‘Temperature/Average’, 因为其他条目均为子主题。
<strong>配置安全的 Mosquitto 代理</strong>
从根本上来说,连接 MQTT 客户端与 Mosquitto 服务器的方法有 4 种。
1、安全打开端口:这种方法能够在部署前方便地开发和测试 MQTT 设备。 代 理和客户端之间的数据通过纯文本传输,只有使用正确的工具,才能查看数据传输。
2、用户和密码保护:这种方法基本上用于验证代理的客户端。 它不对数据传 输进行加密。
3、TLS-PSK 加密:客户端和代理拥有共享安全密钥,可用于协商安全连接。 Mosquitto 1.3.5 或更高版本可提供这种加密。
4、SSL 加密:这是 Mosquitto MQTT 分发版提供的最高级的加密和验证机制。 如果采用 SSL 加密,需从可信的认证机构 (CA) (比如 Verisign 或 Equifax)获取服 务器证书。 通常,可创建自签名证书用于测试,然后将其用于签署服务器证书。 这种加 密方法虽然比较安全,但实施过程比较繁琐,因为它要求所有客户端设备拥有准确的证书 ,而且必须开发现场升级机制,以确保客户端和代理之间的证书版本保持同步。
如欲配置安全的 Mosquitto MQTT 代理,需使用下列配置指令。 我们在此案例中使用 的证书为仅针对测试的自签名证书,不能用于生产服务器。
01 port 1883
02 password_file /home/mosquitto/conf/pwdfile.txt
03 log_type debug
04 log_type error
05 log_type warning
06 log_type information
07 log_type notice
08 user root
09 pid_file /home/mosquitto/logs/mosquitto.pid
10 persistence true
11 persistence_location /home/mosquitto/db/
12 log_dest file /home/mosquitto/logs/mosquitto.log
13
14 listener 1995
15 # port 1995 is the TLS-PSK secure port, client must provide
16 # --psk-identity and --psk to access
17 psk_file /home/mosquitto/conf/pskfile.txt
18 # psk_hint must be present or port 1995 won't work properly
19 psk_hint hint
20
21 listener 1994
22 # port 1994 is the SSL secure port, client must present
23 # a certificate from the certificate authority that this server trust
24 # e.g. ca.crt
25 cafile /home/mosquitto/certs/ca.crt
26 certfile /home/mosquitto/certs/server.crt
27 keyfile /home/mosquitto/certs/server.key
文件 pwdfile.txt 为密码文件,加密方式类似 Linux 密码文件。 该文件 可通过使用 Mosquitto 软件自带的 mosquitto_passwd 实用程序生成。 文件 pskfile.txt 为预共享密码文件,可用于 TLS-PSK 连接。 该文件的格式为文 本文件,每行一个用户:密码对。 用于 TLS-PSK 的密码只能使用十六进制字符,不区分 大小写。 下列所示为密码_文件和 psk_文件示例。
1 /* sample Mosquitto password_file */
2 user:$6$Su4WZkkQ0LmqeD/X$Q57AYfcu27McE14/MojWgto7fmloRyrZ7BpTtKOkME8UZzJZ5hGXpOea81RlgcXttJbeRFY9s0f+UTY2dO5xdg==
3 /* sample Mosquitto PSK file */
4 user1:deadbeef
5 user2:DEADBEEF0123456789ABCDEF0123456789abcdef0123456789abcdef0123456789abcdef
<strong>集成 MQTT 与物联网传感器</strong>
由于 Edison 开发板为真正的 Linux 开发板(将 Arduino sketch 作为其进程之一运 行),我们将充分利用现有的 Mosquitto MQTT 软件包(之前构建的)。 在 Arduino sketch 中,我们只使用 Linux 编程设施来调用 ‘mosquitto_sub’ 和 ‘mosquitto_pub’ 程序。 这种方法有几大优势:
1、我们无需为 Arduino 重新编写 MQTT 客户端。 Mosquitto MQTT 软件包是 被广泛接受,并经过充分测试的软件。 我们只需创建包装程序类激活 Arduino sketch 的用法。
2、Mosquitto MQTT 客户端能够借助 SSL 实现安全连接。 另一方面,通用 Arduino 的小型微控制器无法满足 SSL 的计算要求。
3、如果发现有不同或更好的 MQTT 软件包,或发布新版 MQTT 软件,我们可以 重复使用大多数现有代码并添加新代码,就可运用新软件包的全新功能。
<strong>MQTTClient 类</strong>
由于英特尔 Edison 开发板是一款全面的 Linux 操作系统,所有 Linux 编程设施均 可供 Arduino sketch 使用。 我们将创建可用于 sketch 的包装程序类,名为 MQTTClient。 采用 MQTTClient 方法时,我们将使用 Linux 系统调用来调用 mosquitto_sub 和 mosquitto_pub 程序,后者可为我们执行实际 MQTT 数据传输。 下列为最简 MQTTClient 的类定义。
01 // File: MQTTClient.h
02 /*
03 Minimalist MQTT Client using mosquitto_sub and mosquitto_pub
04 Assume Mosquitto MQTT server already installed and mosquitto_pub/sub
05 are in search path
06 */
07
08 #ifndef __MQTTClient_H__
09 #define __MQTTClient_H__
10
11 #include <Arduino.h>
12 #include <stdio.h>
13
14 enum security_mode {OPEN = 0, SSL = 1, PSK = 2};
15
16 class MQTTClient {
17
18 public:
19 MQTTClient();
20 ~MQTTClient();
21 void begin(char * broker, int port, security_mode mode,
22 char* certificate_file, char *psk_identity, char *psk);
23 boolean publish(char *topic, char *message);
24 boolean subscribe(char* topic, void (*callback)(char *topic, char* message));
25 boolean loop();
26 boolean available();
27 void close();
28
29 private:
30 void parseDataBuffer();
31 FILE* spipe;
32 char mqtt_broker[32];
33 security_mode mode;
34 char topicString[64];
35 char certificate_file[64];
36 char psk_identity[32];
37 char psk_password[32];
38 int serverPort;
39 char *topic;
40 char *message;
41 boolean retain_flag;
42 void (*callback_function)(char* topic, char* message);
43 char dataBuffer[256];
44 };
45 #endif
如欲使用该类,首先创建 MQTTClient 对象,然后使用 MQTTClient::begin() 初始化 MQTTClient::subscribe() 和 MQTTClient::publish() 将用于连接 MQTT 代理的各种变量。 这些变量包括:
<li>mqtt_broker:MQTT 代理的名称, 可为 IP 地址或 DNS 名称。 在本文中,我们将使用 ‘本地主机’,因为代理运行于与 Arduino sketch 相同的 Edison 开发板,。 如果 Edison 开发板配置为使用静态 IP 地址,可代 之以使用该地址。</li>
<li>serverPort:待使用的端口。 我们采用不同的安全性策略在 Mosquitto 代理上配置 3 个不同的端口:</li>
(1)端口 1883:MQTT 默认端口,对所有客户端开放。
(2)端口 1994:配置为安全 SSL 端口。 用户必须提供 CA (出具服务器证书)出具的 SSL 证书。
(3)端口1995:配置为安全 TLS-PSK 端口。 用户可使用用户 身份和预共享密码访问该端口。
<li>mode:安全使用模式。 目前,安全模式可为 OPEN、SSL 或 PSK。 根据选定的安全模式,为证书、用户身份和预共享密码提供该方法的其他参数。 不适用 于指定安全模式的参数将被忽略。</li>
如欲订阅主题,需调用包含主题名称的 MQTTClient::subscribe(),以及回 调函数,以在出现新消息将时调用。 如欲发布主题,需调用 MQTTClient::publish (),并提供待发布的主题名称和消息。
关于订阅和发布方法,可使用该方法的参数和保存的参数组成合适的命令字符串,以 调用 mosquitto_sub 或 mosquitto_pub。 Linux 管道随后打开,并 同时执行命令字符串和传回至 Arduino sketch 的结果。 发布时,消息发送完成后,应 立即关闭管道。 订阅时,需打开管道,以便接收新数据。 在 Arduino sketch 中,需要 定期检查管道内是否有新数据。 MQTTClient::loop()方法旨在检查管道内的数 据,并调用回调函数处理新消息。 由于检查时空 Linux 管道将会被阻塞,因此我们将其 设定为非阻塞管道,以便MQTTClient::loop() 方法返回管道是否为空管道。 下列伪码展示了 MQTTClient 类的常见用法。
01 #include <MQTTClient.h>
02 #define SAMPLING_PERIOD 100
03 #define PUBLISH_INTERVAL 30000
04
05 MQTTClient mqttClient;
06
07 void setup() {
08 mqttClient.begin(“localhost”,1833,PSK,NULL,”psk_user”,”psk_password”);
09 mqttClient.subscribe(“edison/#”,myCallBack);
10 }
11
12 void myCallBack(char* topic, char* message) {
13 // scan for matching topic, scan for matching command, do something useful
14 }
15
16 unsigned long publishTime = millis();
17 void loop() {
18 mqttClient.loop(); // check for new message
19 if (millis() > publishTime) {
20 publishTime = millis() + PUBLISH_INTERVAL;
21 mqttClient.publish(“edison/sensor1”,”sensor1value”);
22 mqttClient.publish(“edison/sensor2”,”sensor2value”);
23 }
24 delay(SAMPLING_PERIOD);
25 }
变量 “SAMPLING_PERIOD” 决定了代码检查新消息的频率,以及是否 为该代码选择新消息,从而使 sketch 的行为及时到达并形成有效行为。 在大多数情况下,MQTTClient::loop() 方法可快速返回,而且频繁取样循环时的开销最低。 发布间隔取决于变量 “PUBLISH_INTERVAL”,并且应选择针对特定传感器的合适数据速率,例如,如果仅为提供信息之用,温 度传感器应每隔一分钟发布一次数值;而烟雾传感器可能希望每隔几秒更新结果,以便收 听烟雾传感器消息的订阅程序能够及时采取行动。
MQTTClient 类的实施流程如下所示。
001 // File: MQTTClient.cpp
002 #include "MQTTClient.h"
003 #include <fcntl.h>
004
005 /*======================================================================
006 Constructor/Destructor
007 ========================================================================*/
008 MQTTClient::MQTTClient()
009 {
010 }
011 MQTTClient::~MQTTClient()
012 {
013 close();
014 }
015 void MQTTClient::close()
016 {
017 if (spipe) {
018 fclose(spipe);
019 }
020 }
021 /*========================================================================
022 Initialization. Store variables to be used for subscribe/publish calls
023 ==========================================================================*/
024 void MQTTClient::begin(char *broker, int port, security_mode smode,
025 char* cafile, char *user, char *psk)
026 {
027 strcpy(mqtt_broker, broker);
028 serverPort = port;
029 mode = smode;
030 if (mode == SSL) {
031 strcpy(certificate_file, cafile);
032 }
033 else if (mode == PSK) {
034 strcpy(psk_identity, user);
035 strcpy(psk_password, psk);
036 }
037 Serial.println("MQTTClient initialized");
038 Serial.print("Broker: "); Serial.println(mqtt_broker);
039 Serial.print("Port: "); Serial.println(serverPort);
040 Serial.print("Mode: "); Serial.println(mode);
041 }
042 /*=======================================================================
043 Subscribe to a topic, (*callback) is a function to be called when client
044 receive a message
045 =========================================================================*/
046 boolean MQTTClient::subscribe(char* topic,
047 void (*callback)(char* topic, char* message))
048 {
049 char cmdString[256];
050
051 if (mqtt_broker == NULL) {
052 return false;
053 }
054 if (topic == NULL) {
055 return false;
056 }
057
058 callback_function = callback;
059 switch(mode) {
060 case OPEN:
061 sprintf(cmdString,
062 "mosquitto_sub -h %s -p %d -t %s -v",
063 mqtt_broker, serverPort, topic);
064 break;
065 case SSL:
066 sprintf(cmdString,
067 "mosquitto_sub -h %s -p %d -t %s -v --cafile %s",
068 mqtt_broker, serverPort, topic, certificate_file);
069 break;
070 case PSK:
071 sprintf(cmdString,
072 "mosquitto_sub -h %s -p %d -t %s -v --psk-identity %s --psk %s",
073 mqtt_broker, serverPort, topic, psk_identity, psk_password);
074 break;
075 default:
076 break;
077 }
078 if ((spipe = (FILE*)popen(cmdString, "r")) != NULL) {
079 // we need to set the pipe read mode to non-blocking
080 int fd = fileno(spipe);
081 int flags = fcntl(fd, F_GETFL, 0);
082 flags |= O_NONBLOCK;
083 fcntl(fd, F_SETFL, flags);
084 strcpy(topicString, topic);
085 return true;
088 }
087 else {
088 return false;
089 }
090 }
091 /*====================================================================
092 Check if there is data in the pipe,
093 if true, parse topic and message and execute callback function
094 return if pipe is empty
095 ======================================================================*/
096 boolean MQTTClient::loop()
097 {
098 if (fgets(dataBuffer, sizeof(dataBuffer), spipe)) {
099 parseDataBuffer();
100 callback_function(topic, message);
101 }
102 }
103 /*====================================================================
104 Publish a message on the given topic
105 ======================================================================*/
106 boolean MQTTClient::publish(char *topic, char *message)
107 {
108 FILE* ppipe;
109 char cmdString[256];
110 boolean retval = false;
111 if (this->mqtt_broker == NULL) {
112 return false;
113 }
114 if (topic == NULL) {
115 return false;
116 }
117 switch (this->mode) {
118 case OPEN:
119 sprintf(cmdString,
120 "mosquitto_pub -h %s -p %d -t %s -m \"%s\" %s",
121 mqtt_broker, serverPort, topic, message, retain_flag?"-r":"");
122 break;
123 case SSL:
124 sprintf(cmdString,
125 "mosquitto_pub -h %s -p %d --cafile %s -t %s -m \"%s\" %s",
126 mqtt_broker, serverPort, certificate_file,
127 topic, message, retain_flag?"-r":"");
128 break;
129 case PSK:
130 sprintf(cmdString,
131 "mosquitto_pub -h %s -p %d --psk-identity %s --psk %s -t %s -m \"%s\" %s",
132 mqtt_broker, serverPort, psk_identity, psk_password,
133 topic, message, retain_flag?"-r":"");
134 break;
135 }
136 if (!(ppipe = (FILE *)popen(cmdString, "w"))) {
137 retval = false;
138 }
139 if (fputs(cmdString, ppipe) != EOF) {
140 retval = true;
141 }
142 else {
143 retval = false;
144 }
145 fclose(ppipe);
146 return retval;
147 }
148 /*======================================================================
149 Parse data in the data buffer to topic and message buffer
150 delimiter is the first space
151 if there is only one recognizable string, it is assumed a message
152 string and topic is set to NULL
153 ========================================================================*/
154 void MQTTClient::parseDataBuffer()
155 {
156 topic = dataBuffer;
157 message = dataBuffer;
158 while((*message) != 0) {
159 if ((*message) == 0x20) {
160 // replace the first space with the NULL character
161 (*message) = 0;
162 message++;
163 break;
164 }
165 else {
166 message++;
167 }
168 }
169 if (strlen(message) == 0) {
170 topic = NULL;
171 message = dataBuffer;
172 }
173 }
<strong>传感器节点</strong>
在博文“<a href="https://software.intel.com/zh-cn/blogs/2015/03/20/using-intel-edison-bu… Edison: 构建物联网传感器节点</a>”中,我们通过使用传感器和制动器构建了传感器节点:
1、运行传感器:检测运动
2、温度传感器:测量环境温度
3、光传感器:测量环境照度
4、按钮:检测用户动作,比如用户按下按钮
5、红色 LED:感应到按钮时进行切换
6、绿色 LED:运动传感器检测到运动时亮起
<center><img src="http://intel.eetrend.com/files/2016-03/wen_zhang_/100001224-909-capture…; alt=""></center>
通过使用 MQTT,我们可定期向基于 Edison 开发板运行的 Mosquitto 代理发布各种 传感器读数,并订阅所有主题 ‘edison/#’。 我们对传感器节点 的运行方式作了细微调整:
1、按钮不再切换红色 LED。 经过调整,按钮用于切换布尔变量的信号,该变量将传输至 MQTT 订阅程序。
2、红色 LED 现在只能通过 MQTT 消息进行控制。 它可代表互联网控制的灯, 可远程开关。
3、绿色 LED 的行为现在通过 MQTT 进行控制。 编程后,它可追踪运动传感器 的活动,也可被禁用。
主 Arduino sketch 如下所示。
001 // File: MQTT_IoT_Sensor.ino
002
003 /******************************************************************************
004 Internet of Thing Sensor Node using Intel Edison Development board
005 ******************************************************************************/
006
007 #include <stdio.h>
008 #include <Arduino.h>
009
010 #include "sensors.h"
011 #include "MQTTClient.h"
012
013 // Global variables
014 unsigned long updateTime = 0;
015 // PIR variables
016 volatile unsigned long activityMeasure;
017 volatile unsigned long activityStart;
018 volatile boolean motionLED = true;
019 unsigned long resetActivityCounterTime;
020
021 // button variables
022 boolean toggleLED = false;
023 volatile unsigned long previousEdgeTime = 0;
024 volatile unsigned long count = 0;
025 volatile boolean arm_alarm = false;
026
027 // MQTT Client
028 #define SECURE_MODE 2
029 MQTTClient mqttClient;
030 char fmtString[256]; // utility string
031 char topicString[64]; // topic for publishing
032 char msgString[64]; // message
033
034 /*****************************************************************************
035 Setup
036 ******************************************************************************/
037 void setup() {
038 Serial.begin(115200);
039 delay(3000);
040 Serial.println("Ready");
041
042 pinMode(RED_LED, OUTPUT);
043 pinMode(GREEN_LED, OUTPUT);
044 pinMode(BUTTON, INPUT_PULLUP);
045 pinMode(PIR_SENSOR, INPUT);
046
047 // Button interrupt. Trigger interrupt when button is released
048 attachInterrupt(BUTTON, buttonISR, RISING);
049
050 // PIR interrupt. We want both edges to compute pulse width
051 attachInterrupt(PIR_SENSOR, pirISR, CHANGE);
052 digitalWrite(RED_LED, HIGH);
053 digitalWrite(GREEN_LED,LOW);
054 resetActivityCounterTime = 0;
055
056 // initializing MQTTClient
057 #if ( SECURE_MODE == 0 )
058 Serial.println("No security");
059 mqttClient.begin("localhost", 1883, OPEN, NULL, NULL, NULL);
060 #elif ( SECURE_MODE == 1 )
061 Serial.println("SSL security");
062 mqttClient.begin("localhost", 1994, SSL,
063 "/home/mosquitto/certs/ca.crt", NULL, NULL);
064 #elif ( SECURE_MODE == 2 )
065 Serial.println("TLS-PSK security");
066 mqttClient.begin("localhost", 1995, PSK, NULL, "user", "deadbeef");
067 #endif
068
069 // subscribe to all topics published under edison
070 mqttClient.subscribe("edison/#", mqttCallback);
071 mqttClient.publish("edison/bootMsg","Booted");
072 digitalWrite(RED_LED, LOW);
073 }
074 /**************************************************************************
075 MQTT Callback function
076 **************************************************************************/
077 void mqttCallback(char* topic, char* message)
078 {
079 sprintf(fmtString, "mqttCallback(), topic: %s, message: %s",topic,message);
080 Serial.print(fmtString);
081 // check for matching topic first
082 if (strcmp(topic,"edison/LED") == 0) {
083 // then execute command as appropriate
084 if (message[0] == 'H') {
085 digitalWrite(RED_LED, HIGH);
086 toggleLED = false;
087 }
088 else if (message[0] == 'L') {
089 digitalWrite(RED_LED, LOW);
090 toggleLED = false;
091 }
092 else if (message[0] == 'B') {
093 toggleLED = true;
094 }
095 }
096 if (strcmp(topic, "edison/motionLED") == 0) {
097 // note that there is an extra carriage return at the end of the message
098 // using strncmp to exclude the last carriage return
099 if (strncmp(message, "OFF", 3) == 0) {
100 digitalWrite(GREEN_LED, LOW);
101 motionLED = false;
102 }
103 else if (strncmp(message, "ON", 2) == 0) {
104 motionLED = true;
105 }
106 }
107 }
108 /***********************************************************************
109 Main processing loop
110 ***********************************************************************/
111 void loop() {
112
113 // check for any new message from mqtt_sub
114 mqttClient.loop();
115
116 if (millis() > resetActivityCounterTime) {
117 resetActivityCounterTime = millis() + 60000;
118 // publish motion level
119 sprintf(msgString,"%.0f",100.0*activityMeasure/60000.0);
120 mqttClient.publish("edison/ActivityLevel",msgString);
121 activityMeasure = 0;
122 }
123 if (millis() > updateTime) {
124 updateTime = millis() + 10000;
125 // publish temperature
126 sprintf(msgString,"%.1f",readTemperature(TEMP_SENSOR));
127 mqttClient.publish("edison/Temperature",msgString);
128
129 // publish light sensor reading
130 sprintf(msgString,"%d",readLightSensor(LIGHT_SENSOR));
131 mqttClient.publish("edison/LightSensor",msgString);
132
133 // publish arm_alarm
134 sprintf(msgString,"%s", (arm_alarm == true)? "ARMED" : "NOTARMED");
135 mqttClient.publish("edison/alarm_status", msgString);
136 }
137
138 if (toggleLED == true) {
139 digitalWrite(RED_LED, digitalRead(RED_LED) ^ 1);
140 }
141 delay(100);
142 }
代码大多具备自解释性。 我们将传感器处理代码移至单独的模块: sensors.h 和 sensors.cpp,以增强可读性。 设置主 Arduino sketch 中的 SECURE_MODE 变量后,将允许使用不同的安全模式。 只要 MQTT 代理发布新消息,可随时调用回调函数 mqttCallback()。 订阅的 MQTT 主题 名称和消息传递给回调函数,如果与一套预定义行动相匹配,将对它们进行扫描并采取行 动。 回调函数是控制互联网的 IOT 传感器的主要机制。 在该案例中,发布给主题 ‘edison/LED’ 的消息可用于开启、关闭或闪烁红色 LED,发布给 主题 ‘edison/motionLED’ 的消息可用于控制绿色 LED 的行为: 是否追踪运动传感器输出。
01 // File: sensors.h
02 //
03 #define USE_TMP036 0
04
05 #define RED_LED 10 // Red LED
06 #define GREEN_LED 11 // Green LED
07 #define BUTTON 13 // push button with 10K pull up resistor
08 #define PIR_SENSOR 12 // Infrared motion sensor
09 #define LIGHT_SENSOR A0 // light sensor
10 #define TEMP_SENSOR A1 // TMP36 or LM35 analog temperature sensor
11
12 #define MIN_PULSE_SEPARATION 200 // for debouncing button
13 #define ADC_STEPSIZE 4.61 // in mV, for temperature conversion.
14
15 #if (USE_TMP036 == 1)
16 #define TEMP_SENSOR_OFFSET_VOLTAGE 750
17 #define TEMP_SENSOR_OFFSET_TEMPERATURE 25
18 #else // LM35 temperature sensor
19 #define TEMP_SENSOR_OFFSET_VOLTAGE 0
20 #define TEMP_SENSOR_OFFSET_TEMPERATURE 0
21 #endif
22
23 // Global variables
24 extern unsigned long updateTime;
25 // PIR variables
26 extern volatile unsigned long activityMeasure;
27 extern volatile unsigned long activityStart;
28 extern volatile boolean motionLED;
29 extern unsigned long resetActivityCounterTime;
30
31 // button variables
32 extern boolean toggleLED;
33 extern volatile unsigned long previousEdgeTime;
34 extern volatile unsigned long count;
35 extern volatile boolean arm_alarm;
36 float readTemperature(int analogPin);
37 int readLightSensor(int analogPin);
38 void buttonISR();
39 void pirISR();
01 // File: sensors.cpp
02 #include <Arduino.h>
03 #include "sensors.h"
04 /***********************************************************************************
05 PIR Sensor
06 Each time the sensor detect motion, the output pin goes HIGH and stay HIGH as
07 long as there is motion and goes LOW 2 or 3 seconds after motion ceased
08 We will count the duration when the PIR sensor output is HIGH as a measure of
09 Activity. The main loop will report the activity level as percentage of time in the
10 previous time interval that motion was detected
11 ************************************************************************************/
12 void pirISR()
13 {
14 int pirReading;
15 unsigned long timestamp;
16 timestamp = millis();
17 pirReading = digitalRead(PIR_SENSOR);
18 if (pirReading == 1) {
19 // mark the start of the pulse
20 activityStart = timestamp;
21 }
22 else {
23 int pulseWidth = timestamp-activityStart;
24 activityMeasure += pulseWidth;
25 }
26 // light up GREEN LED when motion is detected
27 if (motionLED == true) {
28 digitalWrite(GREEN_LED, pirReading);
29 }
30 }
31 /************************************************************************
32 return light sensor reading
33 ************************************************************************/
34 int readLightSensor(int sensorPin)
35 {
36 return analogRead(sensorPin);
37 }
38 /***********************************************************************
39 return temperature in Fahrenheit degrees
40 ***********************************************************************/
41 float readTemperature(int sensorPin)
42 {
43 int sensorReading;
44 float temperature;
45 sensorReading = analogRead(sensorPin);
46 // convert to millivolts
47 temperature = sensorReading * ADC_STEPSIZE;
48 // Both LM35 and TMP036 temperature sensor has temperature slope of 10mV
49 // per degrees celsius
50 // LM35 offset voltage is 0 mv, TMP036 offset voltage is 750 mV
51 // LM35 offset temperature is 0 degree C, TMP036 offset temperature is 25 degrees C
52 temperature = (temperature - TEMP_SENSOR_OFFSET_VOLTAGE)/10.0 +
53 TEMP_SENSOR_OFFSET_TEMPERATURE;
54 // convert to fahrenheit
55 temperature = temperature * 1.8 + 32.0;
56 return temperature;
57 }
58 /*************************************************************************
59 Interrupt handler for button press
60 **************************************************************************/
61 void buttonISR()
62 {
63 // if current edge occurs too close to previous edge, we consider that a bounce
64 if ((millis()-previousEdgeTime) >= MIN_PULSE_SEPARATION) {
65 arm_alarm = !arm_alarm;
66 Serial.print("Alarm is: ");
67 if (arm_alarm == true) {
68 Serial.println("ARMED");
69 }
70 else {
71 Serial.println("NOT ARMED");
72 }
73 count++;
74 Serial.print("Count: "); Serial.println(count);
75 }
76 previousEdgeTime=millis();
77 }
<strong>测试</strong>
测试 IoT 传感器时,我们需要使用两个 MQTT 客户端,一个订阅传感器发布的主题, 一个发布传感器将予以响应的主题。 我们可使用 ‘SSH’ 登录 Edison 开发板,并使用mosquitto_sub/pub 命令本地观察和控制传感器节点, 或者,我们还可使用另外一台也安装了 Mosquitto 软件包的主机。
测试传感器节点:
在订阅客户端上订阅所有主题
$> mosquitto_sub –h ipaddr –p 1883 –t edison/# -v
其中,ipaddr 指 Edison 开发板的 IP 地址。 如果“本地主机 ”基于 Edison 开发板运行,将其替换掉。 我们在此案例中使用开放端口 1883 连 接代理。 我们还可使用带有证书的端口 1994 或带有 PSK 用户名和密码的端口 1995。 成功订阅主题 ‘edison/#’ 后,我们应观察传感器读数以及发布客户端发布 的所有命令。
在发布客户端上发布主题 edison/LED 控制红色 LED,或发布 Edison/motionLED 启 用/禁用绿色 LED。
$> mosquitto_pub –h ipaddr –p 1883 –t Edison/LED –m {H, L, B}
$> mosquitto_pub –h ipaddr –p 1883 –t Edison/motionLED –m {ON, OFF}
发布上述命令时,红色 LED 应相应地开启、关闭或闪烁。
如欲让绿色 LED 停止追踪运动传感器,需发布消息为开启或关闭的主题 ‘edison/motionLED’。
随附的视频显示了测试流程的截屏。
<strong>总结</strong>
我们介绍了如何选择相对复杂的软件(比如 Mosquitto MQTT),以及借助 Arduino sketch 中的 Linux 编程设施将这些软件用于 Arduino sketch。 因此,我们可充分利用 面向 Arduino 生态系统开发的多个现有 Arduino 硬件传感器通用库。 通过使用支持所 有 Arduino sketch 的 Linux,我们还能够以相对简单的方式充分利用可用于 Linux 的 开源软件包。 在后面的文章中,我们将通过连接传感器和 Node-Red,为传感器添加更多 功能,并执行一些基础分析方法处理传感器数据并发送适当的告警。
文章来源:<a href="https://software.intel.com/zh-cn/blogs/2015/04/06/using-edison-securely…