SECURELY CONNECT IOT SENSOR TO THE INTERNET WITH MQTT 1
来源:互联网 发布:淘宝默认发票抬头 编辑:程序博客网 时间:2024/05/19 07:07
https://software.intel.com/en-us/blogs/2015/04/06/using-edison-securely-connect-iot-sensor-to-the-internet-with-mqtt
Introduction
In previous blog posts [1], [2], we’ve built the Mosquitto MQTT broker on Edison and created a sensor node for sensing motion, temperature, and light level. In this article, we will connect those sensors and actuators to the Mosquitto MQTT server we’ve built to turn those sensors into true IoT sensors.
Using MQTT
Basic MQTT Publish/Subscribe
MQTT is a publish/subscribe protocol built on top of TCP/IP protocol. MQTT is one of the popular protocols being used for M2M (Machine to Machine) communications. The two main components of MQTT are the MQTT clients and the MQTT broker. The MQTT clients publish messages to a particular topic or, subscribe and listen, to a particular topic. The MQTT broker receives all published messages from MQTT publishers and forward the relevant messages to all MQTT subscribers. Subscribers and publishers do not have to be aware of each other, only the topics and messages are relevant. To properly communicate, publishers and subscribers have to agree to use a common topic name and message format.
To publish or subscribe to an MQTT topic, a MQTT client program is needed. In the Mosquitto MQTT distribution, the publishing client is called ‘mosquitto_pub’ and the subscribing client is called‘mosquitto_sub’.
The screenshot below shows the sequence of activities we’ll be describing next. The red window shows outputs of the ‘mosquitto_sub’ commands and the black window shows the ‘mosquitto_pub’ commands. Commands were labeled so that we can refer to them conveniently.
The minimal arguments required for the MQTT clients are: the broker IP address, the topic name and for the publishing clients, the message. Assuming that we already have an MQTT broker running on 127.0.0.1 (localhost), the command below will listen to all messages published to a topic named ‘edison/hello’
1
sub 3> mosquitto_sub –h localhost –t edison/hello
To publish to the topic ‘edison/hello’:
1
pub 3> mosquitto_pub –h localhost –t edison/hello –m “Message
#1: Hello World!”
When the MQTT broker receives the publication to topic ‘edison/hello’ with the message ‘Message#1: Hello World’ from the publishing client (pub3), it scans for subscribers that was listening to the topic ‘edison/hello’. Having found that the subscribing client, (sub3) was listening to the same topic, the MQTT broker forward the message to (sub3). When subscribing client (sub3) receives the message, it echo the message content to the terminal window. In the (sub 3) and (pub 3) commands, the default MQTT port number was implicitly assumed to be 1883 and was not specificed on the commandline. In (sub 4) and (pub 4) commands, the port number is explicitly provided. In (sub 5) and (pub 5) commands, the option ‘-v’ was added to the subscription argument to enable printing of the topic name as well as the message. This will be useful when subscribing to multiple topics using wildcards. The subscription client is persistent, so that additional messages published to the topic ‘edison/hello’ in command (pub 6) will be seen by the subscription client.
The default MQTT broker behavior is to discard the message as soon as it was delivered to subscribing clients. When a new topic ‘edison/hello1’ was published by client (pub 7), subscribing client (sub 5) was listening to the topic ‘edison/hello’, hence was not informed. The subscribing client then subscribes to the new topic (sub 6), the previous message published to the topic ‘edison/hello1’ was already discarded. It will receive subsequent publish to ‘edison/hello1’ topic as shown in command (pub 8). There are additional options to tell the MQTT broker to retain the last message published to a topic as well as other QOS (Quality of Service) directives on how messages should be delivered by the broker. More details on these capabilities can be found at the MQTT Wiki site.
Depending on the configuration of the ports, additional arguments may be needed. We will discuss these later in this document.
Subscribing to multiple MQTT topics
Usually, one would have an MQTT client subscribing to multiple topics from a publisher and perform certain action based on the message received. It is worth a few words discussing how MQTT topic are organized.
MQTT topics are organized in a directory structure with the ‘/’ character used to indicate sub-topics. The listing below are examples of various topics:
1
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 subscribers can subscribe to one single topic, such as ‘Temperature/Outdoor/West’ or to multiple topics using wildcards. There are two wildcard characters in MQTT ‘+’ and ‘#’. The ‘+’ wildcard is used to subscribe to topics at the same hierarchical level. The ‘#’ wildcard is used to subscribe to all sub-topics below the specified topics. For example, subscribing to the topic ‘Temperature/#’ will return result when any of the topics in above list have a new message. Subscribing to ‘Temperature/+’ will return only ‘Temperature/Average’ since all other entries are sub-topic. More details on MQTT data organization can be found on the MQTT wiki site.
Configuring a secured Mosquitto broker
There are basically 4 methods to connect a MQTT client to the Mosquitto server.
- Open port or no security. This is a convenient method to develop and test MQTT devices before deployment. Data transmission between broker and client are transmitted in plain text and can be snooped on by anyone with the right tool.
- User and password protection. This is used basically to authenticate a client to the broker. It doesn’t encrypt the data transmission.
- TLS-PSK encryption. Clients and broker possess a shared secret key that they used to negotiate a secure connection. This is available in Mosquitto version 1.3.5 or later.
- SSL encryption. This is the most advanced encryption and authentication scheme available in the Mosquitto MQTT distribution. With SSL encryption, one needs to obtain a server certificate from a trusted Certificate Authority (CA) such as Verisign or Equifax. Often, for testing, one can create a self-signed certificate that can then be used to sign the server certificate. This encryption method, while more secured, is cumbersome to implement as it requires all client devices to be provisioned with the correct certificates and mechanism for in-the-field upgrade must be developed to keep the certificate versions between clients and broker in sync.
To configure a secure Mosquitto MQTT broker, use the configuration directives shown below. The certificate we used in this case is a self-signed certificate for testing purpose and should not be used in production servers.
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
The file, pwdfile.txt, is a password file, encrypted in similar manner as the Linux password file. It is generated by using the mosquitto_passwd utility that comes with the Mosquitto software. The file pskfile.txt, is the pre-shared password file to be used in a TLS-PSK connection. The format of this file is a text file of one user:password pair per line. The passwords used for TLS-PSK must use only hexadecimal characters and are case insensitive. Sample of the password_file and psk_file is shown below.
1
/* sample Mosquitto password_file */
2
user:$6$Su4WZkkQ0LmqeD/X$Q57AYfcu27McE14/MojWgto7fmloRyrZ7BpTtKOkME8UZzJZ5hGXpOea81RlgcXttJbeRFY9s0f+UTY2dO5xdg==
3
/* sample Mosquitto PSK
file
*/
4
user1:deadbeef
5
user2:DEADBEEF0123456789ABCDEF0123456789abcdef0123456789abcdef0123456789abcdef
Incorporating MQTT into an IoT Sensor
Since the Edison board is really a Linux board that ran an Arduino sketch as one of its processes, we will leverage the existing Mosquitto MQTT package we previously built. Within the Arduino sketch, we will simply use Linux programming facilities to call the ‘mosquitto_sub’ and ‘mosquitto_pub’ programs. There are several advantages to this approach:
- We don’t have to rewrite the MQTT clients for Arduino. The Mosquitto MQTT package is a well- accepted and well-tested piece of software. We only need to create a wrapper class to enable usage within an Arduino sketch.
- The Mosquitto MQTT clients are capable of secured connections using SSL. The small microcontroller in a generic Arduino, on the other hand, simply cannot handle the computational demands of SSL.
- When a different or better MQTT software package is found or a newer version of the existing MQTT software is released, we can re-use most of the existing code and only add new code to leverage new capability of the new package.
The MQTTClient Class
Since the Intel Edison board is a full-fledged Linux operating system, all of the Linux programming facilities are available to our Arduino sketches. We will create a wrapper class called MQTTClient that will be used in the sketch. Within the MQTTClient methods, we will use Linux system calls to invoke the mosquitto_suband mosquitto_pub program that will perform the actual MQTT data transfer for us. Below is the class definition for a minimalist 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
To use the class, one starts with creating an MQTTClient object, then use MQTTClient::begin() to initialize various variables that MQTTClient::subscribe() and MQTTClient::publish() will use to connect with the MQTT broker. These variables are:
- mqtt_broker: name of the MQTT broker. This can be an IP address or a DNS name. For this article, we’ll use ‘localhost’ as the broker is running on the same Edison board as the Arduino sketch. If the Edison board was configured to use static IP address, this address can be used instead.
- serverPort: the port to be used. We configured 3 different ports on the Mosquitto broker with different security policy:
- Port 1883 is the MQTT default port that is open to all clients.
- Port 1994 is configured as a secured SSL port. User must supply an SSL certificate from the CA that issued the server certificate.
- Port 1995 is configured as a secured TLS-PSK port. Users access this port with a user identity and a pre-shared password.
- mode: security mode to use. Currently, security mode can be one of OPEN, SSL or PSK. Depending on the selected security mode, the certificate, user identity and pre-shared password are supplied with the rest of the method’s arguments. Arguments that are not applicable to the selected security mode are ignored.
To subscribe to a topic, one calls MQTTClient::subscribe() with a topic name and a callback function to be invoked when a new message is available. To publish to a topic, one calls MQTTClient::publish() and supply a topic name and message to be publish.
For both subscribe and publish method, the appropriate command string to invoke either mosquitto_sub ormosquitto_pub is formed using the method’s arguments and stored parameters. A Linux pipe is then opened and the command string is executed with results piped back to the Arduino sketch. When publishing, the pipe is immediately closed after the message was sent. When subscribing, the pipe need to be kept open so that new data can be received. Within the Arduino sketch, one needs to periodically check the pipe to see if there is any new data. The MQTTClient::loop() method was designed to check for data in the pipe and invoke the callback function to process new messages. Because the Linux pipe will block if the pipe is empty when checked, we’ve made the pipe non-blocking so that the MQTTClient::loop() method will return if the pipe is empty. Pseudo-code below shows typical usage of the MQTTClient class.
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
}
The variable “SAMPLING_PERIOD” determines the frequency the code will check for new messages and should be chosen such that a new message that will cause some action from the sketch will not arrive too late for meaningful actions. Most of the time, MQTTClient::loop() method will return quickly and there is minimal overhead in sampling the loop frequently. The publication interval is determined by the variable“PUBLISH_INTERVAL” and should be chosen at the appropriate data rate for the given sensor e.g. a temperature sensor may publish its value once a minute if it is merely for informational purpose while a smoke sensor may want to update its result every few seconds so that a subscriber listening to the smoke sensor messages will have a chance to act before it is too late.
An implementation of the MQTTClient class is shown below.
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
;
086
}
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
}
- SECURELY CONNECT IOT SENSOR TO THE INTERNET WITH MQTT 1
- SECURELY CONNECT IOT SENSOR TO THE INTERNET WITH MQTT 2
- Building a battery powered WiFi IoT Sensor with ESP8266, MS-5611 (GY-63), nodemcu and MQTT
- How to use passwords securely with wget
- How to Build an High Availability MQTT Cluster for the Internet of Things
- How to Build an High Availability MQTT Cluster for the Internet of Things
- OLE with the internet explorer
- killer1525 Wireless Used CLI Connect to Internet
- Do you want to view only the webpage content that was delivered securely?
- Say Hi To The Internet
- 5.2 CONNECTING TO THE INTERNET
- 树莓派IoT 学习4 mosquitto实现mqtt通讯(1)
- Tomcat 6.x:please verify the preference field with the prompt: cannot connect to VM
- connect to windows with rdp
- Mqtt Java源码分析1 Connect
- IoT协议: SharkMQTT 与MQTT
- How to Connect Oracle 9i with SQL Server 2000 for establishing the linked server
- How to Connect Oracle 9i with SQL Server 2000 for establishing the linked server
- 【一天一道LeetCode】索引目录 ---C++实现
- 中间件涉及的标准
- JAVA学习 (1) main函数
- 【LeetCode】338. Counting Bits
- 河南省第六届ACM River Crossing
- SECURELY CONNECT IOT SENSOR TO THE INTERNET WITH MQTT 1
- PYthon读串口
- Myeclipse优化中心
- 关于Android蓝牙4.0Ble设备开发技术点
- Apache POI介绍
- Java多线程用法解析
- linux 不能启动disk启动失败.
- iOS设置启动页停留时间
- C# 对数据库操作的函数总结