107 Commits

Author SHA1 Message Date
3637cbec69 Revert "Added support of CleanSession flag during connect" 2018-11-01 23:35:31 +00:00
b1bdbb7aaf Merge pull request #500 from maxim-kukushkin/master
Added support of CleanSession flag during connect
2018-11-01 23:33:51 +00:00
539838822c Merge pull request #481 from bwibwi13/master
Correct maximum size of snprintf (use same size as the buffer ;)
2018-11-01 23:32:13 +00:00
b5f90b679b Merge pull request #495 from flok99/patch-1
Update PubSubClient.cpp
2018-11-01 23:28:22 +00:00
4ecd32ec08 Fix up CI tests 2018-11-01 23:25:04 +00:00
7517de7974 Merge pull request #494 from mcqn/master
Add large message API
2018-11-01 23:24:33 +00:00
8154cbc09c Fixed an issue with clean session flag set when willTopic is provided 2018-10-22 23:35:47 +01:00
0e8e3123cf Added support of CleanSession flag during connect 2018-10-02 00:02:31 +01:00
9eff4b3308 Merge pull request #499 from nickdex/patch-1
Fix spelling in mqtt_esp8266 example
2018-09-24 10:02:00 +01:00
a1cfd5af56 Fix spelling in mqtt_esp8266 example 2018-09-24 00:29:11 +05:30
0e2d6c322b Update PubSubClient.cpp
Make sure all data is flushed to the other end when doing a disconnect(): that way we know for sure that it is there when we disconnect the wifi or maybe even reboot.
This change was made after I noticed that I did not get any mqtt messages. I verified that it indeed solves the problem. Example code on request.
2018-09-07 21:39:45 +02:00
b2f3a6d2ec Add example code for using the large message API. 2018-09-04 13:36:45 +01:00
0c2d12d8b0 Allow the large message publishing to use all the print methods. 2018-09-04 13:31:43 +01:00
c87c9a47b3 Correct maximum size of snprintf (use same size as the buffer ;) 2018-08-19 08:39:39 +02:00
3b3a8da8d2 Add large-payload API, make max header size a define, not magic number. 2018-08-15 17:15:04 +01:00
54be6e87db Check remaining-length encoding is valid 2018-07-18 11:02:08 +01:00
f029640ee6 Fixing compiler warnings exposed with -Wall and -Wextra: qos can't be less than zero. (#274) 2018-02-22 18:29:44 -08:00
bb101c58e8 Update link http->https (#384) 2018-01-20 00:13:10 +00:00
dddfffbe0c Initialise buffer variables in test framework 2017-06-07 21:31:48 +01:00
bef5814858 Add test for blank (not-null) password 2017-06-07 20:37:45 +01:00
f46d0011ee Merge pull request #270 from pacm93/master
Make Python files compliant with PEP8, except for E501
2017-04-12 08:58:43 +01:00
10925659ef Made Python files compliant with PEP8, except for E501 2017-04-12 09:39:36 +02:00
4c8ce14dad Merge pull request #169 from igrr/master
Use random MQTT client ID in example sketch
2016-08-21 23:22:25 +01:00
df4122466c Merge pull request #173 from edwin-oetelaar/master
Reduce stack usage in loop(). no data duplication on stack
2016-08-21 23:21:03 +01:00
d724864095 Reduce stack usage in loop(). No need to duplicate
topic string onto stack before giving it to callback()
Just move it one byte in buffer to add space for 'C' string end \0x00
2016-06-26 20:53:07 +02:00
33170273a9 Use random MQTT client ID in example sketch
Many users load the sketch having only changed WiFi SSID and password. When multiple users attempt to connect to the same broker using same client ID, they get rejected. Currently the chances of connecting to broker.mqtt-dashboard.com using "ESP8266Client" ID are fairly slim. This change adds a random number to the client ID, increasing chances of connection for new users of this library.
2016-06-14 19:10:15 +08:00
35ead348e3 Fix esp8266 example 2016-02-13 21:23:48 +00:00
341661671b Revert breaking change to callback signature 2016-02-02 07:40:48 +00:00
4739ca0802 Update library files
closes #44
2016-01-31 20:53:44 +00:00
36bb1ffa6a Merge pull request #96 from ElvisTheKing/patch-1
correct handling of dns fauilure
2016-01-31 20:48:43 +00:00
83b69a766e Merge pull request #120 from tomkcook/master
Use std::function on ESP8266 platform.
2016-01-31 20:47:32 +00:00
baeb59e263 Merge pull request #119 from skorokithakis/master
Add definable parameters.
2016-01-31 20:41:19 +00:00
67eba6dad4 Moved #include of <functional> 2016-01-27 12:41:50 +00:00
98a9c296f6 Made use of std::function ESP8266-specific. 2016-01-27 12:39:43 +00:00
68400b7b6c Add definable parameters. 2016-01-26 12:52:38 +02:00
21b75a2c4a Changed callback type. 2016-01-26 09:50:27 +00:00
830f34c7d0 correct handling of dns fauilure
In case of domain name resolution error result can be negative (see Dns.cpp:46)
2015-12-04 04:10:18 +03:00
0bb4efcea5 Update for 2.4 2015-11-21 20:56:32 +00:00
31521085ea Increase rc of write to uint16 to match max possible length
Fixes #85
2015-11-21 20:36:21 +00:00
8a1d7fb620 Merge pull request #93 from vicatcu/master
Implement timeout behavior in readByte / readPacket
2015-11-21 20:20:35 +00:00
803f54b0bd changes to use #define MQTT_SOCKET_TIMEOUT instead of dynamic read timeout interval, per comments from @knolleary on https://github.com/knolleary/pubsubclient/issues/87 2015-11-19 13:58:23 -05:00
6f97ea04f2 minor cleanup 2015-11-19 09:23:40 -05:00
6bb06187b7 added optional timeout setting so that readByte can be escaped 2015-11-18 17:19:41 -05:00
efebd2e5e4 Merge pull request #82 from e-lin/master
Match the length of type for writing data
2015-10-04 19:35:29 +01:00
5cdadf43da Match the length of type for writing data
In MQTT_MAX_TRANSFER_SIZE case, the variable bytesRemaining needs to match the type of data length.
2015-10-04 22:16:27 +09:00
2f97e4a558 Add publish(topic,payload,retained) function 2015-09-11 23:25:21 +01:00
15a0e41c81 Update project url 2015-09-09 20:52:20 +01:00
aa9afc7b44 Fix esp example 2015-09-09 10:32:35 +01:00
461cbdb6e8 Merge pull request #75 from marcelrv/patch-1
Example for ESP8266
2015-09-09 10:23:29 +01:00
47a37a4663 Update library.properties 2015-09-09 10:22:06 +01:00
efcf6dbf1e Update README.md 2015-09-09 10:18:08 +01:00
d5c13d578e Update README.md 2015-09-09 10:17:19 +01:00
b6f2cb29bc Example for ESP8266
I was very happy to see this working well. Now ESP8266 and MQTT becomes even more easy.
Maybe attached example will help others to quickly get started
2015-09-09 09:36:39 +02:00
c1d327cac6 Update tests makefile for new src location 2015-09-07 23:06:38 +01:00
5ace47bc93 Re-layout project to match Arduino Library reqs 2015-09-07 22:56:53 +01:00
da87f50b98 Update library.properties 2015-09-07 21:18:39 +01:00
8c56c2f9fc Update README.md 2015-09-07 21:14:26 +01:00
ce6b128efb Update CHANGES.txt 2015-09-07 21:10:02 +01:00
2b582f6899 Add MAX_TRANSFER_SIZE def to chunk messages if needed 2015-09-07 18:06:17 +01:00
545d0045f9 Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE 2015-09-02 10:44:21 +01:00
fc02df2f6f Fix subscribe test description 2015-08-28 23:13:31 +01:00
a181852893 Update travis to use container infrastructure 2015-08-28 23:07:48 +01:00
c51f6c1673 Reenable all keepalive tests 2015-08-28 22:49:10 +01:00
0d11ce4a7e Fix keepalive test & add state checks to connect test 2015-08-28 22:44:52 +01:00
1c54371b1c Update tests & add travis 2015-08-28 22:16:48 +01:00
9459df9d44 Update changes file 2015-08-28 16:38:46 +01:00
f6521a2ed0 Readme update 2015-08-28 13:44:56 +01:00
fa5b7f75d1 Tidy up more api and examples 2015-08-28 11:21:52 +01:00
8e7e99cb8d Tidy up PROGMEM and const char*s 2015-08-27 23:22:54 +01:00
12a9d89ea2 Add wider IPAddress support in api 2015-08-27 14:18:16 +01:00
c753ecaebe Add MQTT 3.1.1 support 2015-08-26 23:07:22 +01:00
c1a522102d Ensure big packets are properly length encoded
Fixes #63
2015-08-26 22:23:55 +01:00
acc9fe500e ifdef PROGMEM references to support ESP8266/Galileo etc 2015-08-26 22:15:55 +01:00
3852808aac Add single setServer func in place of port/ip/domain 2015-08-26 22:15:19 +01:00
5418f32458 Merge pull request #60 from vicatcu/master
refactored constructors to use getters and setters
2015-08-26 21:22:43 +01:00
fb7884573d a couple functionally equivalent minor update based on pull request diff 2015-04-08 17:21:53 -04:00
8fbb41a429 refactored constructors to use getters and setters 2015-04-08 17:07:56 -04:00
d00a6c3cda Update test README.md 2014-02-08 23:45:27 +00:00
495dc5c373 Update test README.md 2014-02-08 23:41:27 +00:00
3cc0c85f05 Add connect/disconnect/connect cycle test 2014-02-08 23:28:58 +00:00
c37e03e2a8 Add keepalive_spec and stream handling tests 2014-02-08 23:15:13 +00:00
3930b15158 Add qos1 receive test 2014-02-08 17:50:13 +00:00
aab2dcdf1a Add subscribe and receive specs 2014-02-08 00:54:22 +00:00
c6bb3caf57 Fix publish_P and add test coverage 2014-02-07 20:57:12 +00:00
bc80d7cf77 Add publish spec 2014-02-06 22:27:33 +00:00
f945d512d5 Test connect ip/host/port 2014-02-06 21:53:53 +00:00
011171cfc3 Add new unit tests 2014-02-06 21:23:13 +00:00
989ca99594 Stream.h should be included for completeness 2014-02-03 22:22:50 +00:00
6c82bdcf8a readPacket assumed all packets had a topic
Fixes #37
2014-02-02 14:28:57 +00:00
bcead02903 Merge pull request #32 from ennui2342/stream
Stream support for large packets
2014-01-26 06:51:06 -08:00
5e106d710e Merge branch 'stream' of github.com:ennui2342/pubsubclient into stream 2014-01-26 14:31:39 +00:00
1344cdf1b4 Added support for stream the MQTT message to storage via a Stream object 2014-01-26 14:30:33 +00:00
93b9a4e2d1 A *working* example this time! 2014-01-20 22:50:10 +00:00
1113695f4a Example of using Stream storage for messages 2014-01-20 22:16:15 +00:00
edd167b81b Payload for Stream now doesnt include the topic headers and msgid 2014-01-20 22:06:22 +00:00
8abd634be7 Merge remote-tracking branch 'upstream/master' into stream 2014-01-20 20:21:41 +00:00
593c9ae13a Merge pull request #29 from ennui2342/qos1
Added support for QOS1 for messages received from the server
2014-01-06 06:06:36 -08:00
8b458cdad9 Fixed precedence bug 2013-12-04 18:00:17 +00:00
8a29081486 Added support for stream the MQTT message to storage via a Stream object 2013-12-02 12:43:34 +00:00
94df17720e Added check for valid QOS (0 or 1) 2013-11-29 00:18:55 +00:00
359fd03f03 Added support for QOS1 for messages received from the server. Add qos argument to subscribe. Auto ack after callback is run 2013-10-31 21:45:30 +00:00
fb33abc1c0 Merge pull request #28 from ennui2342/master
Fixing bug for large message packets
2013-10-31 08:30:49 -07:00
5060358cce Fixing bug for large message packets 2013-10-31 14:31:54 +00:00
875061615f Callback length field wrong for >128 byte packets 2013-01-11 17:09:39 +00:00
ae44d21503 Merge pull request #19 from binarybucks/master
Add unsubscribe method
2013-01-10 13:14:13 -08:00
5161ec543f Added unsubscribe method 2013-01-10 21:29:15 +01:00
25a2af4238 Fix subscribe message id
Remove pointless &client arg to default constructor
2013-01-10 20:15:26 +00:00
45 changed files with 3443 additions and 719 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tests/bin

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
sudo: false
language: cpp
compiler:
- g++
script: cd tests && make && make test
os:
- linux

View File

@ -1,3 +1,25 @@
2.4
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
whilst waiting for inbound data
* Fixed return code when publishing >256 bytes
2.3
* Add publish(topic,payload,retained) function
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0
* Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266
* Add overloaded constructors for convenience
* Add chainable setters for server/callback/client/stream
* Add state function to return connack return code
1.9 1.9
* Do not split MQTT packets over multiple calls to _client->write() * Do not split MQTT packets over multiple calls to _client->write()
* API change: All constructors now require an instance of Client * API change: All constructors now require an instance of Client
@ -44,4 +66,3 @@
* The maximum message size, including header, is 128 bytes * The maximum message size, including header, is 128 bytes
* The keepalive interval is set to 30 seconds * The keepalive interval is set to 30 seconds
* No support for Will messages * No support for Will messages

View File

@ -1,4 +1,4 @@
Copyright (c) 2008-2012 Nicholas O'Leary Copyright (c) 2008-2015 Nicholas O'Leary
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@ -1,337 +0,0 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nicholas O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include <string.h>
PubSubClient::PubSubClient(Client& client) {
this->_client = &client;
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, void (*callback)(char*,uint8_t*,unsigned int), Client& client) {
this->_client = &client;
this->callback = callback;
this->ip = ip;
this->port = port;
}
PubSubClient::PubSubClient(char* domain, uint16_t port, void (*callback)(char*,uint8_t*,unsigned int), Client& client) {
this->_client = &client;
this->callback = callback;
this->domain = domain;
this->port = port;
}
boolean PubSubClient::connect(char *id) {
return connect(id,NULL,NULL,0,0,0,0);
}
boolean PubSubClient::connect(char *id, char *user, char *pass) {
return connect(id,user,pass,0,0,0,0);
}
boolean PubSubClient::connect(char *id, char* willTopic, uint8_t willQos, uint8_t willRetain, char* willMessage)
{
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage);
}
boolean PubSubClient::connect(char *id, char *user, char *pass, char* willTopic, uint8_t willQos, uint8_t willRetain, char* willMessage) {
if (!connected()) {
int result = 0;
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
if (result) {
nextMsgId = 1;
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p',MQTTPROTOCOLVERSION};
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
unsigned int j;
for (j = 0;j<9;j++) {
buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x06|(willQos<<3)|(willRetain<<5);
} else {
v = 0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
buffer[length++] = v;
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
length = writeString(id,buffer,length);
if (willTopic) {
length = writeString(willTopic,buffer,length);
length = writeString(willMessage,buffer,length);
}
if(user != NULL) {
length = writeString(user,buffer,length);
if(pass != NULL) {
length = writeString(pass,buffer,length);
}
}
write(MQTTCONNECT,buffer,length-5);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity > MQTT_KEEPALIVE*1000UL) {
_client->stop();
return false;
}
}
uint16_t len = readPacket();
if (len == 4 && buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
return true;
}
}
_client->stop();
}
return false;
}
uint8_t PubSubClient::readByte() {
while(!_client->available()) {}
return _client->read();
}
uint16_t PubSubClient::readPacket() {
uint16_t len = 0;
buffer[len++] = readByte();
uint8_t multiplier = 1;
uint16_t length = 0;
uint8_t digit = 0;
do {
digit = readByte();
buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier *= 128;
} while ((digit & 128) != 0);
for (uint16_t i = 0;i<length;i++)
{
if (len < MQTT_MAX_PACKET_SIZE) {
buffer[len++] = readByte();
} else {
readByte();
len = 0; // This will cause the packet to be ignored.
}
}
return len;
}
boolean PubSubClient::loop() {
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
if (pingOutstanding) {
_client->stop();
return false;
} else {
buffer[0] = MQTTPINGREQ;
buffer[1] = 0;
_client->write(buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint16_t len = readPacket();
if (len > 0) {
lastInActivity = t;
uint8_t type = buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (buffer[2]<<8)+buffer[3];
char topic[tl+1];
for (uint16_t i=0;i<tl;i++) {
topic[i] = buffer[4+i];
}
topic[tl] = 0;
// ignore msgID - only support QoS 0 subs
uint8_t *payload = buffer+4+tl;
callback(topic,payload,len-4-tl);
}
} else if (type == MQTTPINGREQ) {
buffer[0] = MQTTPINGRESP;
buffer[1] = 0;
_client->write(buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
}
}
return true;
}
return false;
}
boolean PubSubClient::publish(char* topic, char* payload) {
return publish(topic,(uint8_t*)payload,strlen(payload),false);
}
boolean PubSubClient::publish(char* topic, uint8_t* payload, unsigned int plength) {
return publish(topic, payload, plength, false);
}
boolean PubSubClient::publish(char* topic, uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
length = writeString(topic,buffer,length);
uint16_t i;
for (i=0;i<plength;i++) {
buffer[length++] = payload[i];
}
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,buffer,length-5);
}
return false;
}
boolean PubSubClient::publish_P(char* topic, uint8_t* PROGMEM payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
int rc;
uint16_t tlen;
int pos = 0;
int i;
uint8_t header;
unsigned int len;
if (!connected()) {
return false;
}
tlen = strlen(topic);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,buffer,pos);
rc += _client->write(buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
return rc == len + 1 + plength;
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint8_t rc;
uint8_t len = length;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[5-llen+i] = lenBuf[i];
}
rc = _client->write(buf+(4-llen),length+1+llen);
lastOutActivity = millis();
return (rc == 1+llen+length);
}
boolean PubSubClient::subscribe(char* topic) {
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = 7;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[0] = (nextMsgId >> 8);
buffer[1] = (nextMsgId & 0xFF);
length = writeString(topic, buffer,length);
buffer[length++] = 0; // Only do QoS 0 subs
return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-5);
}
return false;
}
void PubSubClient::disconnect() {
buffer[0] = MQTTDISCONNECT;
buffer[1] = 0;
_client->write(buffer,2);
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(char* string, uint8_t* buf, uint16_t pos) {
char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
boolean PubSubClient::connected() {
int rc = (int)_client->connected();
if (!rc) _client->stop();
return rc;
}

View File

@ -1,75 +0,0 @@
/*
PubSubClient.h - A simple client for MQTT.
Nicholas O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "Client.h"
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#define MQTT_MAX_PACKET_SIZE 128
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#define MQTT_KEEPALIVE 15
#define MQTTPROTOCOLVERSION 3
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
class PubSubClient {
private:
Client* _client;
uint8_t buffer[MQTT_MAX_PACKET_SIZE];
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
void (*callback)(char*,uint8_t*,unsigned int);
uint16_t readPacket();
uint8_t readByte();
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(char* string, uint8_t* buf, uint16_t pos);
uint8_t *ip;
char* domain;
uint16_t port;
public:
PubSubClient(Client& client);
PubSubClient(uint8_t *, uint16_t, void(*)(char*,uint8_t*,unsigned int),Client& client);
PubSubClient(char*, uint16_t, void(*)(char*,uint8_t*,unsigned int),Client& client);
boolean connect(char *);
boolean connect(char *, char *, char *);
boolean connect(char *, char *, uint8_t, uint8_t, char *);
boolean connect(char *, char *, char *, char *, uint8_t, uint8_t, char*);
void disconnect();
boolean publish(char *, char *);
boolean publish(char *, uint8_t *, unsigned int);
boolean publish(char *, uint8_t *, unsigned int, boolean);
boolean publish_P(char *, uint8_t PROGMEM *, unsigned int, boolean);
boolean subscribe(char *);
boolean loop();
boolean connected();
};
#endif

View File

@ -1,38 +0,0 @@
/*
Basic MQTT example
- connects to an MQTT server
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
byte server[] = { 172, 16, 0, 2 };
byte ip[] = { 172, 16, 0, 100 };
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
}
void loop()
{
client.loop();
}

4
README
View File

@ -1,4 +0,0 @@
A client library for the Arduino Ethernet Shield that provides support for MQTT.
Nicholas O'Leary
http://knolleary.net/arduino-client-for-mqtt/

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Arduino Client for MQTT
This library provides a client for doing simple publish/subscribe messaging with
a server that supports MQTT.
## Examples
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: https://pubsubclient.knolleary.net
## Limitations
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
- The maximum message size, including header, is **128 bytes** by default. This
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h`.
- The keepalive interval is set to 15 seconds by default. This is configurable
via `MQTT_KEEPALIVE` in `PubSubClient.h`.
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
changing value of `MQTT_VERSION` in `PubSubClient.h`.
## Compatible Hardware
The library uses the Arduino Ethernet Client api for interacting with the
underlying network hardware. This means it Just Works with a growing number of
boards and shields, including:
- Arduino Ethernet
- Arduino Ethernet Shield
- Arduino YUN use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield [library](https://github.com/dpslwk/WiFly)
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
- Intel Galileo/Edison
- ESP8266
The library cannot currently be used with hardware based on the ENC28J60 chip
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
[alternative library](https://github.com/njh/NanodeMQTT) available.
## License
This code is released under the MIT License.

View File

@ -13,8 +13,8 @@
// Update these with values suitable for your network. // Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
byte server[] = { 172, 16, 0, 2 }; IPAddress ip(172, 16, 0, 100);
byte ip[] = { 172, 16, 0, 100 }; IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) { void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived // handle message arrived
@ -26,6 +26,11 @@ PubSubClient client(server, 1883, callback, ethClient);
void setup() void setup()
{ {
Ethernet.begin(mac, ip); Ethernet.begin(mac, ip);
// Note - the default maximum packet size is 128 bytes. If the
// combined length of clientId, username and password exceed this,
// you will need to increase the value of MQTT_MAX_PACKET_SIZE in
// PubSubClient.h
if (client.connect("arduinoClient", "testuser", "testpass")) { if (client.connect("arduinoClient", "testuser", "testpass")) {
client.publish("outTopic","hello world"); client.publish("outTopic","hello world");
client.subscribe("inTopic"); client.subscribe("inTopic");
@ -36,4 +41,3 @@ void loop()
{ {
client.loop(); client.loop();
} }

View File

@ -0,0 +1,77 @@
/*
Basic MQTT example
This sketch demonstrates the basic capabilities of the library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i=0;i<length;i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
EthernetClient ethClient;
PubSubClient client(ethClient);
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("arduinoClient")) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup()
{
Serial.begin(57600);
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
// Allow the hardware to sort itself out
delay(1500);
}
void loop()
{
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@ -0,0 +1,132 @@
/*
Basic ESP8266 MQTT example
This sketch demonstrates the capabilities of the pubsub library in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic" every two seconds
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
else switch it off
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the LED if an 1 was received as first character
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
// but actually the LED is on; this is because
// it is active low on the ESP-01)
} else {
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
snprintf (msg, 50, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("outTopic", msg);
}
}

View File

@ -0,0 +1,179 @@
/*
Long message ESP8266 MQTT example
This sketch demonstrates sending arbitrarily large messages in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "greenBottles/#", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the sub-topic is a number, it publishes a "greenBottles/lyrics" message
with a payload consisting of the lyrics to "10 green bottles", replacing
10 with the number given in the sub-topic.
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Find out how many bottles we should generate lyrics for
String topicStr(topic);
int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic
if (topicStr.indexOf('/') >= 0) {
// The topic includes a '/', we'll try to read the number of bottles from just after that
topicStr.remove(0, topicStr.indexOf('/')+1);
// Now see if there's a number of bottles after the '/'
bottleCount = topicStr.toInt();
}
if (bottleCount > 0) {
// Work out how big our resulting message will be
int msgLen = 0;
for (int i = bottleCount; i > 0; i--) {
String numBottles(i);
msgLen += 2*numBottles.length();
if (i == 1) {
msgLen += 2*String(" green bottle, standing on the wall\n").length();
} else {
msgLen += 2*String(" green bottles, standing on the wall\n").length();
}
msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length();
switch (i) {
case 1:
msgLen += String("no green bottles, standing on the wall\n\n").length();
break;
case 2:
msgLen += String("1 green bottle, standing on the wall\n\n").length();
break;
default:
numBottles = i-1;
msgLen += numBottles.length();
msgLen += String(" green bottles, standing on the wall\n\n").length();
break;
};
}
// Now we can start to publish the message
client.beginPublish("greenBottles/lyrics", msgLen, false);
for (int i = bottleCount; i > 0; i--) {
for (int j = 0; j < 2; j++) {
client.print(i);
if (i == 1) {
client.print(" green bottle, standing on the wall\n");
} else {
client.print(" green bottles, standing on the wall\n");
}
}
client.print("And if one green bottle should accidentally fall\nThere'll be ");
switch (i) {
case 1:
client.print("no green bottles, standing on the wall\n\n");
break;
case 2:
client.print("1 green bottle, standing on the wall\n\n");
break;
default:
client.print(i-1);
client.print(" green bottles, standing on the wall\n\n");
break;
};
}
// Now we're done!
client.endPublish();
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("greenBottles/#");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@ -20,8 +20,8 @@
// Update these with values suitable for your network. // Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED }; byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
byte server[] = { 172, 16, 0, 2 }; IPAddress ip(172, 16, 0, 100);
byte ip[] = { 172, 16, 0, 100 }; IPAddress server(172, 16, 0, 2);
// Callback function header // Callback function header
void callback(char* topic, byte* payload, unsigned int length); void callback(char* topic, byte* payload, unsigned int length);
@ -58,4 +58,3 @@ void loop()
{ {
client.loop(); client.loop();
} }

View File

@ -0,0 +1,67 @@
/*
Reconnecting MQTT example - non-blocking
This sketch demonstrates how to keep the client connected
using a non-blocking reconnect function. If the client loses
its connection, it attempts to reconnect every 5 seconds
without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your hardware/network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(ethClient);
long lastReconnectAttempt = 0;
boolean reconnect() {
if (client.connect("arduinoClient")) {
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
}
return client.connected();
}
void setup()
{
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
delay(1500);
lastReconnectAttempt = 0;
}
void loop()
{
if (!client.connected()) {
long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
// Attempt to reconnect
if (reconnect()) {
lastReconnectAttempt = 0;
}
}
} else {
// Client connected
client.loop();
}
}

View File

@ -0,0 +1,57 @@
/*
Example of using a Stream object to store the message payload
Uses SRAM library: https://github.com/ennui2342/arduino-sram
but could use any Stream based class such as SD
- connects to an MQTT server
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <SRAM.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
SRAM sram(4, SRAM_1024);
void callback(char* topic, byte* payload, unsigned int length) {
sram.seek(1);
// do something with the message
for(uint8_t i=0; i<length; i++) {
Serial.write(sram.read());
}
Serial.println();
// Reset position for the next message to be stored
sram.seek(1);
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient, sram);
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
sram.begin();
sram.seek(1);
Serial.begin(9600);
}
void loop()
{
client.loop();
}

View File

@ -1,5 +1,5 @@
####################################### #######################################
# Syntax Coloring Map For Ultrasound # Syntax Coloring Map For PubSubClient
####################################### #######################################
####################################### #######################################
@ -15,11 +15,19 @@ PubSubClient KEYWORD1
connect KEYWORD2 connect KEYWORD2
disconnect KEYWORD2 disconnect KEYWORD2
publish KEYWORD2 publish KEYWORD2
publish_P KEYWORD2
beginPublish KEYWORD2
endPublish KEYWORD2
write KEYWORD2
subscribe KEYWORD2 subscribe KEYWORD2
unsubscribe KEYWORD2
loop KEYWORD2 loop KEYWORD2
connected KEYWORD2 connected KEYWORD2
setServer KEYWORD2
setCallback KEYWORD2
setClient KEYWORD2
setStream KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)
####################################### #######################################

17
library.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "PubSubClient",
"keywords": "ethernet, mqtt, m2m, iot",
"description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
"repository": {
"type": "git",
"url": "https://github.com/knolleary/pubsubclient.git"
},
"version": "2.6",
"exclude": "tests",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif"
]
}

9
library.properties Normal file
View File

@ -0,0 +1,9 @@
name=PubSubClient
version=2.6
author=Nick O'Leary <nick.oleary@gmail.com>
maintainer=Nick O'Leary <nick.oleary@gmail.com>
sentence=A client library for MQTT messaging.
paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
category=Communication
url=http://pubsubclient.knolleary.net
architectures=*

635
src/PubSubClient.cpp Executable file
View File

@ -0,0 +1,635 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
PubSubClient::PubSubClient() {
this->_state = MQTT_DISCONNECTED;
this->_client = NULL;
this->stream = NULL;
setCallback(NULL);
}
PubSubClient::PubSubClient(Client& client) {
this->_state = MQTT_DISCONNECTED;
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
boolean PubSubClient::connect(const char *id) {
return connect(id,NULL,NULL,0,0,0,0);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
return connect(id,user,pass,0,0,0,0);
}
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
if (!connected()) {
int result = 0;
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 7
#endif
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x06|(willQos<<3)|(willRetain<<5);
} else {
v = 0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
buffer[length++] = v;
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
length = writeString(id,buffer,length);
if (willTopic) {
length = writeString(willTopic,buffer,length);
length = writeString(willMessage,buffer,length);
}
if(user != NULL) {
length = writeString(user,buffer,length);
if(pass != NULL) {
length = writeString(pass,buffer,length);
}
}
write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint16_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
boolean PubSubClient::readByte(uint8_t * result) {
uint32_t previousMillis = millis();
while(!_client->available()) {
uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
return false;
}
}
*result = _client->read();
return true;
}
// reads a byte into result[*index] and increments index
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]);
if(readByte(write_address)){
*index = current_index + 1;
return true;
}
return false;
}
uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
uint16_t len = 0;
if(!readByte(buffer, &len)) return 0;
bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint16_t length = 0;
uint8_t digit = 0;
uint16_t skip = 0;
uint8_t start = 0;
do {
if (len == 6) {
// Invalid remaining length encoding - kill the connection
_state = MQTT_DISCONNECTED;
_client->stop();
return 0;
}
if(!readByte(&digit)) return 0;
buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier *= 128;
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(buffer, &len)) return 0;
if(!readByte(buffer, &len)) return 0;
skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
start = 2;
if (buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
}
for (uint16_t i = start;i<length;i++) {
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && len-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < MQTT_MAX_PACKET_SIZE) {
buffer[len] = digit;
}
len++;
}
if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
boolean PubSubClient::loop() {
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
} else {
buffer[0] = MQTTPINGREQ;
buffer[1] = 0;
_client->write(buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) buffer+llen+2;
// msgId only present for QOS>0
if ((buffer[0]&0x06) == MQTTQOS1) {
msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
payload = buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
buffer[0] = MQTTPUBACK;
buffer[1] = 2;
buffer[2] = (msgId >> 8);
buffer[3] = (msgId & 0xFF);
_client->write(buffer,4);
lastOutActivity = t;
} else {
payload = buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
buffer[0] = MQTTPINGRESP;
buffer[1] = 0;
_client->write(buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
}
}
return true;
}
return false;
}
boolean PubSubClient::publish(const char* topic, const char* payload) {
return publish(topic,(const uint8_t*)payload,strlen(payload),false);
}
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
return publish(topic, payload, plength, false);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (MQTT_MAX_PACKET_SIZE < MQTT_MAX_HEADER_SIZE + 2+strlen(topic) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,buffer,length);
uint16_t i;
for (i=0;i<plength;i++) {
buffer[length++] = payload[i];
}
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
if (!connected()) {
return false;
}
tlen = strlen(topic);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,buffer,pos);
rc += _client->write(buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
return rc == tlen + 4 + plength;
}
boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
if (connected()) {
// Send the header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,buffer,length);
uint16_t i;
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
lastOutActivity = millis();
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
}
return false;
}
int PubSubClient::endPublish() {
return 1;
}
size_t PubSubClient::write(uint8_t data) {
lastOutActivity = millis();
return _client->write(data);
}
size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
lastOutActivity = millis();
return _client->write(buffer,size);
}
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint16_t len = length;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
}
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
uint16_t bytesRemaining = length+hlen; //Match the length type
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
lastOutActivity = millis();
return (rc == hlen+length);
#endif
}
boolean PubSubClient::subscribe(const char* topic) {
return subscribe(topic, 0);
}
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
if (qos > 1) {
return false;
}
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, buffer,length);
buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::unsubscribe(const char* topic) {
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
void PubSubClient::disconnect() {
buffer[0] = MQTTDISCONNECT;
buffer[1] = 0;
_client->write(buffer,2);
_state = MQTT_DISCONNECTED;
_client->flush();
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
const char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
boolean PubSubClient::connected() {
boolean rc;
if (_client == NULL ) {
rc = false;
} else {
rc = (int)_client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
}
}
return rc;
}
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
return setServer(addr,port);
}
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
this->ip = ip;
this->port = port;
this->domain = NULL;
return *this;
}
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
this->domain = domain;
this->port = port;
return *this;
}
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback;
return *this;
}
PubSubClient& PubSubClient::setClient(Client& client){
this->_client = &client;
return *this;
}
PubSubClient& PubSubClient::setStream(Stream& stream){
this->stream = &stream;
return *this;
}
int PubSubClient::state() {
return this->_state;
}

169
src/PubSubClient.h Executable file
View File

@ -0,0 +1,169 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 128
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#ifdef ESP8266
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
class PubSubClient : public Print {
private:
Client* _client;
uint8_t buffer[MQTT_MAX_PACKET_SIZE];
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint16_t readPacket(uint8_t*);
boolean readByte(uint8_t * result);
boolean readByte(uint8_t * result, uint16_t * index);
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
// Build up the header ready to send
// Returns the size of the header
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(Client& client);
PubSubClient(IPAddress, uint16_t, Client& client);
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, Client& client);
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(const char*, uint16_t, Client& client);
PubSubClient(const char*, uint16_t, Client& client, Stream&);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient& setServer(IPAddress ip, uint16_t port);
PubSubClient& setServer(uint8_t * ip, uint16_t port);
PubSubClient& setServer(const char * domain, uint16_t port);
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
PubSubClient& setClient(Client& client);
PubSubClient& setStream(Stream& stream);
boolean connect(const char* id);
boolean connect(const char* id, const char* user, const char* pass);
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
void disconnect();
boolean publish(const char* topic, const char* payload);
boolean publish(const char* topic, const char* payload, boolean retained);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
// Start to publish a message.
// This API:
// beginPublish(...)
// one or more calls to write(...)
// endPublish()
// Allows for arbitrarily large payloads to be sent without them having to be copied into
// a new buffer and held in memory at one time
// Returns 1 if the message was started successfully, 0 if there was an error
boolean beginPublish(const char* topic, unsigned int plength, boolean retained);
// Finish off this publish message (started with beginPublish)
// Returns 1 if the packet was sent successfully, 0 if there was an error
int endPublish();
// Write a single byte of payload (only to be used with beginPublish/endPublish)
virtual size_t write(uint8_t);
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
// Returns the number of bytes written
virtual size_t write(const uint8_t *buffer, size_t size);
boolean subscribe(const char* topic);
boolean subscribe(const char* topic, uint8_t qos);
boolean unsubscribe(const char* topic);
boolean loop();
boolean connected();
int state();
};
#endif

25
tests/Makefile Normal file
View File

@ -0,0 +1,25 @@
SRC_PATH=./src
OUT_PATH=./bin
TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp)
TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%)
VPATH=${SRC_PATH}
SHIM_FILES=${SRC_PATH}/lib/*.cpp
PSC_FILE=../src/PubSubClient.cpp
CC=g++
CFLAGS=-I${SRC_PATH}/lib -I../src
all: $(TEST_BIN)
${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES}
mkdir -p ${OUT_PATH}
${CC} ${CFLAGS} $^ -o $@
clean:
@rm -rf ${OUT_PATH}
test:
@bin/connect_spec
@bin/publish_spec
@bin/receive_spec
@bin/subscribe_spec
@bin/keepalive_spec

View File

@ -1,22 +1,55 @@
## Arduino Client for MQTT Test Suite # Arduino Client for MQTT Test Suite
This is a regression test suite for the `PubSubClient` library. This is a regression test suite for the `PubSubClient` library.
There are two parts:
- Tests that can be compiled and run on any machine
- Tests that build the example sketches using the Arduino IDE
It is a work-in-progress and is subject to complete refactoring as the whim takes It is a work-in-progress and is subject to complete refactoring as the whim takes
me. me.
## Local tests
These are a set of executables that can be run to test specific areas of functionality.
They do not require a real Arduino to be attached, nor the use of the Arduino IDE.
The tests include a set of mock files to stub out the parts of the Arduino environment the library
depends on.
### Dependencies
- g++
### Running
Build the tests using the provided `Makefile`:
$ make
This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality.
*Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through.
## Arduino tests
*Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite.
Without a suitable arduino plugged in, the test suite will only check the Without a suitable arduino plugged in, the test suite will only check the
example sketches compile cleanly against the library. example sketches compile cleanly against the library.
With an arduino plugged in, each sketch that has a corresponding python With an arduino plugged in, each sketch that has a corresponding python
test case is built, uploaded and then the tests run. test case is built, uploaded and then the tests run.
## Dependencies ### Dependencies
- Python 2.7+ - Python 2.7+
- [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches - [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches
## Running ### Running
The test suite _does not_ run an MQTT server - it is assumed to be running already. The test suite _does not_ run an MQTT server - it is assumed to be running already.
@ -25,7 +58,7 @@ The test suite _does not_ run an MQTT server - it is assumed to be running alrea
A summary of activity is printed to the console. More comprehensive logs are written A summary of activity is printed to the console. More comprehensive logs are written
to the `logs` directory. to the `logs` directory.
## What it does ### What it does
For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case
`testcases/mqtt_basic.py`. `testcases/mqtt_basic.py`.
@ -40,7 +73,7 @@ allows any test setup to be performed before the sketch runs - such as connectin
a client and subscribing to topics. a client and subscribing to topics.
## Settings ### Settings
The file `testcases/settings.py` is used to config the test environment. The file `testcases/settings.py` is used to config the test environment.

273
tests/src/connect_spec.cpp Normal file
View File

@ -0,0 +1,273 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
byte server[] = { 172, 16, 0, 2 };
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
int test_connect_fails_no_network() {
IT("fails to connect if underlying client doesn't connect");
ShimClient shimClient;
shimClient.setAllowConnect(false);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECT_FAILED);
END_IT
}
int test_connect_fails_on_no_response() {
IT("fails to connect if no response received after 15 seconds");
ShimClient shimClient;
shimClient.setAllowConnect(true);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
END_IT
}
int test_connect_properly_formatted() {
IT("sends a properly formatted connect packet and succeeds");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
END_IT
}
int test_connect_properly_formatted_hostname() {
IT("accepts a hostname");
ShimClient shimClient;
shimClient.setAllowConnect(true);
shimClient.expectConnect((char* const)"localhost",1883);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client((char* const)"localhost", 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_fails_on_bad_rc() {
IT("fails to connect if a bad return code is received");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x01 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == 0x01);
END_IT
}
int test_connect_accepts_username_password() {
IT("accepts a username and password");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_accepts_username_no_password() {
IT("accepts a username but no password");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x20);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",0);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_accepts_username_blank_password() {
IT("accepts a username and blank password");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x20,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x0};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_ignores_password_no_username() {
IT("ignores a password but no username");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",0,(char*)"pass");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_with_will() {
IT("accepts a will");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x32);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"willTopic",1,0,(char*)"willMessage");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_with_will_username_password() {
IT("accepts a will, username and password");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x42);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"password",(char*)"willTopic",1,0,(char*)"willMessage");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_connect_disconnect_connect() {
IT("connects, disconnects and connects again");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
byte disconnect[] = {0xE0,0x00};
shimClient.expect(disconnect,2);
client.disconnect();
IS_FALSE(client.connected());
IS_FALSE(shimClient.connected());
IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
shimClient.expect(connect,28);
shimClient.respond(connack,4);
rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
END_IT
}
int main()
{
SUITE("Connect");
test_connect_fails_no_network();
test_connect_fails_on_no_response();
test_connect_properly_formatted();
test_connect_accepts_username_password();
test_connect_fails_on_bad_rc();
test_connect_properly_formatted_hostname();
test_connect_accepts_username_no_password();
test_connect_ignores_password_no_username();
test_connect_with_will();
test_connect_with_will_username_password();
test_connect_disconnect_connect();
FINISH
}

View File

@ -0,0 +1,185 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
#include <unistd.h>
byte server[] = { 172, 16, 0, 2 };
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
int test_keepalive_pings_idle() {
IT("keeps an idle connection alive (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte pingreq[] = { 0xC0,0x0 };
shimClient.expect(pingreq,2);
byte pingresp[] = { 0xD0,0x0 };
shimClient.respond(pingresp,2);
for (int i = 0; i < 50; i++) {
sleep(1);
if ( i == 15 || i == 31 || i == 47) {
shimClient.expect(pingreq,2);
shimClient.respond(pingresp,2);
}
rc = client.loop();
IS_TRUE(rc);
}
IS_FALSE(shimClient.error());
END_IT
}
int test_keepalive_pings_with_outbound_qos0() {
IT("keeps a connection alive that only sends qos0 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
for (int i = 0; i < 50; i++) {
TRACE(i<<":");
shimClient.expect(publish,16);
rc = client.publish((char*)"topic",(char*)"payload");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
sleep(1);
if ( i == 15 || i == 31 || i == 47) {
byte pingreq[] = { 0xC0,0x0 };
shimClient.expect(pingreq,2);
byte pingresp[] = { 0xD0,0x0 };
shimClient.respond(pingresp,2);
}
rc = client.loop();
IS_TRUE(rc);
IS_FALSE(shimClient.error());
}
END_IT
}
int test_keepalive_pings_with_inbound_qos0() {
IT("keeps a connection alive that only receives qos0 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
for (int i = 0; i < 50; i++) {
TRACE(i<<":");
sleep(1);
if ( i == 15 || i == 31 || i == 47) {
byte pingreq[] = { 0xC0,0x0 };
shimClient.expect(pingreq,2);
byte pingresp[] = { 0xD0,0x0 };
shimClient.respond(pingresp,2);
}
shimClient.respond(publish,16);
rc = client.loop();
IS_TRUE(rc);
IS_FALSE(shimClient.error());
}
END_IT
}
int test_keepalive_no_pings_inbound_qos1() {
IT("does not send pings for connections with inbound qos1 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte puback[] = {0x40,0x2,0x12,0x34};
for (int i = 0; i < 50; i++) {
shimClient.respond(publish,18);
shimClient.expect(puback,4);
sleep(1);
rc = client.loop();
IS_TRUE(rc);
IS_FALSE(shimClient.error());
}
END_IT
}
int test_keepalive_disconnects_hung() {
IT("disconnects a hung connection (takes 30 seconds)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte pingreq[] = { 0xC0,0x0 };
shimClient.expect(pingreq,2);
for (int i = 0; i < 32; i++) {
sleep(1);
rc = client.loop();
}
IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
IS_FALSE(shimClient.error());
END_IT
}
int main()
{
SUITE("Keep-alive");
test_keepalive_pings_idle();
test_keepalive_pings_with_outbound_qos0();
test_keepalive_pings_with_inbound_qos0();
test_keepalive_no_pings_inbound_qos1();
test_keepalive_disconnects_hung();
FINISH
}

24
tests/src/lib/Arduino.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef Arduino_h
#define Arduino_h
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "Print.h"
extern "C"{
typedef uint8_t byte ;
typedef uint8_t boolean ;
/* sketch */
extern void setup( void ) ;
extern void loop( void ) ;
uint32_t millis( void );
}
#define PROGMEM
#define pgm_read_byte_near(x) *(x)
#endif // Arduino_h

50
tests/src/lib/BDDTest.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "BDDTest.h"
#include "trace.h"
#include <sstream>
#include <iostream>
#include <string>
#include <list>
int testCount = 0;
int testPasses = 0;
const char* testDescription;
std::list<std::string> failureList;
void bddtest_suite(const char* name) {
LOG(name << "\n");
}
int bddtest_test(const char* file, int line, const char* assertion, int result) {
if (!result) {
LOG("\n");
std::ostringstream os;
os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
failureList.push_back(os.str());
}
return result;
}
void bddtest_start(const char* description) {
LOG(" - "<<description<<" ");
testDescription = description;
testCount ++;
}
void bddtest_end() {
LOG("\n");
testPasses ++;
}
int bddtest_summary() {
for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) {
LOG("\n");
LOG(*it);
LOG("\n");
}
LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n");
if (testPasses == testCount) {
return 0;
}
return 1;
}

23
tests/src/lib/BDDTest.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef bddtest_h
#define bddtest_h
void bddtest_suite(const char* name);
int bddtest_test(const char*, int, const char*, int);
void bddtest_start(const char*);
void bddtest_end();
int bddtest_summary();
#define SUITE(x) { bddtest_suite(x); }
#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; }
#define IT(x) { bddtest_start(x); }
#define END_IT { bddtest_end();return true;}
#define FINISH { return bddtest_summary(); }
#define IS_TRUE(x) TEST(x)
#define IS_FALSE(x) TEST(!(x))
#define IS_EQUAL(x,y) TEST(x==y)
#define IS_NOT_EQUAL(x,y) TEST(x!=y)
#endif

34
tests/src/lib/Buffer.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "Buffer.h"
#include "Arduino.h"
Buffer::Buffer() {
this->pos = 0;
this->length = 0;
}
Buffer::Buffer(uint8_t* buf, size_t size) {
this->pos = 0;
this->length = 0;
this->add(buf,size);
}
bool Buffer::available() {
return this->pos < this->length;
}
uint8_t Buffer::next() {
if (this->available()) {
return this->buffer[this->pos++];
}
return 0;
}
void Buffer::reset() {
this->pos = 0;
}
void Buffer::add(uint8_t* buf, size_t size) {
uint16_t i = 0;
for (;i<size;i++) {
this->buffer[this->length++] = buf[i];
}
}

23
tests/src/lib/Buffer.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef buffer_h
#define buffer_h
#include "Arduino.h"
class Buffer {
private:
uint8_t buffer[1024];
uint16_t pos;
uint16_t length;
public:
Buffer();
Buffer(uint8_t* buf, size_t size);
virtual bool available();
virtual uint8_t next();
virtual void reset();
virtual void add(uint8_t* buf, size_t size);
};
#endif

21
tests/src/lib/Client.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef client_h
#define client_h
#include "IPAddress.h"
class Client {
public:
virtual int connect(IPAddress ip, uint16_t port) =0;
virtual int connect(const char *host, uint16_t port) =0;
virtual size_t write(uint8_t) =0;
virtual size_t write(const uint8_t *buf, size_t size) =0;
virtual int available() = 0;
virtual int read() = 0;
virtual int read(uint8_t *buf, size_t size) = 0;
virtual int peek() = 0;
virtual void flush() = 0;
virtual void stop() = 0;
virtual uint8_t connected() = 0;
virtual operator bool() = 0;
};
#endif

View File

@ -0,0 +1,44 @@
#include <Arduino.h>
#include <IPAddress.h>
IPAddress::IPAddress()
{
memset(_address, 0, sizeof(_address));
}
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
{
_address[0] = first_octet;
_address[1] = second_octet;
_address[2] = third_octet;
_address[3] = fourth_octet;
}
IPAddress::IPAddress(uint32_t address)
{
memcpy(_address, &address, sizeof(_address));
}
IPAddress::IPAddress(const uint8_t *address)
{
memcpy(_address, address, sizeof(_address));
}
IPAddress& IPAddress::operator=(const uint8_t *address)
{
memcpy(_address, address, sizeof(_address));
return *this;
}
IPAddress& IPAddress::operator=(uint32_t address)
{
memcpy(_address, (const uint8_t *)&address, sizeof(_address));
return *this;
}
bool IPAddress::operator==(const uint8_t* addr)
{
return memcmp(addr, _address, sizeof(_address)) == 0;
}

72
tests/src/lib/IPAddress.h Normal file
View File

@ -0,0 +1,72 @@
/*
*
* MIT License:
* Copyright (c) 2011 Adrian McEwen
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* adrianm@mcqn.com 1/1/2011
*/
#ifndef IPAddress_h
#define IPAddress_h
// A class to make it easier to handle and pass around IP addresses
class IPAddress {
private:
uint8_t _address[4]; // IPv4 address
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t* raw_address() { return _address; };
public:
// Constructors
IPAddress();
IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
IPAddress(uint32_t address);
IPAddress(const uint8_t *address);
// Overloaded cast operator to allow IPAddress objects to be used where a pointer
// to a four-byte uint8_t array is expected
operator uint32_t() { return *((uint32_t*)_address); };
bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); };
bool operator==(const uint8_t* addr);
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const { return _address[index]; };
uint8_t& operator[](int index) { return _address[index]; };
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
IPAddress& operator=(const uint8_t *address);
IPAddress& operator=(uint32_t address);
friend class EthernetClass;
friend class UDP;
friend class Client;
friend class Server;
friend class DhcpClass;
friend class DNSClient;
};
#endif

28
tests/src/lib/Print.h Normal file
View File

@ -0,0 +1,28 @@
/*
Print.h - Base class that provides print() and println()
Copyright (c) 2008 David A. Mellis. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef Print_h
#define Print_h
class Print {
public:
virtual size_t write(uint8_t) = 0;
};
#endif

View File

@ -0,0 +1,153 @@
#include "ShimClient.h"
#include "trace.h"
#include <iostream>
#include <Arduino.h>
#include <ctime>
extern "C" {
uint32_t millis(void) {
return time(0)*1000;
}
}
ShimClient::ShimClient() {
this->responseBuffer = new Buffer();
this->expectBuffer = new Buffer();
this->_allowConnect = true;
this->_connected = false;
this->_error = false;
this->expectAnything = true;
this->_received = 0;
this->_expectedPort = 0;
}
int ShimClient::connect(IPAddress ip, uint16_t port) {
if (this->_allowConnect) {
this->_connected = true;
}
if (this->_expectedPort !=0) {
// if (memcmp(ip,this->_expectedIP,4) != 0) {
// TRACE( "ip mismatch\n");
// this->_error = true;
// }
if (port != this->_expectedPort) {
TRACE( "port mismatch\n");
this->_error = true;
}
}
return this->_connected;
}
int ShimClient::connect(const char *host, uint16_t port) {
if (this->_allowConnect) {
this->_connected = true;
}
if (this->_expectedPort !=0) {
if (strcmp(host,this->_expectedHost) != 0) {
TRACE( "host mismatch\n");
this->_error = true;
}
if (port != this->_expectedPort) {
TRACE( "port mismatch\n");
this->_error = true;
}
}
return this->_connected;
}
size_t ShimClient::write(uint8_t b) {
this->_received += 1;
TRACE(std::hex << (unsigned int)b);
if (!this->expectAnything) {
if (this->expectBuffer->available()) {
uint8_t expected = this->expectBuffer->next();
if (expected != b) {
this->_error = true;
TRACE("!=" << (unsigned int)expected);
}
} else {
this->_error = true;
}
}
TRACE("\n"<< std::dec);
return 1;
}
size_t ShimClient::write(const uint8_t *buf, size_t size) {
this->_received += size;
TRACE( "[" << std::dec << (unsigned int)(size) << "] ");
uint16_t i=0;
for (;i<size;i++) {
if (i>0) {
TRACE(":");
}
TRACE(std::hex << (unsigned int)(buf[i]));
if (!this->expectAnything) {
if (this->expectBuffer->available()) {
uint8_t expected = this->expectBuffer->next();
if (expected != buf[i]) {
this->_error = true;
TRACE("!=" << (unsigned int)expected);
}
} else {
this->_error = true;
}
}
}
TRACE("\n"<<std::dec);
return size;
}
int ShimClient::available() {
return this->responseBuffer->available();
}
int ShimClient::read() { return this->responseBuffer->next(); }
int ShimClient::read(uint8_t *buf, size_t size) {
uint16_t i = 0;
for (;i<size;i++) {
buf[i] = this->read();
}
return size;
}
int ShimClient::peek() { return 0; }
void ShimClient::flush() {}
void ShimClient::stop() {
this->setConnected(false);
}
uint8_t ShimClient::connected() { return this->_connected; }
ShimClient::operator bool() { return true; }
ShimClient* ShimClient::respond(uint8_t *buf, size_t size) {
this->responseBuffer->add(buf,size);
return this;
}
ShimClient* ShimClient::expect(uint8_t *buf, size_t size) {
this->expectAnything = false;
this->expectBuffer->add(buf,size);
return this;
}
void ShimClient::setConnected(bool b) {
this->_connected = b;
}
void ShimClient::setAllowConnect(bool b) {
this->_allowConnect = b;
}
bool ShimClient::error() {
return this->_error;
}
uint16_t ShimClient::received() {
return this->_received;
}
void ShimClient::expectConnect(IPAddress ip, uint16_t port) {
this->_expectedIP = ip;
this->_expectedPort = port;
}
void ShimClient::expectConnect(const char *host, uint16_t port) {
this->_expectedHost = host;
this->_expectedPort = port;
}

View File

@ -0,0 +1,51 @@
#ifndef shimclient_h
#define shimclient_h
#include "Arduino.h"
#include "Client.h"
#include "IPAddress.h"
#include "Buffer.h"
class ShimClient : public Client {
private:
Buffer* responseBuffer;
Buffer* expectBuffer;
bool _allowConnect;
bool _connected;
bool expectAnything;
bool _error;
uint16_t _received;
IPAddress _expectedIP;
uint16_t _expectedPort;
const char* _expectedHost;
public:
ShimClient();
virtual int connect(IPAddress ip, uint16_t port);
virtual int connect(const char *host, uint16_t port);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buf, size_t size);
virtual int available();
virtual int read();
virtual int read(uint8_t *buf, size_t size);
virtual int peek();
virtual void flush();
virtual void stop();
virtual uint8_t connected();
virtual operator bool();
virtual ShimClient* respond(uint8_t *buf, size_t size);
virtual ShimClient* expect(uint8_t *buf, size_t size);
virtual void expectConnect(IPAddress ip, uint16_t port);
virtual void expectConnect(const char *host, uint16_t port);
virtual uint16_t received();
virtual bool error();
virtual void setAllowConnect(bool b);
virtual void setConnected(bool b);
};
#endif

39
tests/src/lib/Stream.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "Stream.h"
#include "trace.h"
#include <iostream>
#include <Arduino.h>
Stream::Stream() {
this->expectBuffer = new Buffer();
this->_error = false;
this->_written = 0;
}
size_t Stream::write(uint8_t b) {
this->_written++;
TRACE(std::hex << (unsigned int)b);
if (this->expectBuffer->available()) {
uint8_t expected = this->expectBuffer->next();
if (expected != b) {
this->_error = true;
TRACE("!=" << (unsigned int)expected);
}
} else {
this->_error = true;
}
TRACE("\n"<< std::dec);
return 1;
}
bool Stream::error() {
return this->_error;
}
void Stream::expect(uint8_t *buf, size_t size) {
this->expectBuffer->add(buf,size);
}
uint16_t Stream::length() {
return this->_written;
}

22
tests/src/lib/Stream.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef Stream_h
#define Stream_h
#include "Arduino.h"
#include "Buffer.h"
class Stream {
private:
Buffer* expectBuffer;
bool _error;
uint16_t _written;
public:
Stream();
virtual size_t write(uint8_t);
virtual bool error();
virtual void expect(uint8_t *buf, size_t size);
virtual uint16_t length();
};
#endif

10
tests/src/lib/trace.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef trace_h
#define trace_h
#include <iostream>
#include <stdlib.h>
#define LOG(x) {std::cout << x << std::flush; }
#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }}
#endif

190
tests/src/publish_spec.cpp Normal file
View File

@ -0,0 +1,190 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
byte server[] = { 172, 16, 0, 2 };
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
int test_publish() {
IT("publishes a null-terminated string");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
shimClient.expect(publish,16);
rc = client.publish((char*)"topic",(char*)"payload");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_bytes() {
IT("publishes a byte array");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
int length = 5;
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
shimClient.expect(publish,14);
rc = client.publish((char*)"topic",payload,length);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_retained() {
IT("publishes retained - 1");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
int length = 5;
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
shimClient.expect(publish,14);
rc = client.publish((char*)"topic",payload,length,true);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_retained_2() {
IT("publishes retained - 2");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'};
shimClient.expect(publish,14);
rc = client.publish((char*)"topic",(char*)"ABCDE",true);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_not_connected() {
IT("publish fails when not connected");
ShimClient shimClient;
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.publish((char*)"topic",(char*)"payload");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_too_long() {
IT("publish fails when topic/payload are too long");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_P() {
IT("publishes using PROGMEM");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
int length = 5;
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
shimClient.expect(publish,14);
rc = client.publish_P((char*)"topic",payload,length,true);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int main()
{
SUITE("Publish");
test_publish();
test_publish_bytes();
test_publish_retained();
test_publish_retained_2();
test_publish_not_connected();
test_publish_too_long();
test_publish_P();
FINISH
}

279
tests/src/receive_spec.cpp Normal file
View File

@ -0,0 +1,279 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
byte server[] = { 172, 16, 0, 2 };
bool callback_called = false;
char lastTopic[1024];
char lastPayload[1024];
unsigned int lastLength;
void reset_callback() {
callback_called = false;
lastTopic[0] = '\0';
lastPayload[0] = '\0';
lastLength = 0;
}
void callback(char* topic, byte* payload, unsigned int length) {
callback_called = true;
strcpy(lastTopic,topic);
memcpy(lastPayload,payload,length);
lastLength = length;
}
int test_receive_callback() {
IT("receives a callback message");
reset_callback();
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
shimClient.respond(publish,16);
rc = client.loop();
IS_TRUE(rc);
IS_TRUE(callback_called);
IS_TRUE(strcmp(lastTopic,"topic")==0);
IS_TRUE(memcmp(lastPayload,"payload",7)==0);
IS_TRUE(lastLength == 7);
IS_FALSE(shimClient.error());
END_IT
}
int test_receive_stream() {
IT("receives a streamed callback message");
reset_callback();
Stream stream;
stream.expect((uint8_t*)"payload",7);
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient, stream);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
shimClient.respond(publish,16);
rc = client.loop();
IS_TRUE(rc);
IS_TRUE(callback_called);
IS_TRUE(strcmp(lastTopic,"topic")==0);
IS_TRUE(lastLength == 7);
IS_FALSE(stream.error());
IS_FALSE(shimClient.error());
END_IT
}
int test_receive_max_sized_message() {
IT("receives an max-sized message");
reset_callback();
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
int length = MQTT_MAX_PACKET_SIZE;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length];
memset(bigPublish,'A',length);
bigPublish[length] = 'B';
memcpy(bigPublish,publish,16);
shimClient.respond(bigPublish,length);
rc = client.loop();
IS_TRUE(rc);
IS_TRUE(callback_called);
IS_TRUE(strcmp(lastTopic,"topic")==0);
IS_TRUE(lastLength == length-9);
IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0);
IS_FALSE(shimClient.error());
END_IT
}
int test_receive_oversized_message() {
IT("drops an oversized message");
reset_callback();
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
int length = MQTT_MAX_PACKET_SIZE+1;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length];
memset(bigPublish,'A',length);
bigPublish[length] = 'B';
memcpy(bigPublish,publish,16);
shimClient.respond(bigPublish,length);
rc = client.loop();
IS_TRUE(rc);
IS_FALSE(callback_called);
IS_FALSE(shimClient.error());
END_IT
}
int test_drop_invalid_remaining_length_message() {
IT("drops invalid remaining length message");
reset_callback();
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x30,0x92,0x92,0x92,0x92,0x92,0x92,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
shimClient.respond(publish,21);
rc = client.loop();
IS_FALSE(rc);
IS_FALSE(callback_called);
IS_FALSE(shimClient.error());
END_IT
}
int test_receive_oversized_stream_message() {
IT("drops an oversized message");
reset_callback();
Stream stream;
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient, stream);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
int length = MQTT_MAX_PACKET_SIZE+1;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length];
memset(bigPublish,'A',length);
bigPublish[length] = 'B';
memcpy(bigPublish,publish,16);
shimClient.respond(bigPublish,length);
stream.expect(bigPublish+9,length-9);
rc = client.loop();
IS_TRUE(rc);
IS_TRUE(callback_called);
IS_TRUE(strcmp(lastTopic,"topic")==0);
IS_TRUE(lastLength == length-9);
IS_FALSE(stream.error());
IS_FALSE(shimClient.error());
END_IT
}
int test_receive_qos1() {
IT("receives a qos1 message");
reset_callback();
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
shimClient.respond(publish,18);
byte puback[] = {0x40,0x2,0x12,0x34};
shimClient.expect(puback,4);
rc = client.loop();
IS_TRUE(rc);
IS_TRUE(callback_called);
IS_TRUE(strcmp(lastTopic,"topic")==0);
IS_TRUE(memcmp(lastPayload,"payload",7)==0);
IS_TRUE(lastLength == 7);
IS_FALSE(shimClient.error());
END_IT
}
int main()
{
SUITE("Receive");
test_receive_callback();
test_receive_stream();
test_receive_max_sized_message();
test_drop_invalid_remaining_length_message();
test_receive_oversized_message();
test_receive_oversized_stream_message();
test_receive_qos1();
FINISH
}

View File

@ -0,0 +1,177 @@
#include "PubSubClient.h"
#include "ShimClient.h"
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
byte server[] = { 172, 16, 0, 2 };
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
int test_subscribe_no_qos() {
IT("subscribe without qos defaults to 0");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 };
shimClient.expect(subscribe,12);
byte suback[] = { 0x90,0x3,0x0,0x2,0x0 };
shimClient.respond(suback,5);
rc = client.subscribe((char*)"topic");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_subscribe_qos_1() {
IT("subscribes qos 1");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 };
shimClient.expect(subscribe,12);
byte suback[] = { 0x90,0x3,0x0,0x2,0x1 };
shimClient.respond(suback,5);
rc = client.subscribe((char*)"topic",1);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_subscribe_not_connected() {
IT("subscribe fails when not connected");
ShimClient shimClient;
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.subscribe((char*)"topic");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_subscribe_invalid_qos() {
IT("subscribe fails with invalid qos values");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
rc = client.subscribe((char*)"topic",2);
IS_FALSE(rc);
rc = client.subscribe((char*)"topic",254);
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_subscribe_too_long() {
IT("subscribe fails with too long topic");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
// max length should be allowed
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
IS_TRUE(rc);
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_unsubscribe() {
IT("unsubscribes");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 };
shimClient.expect(unsubscribe,12);
byte unsuback[] = { 0xB0,0x2,0x0,0x2 };
shimClient.respond(unsuback,4);
rc = client.unsubscribe((char*)"topic");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_unsubscribe_not_connected() {
IT("unsubscribe fails when not connected");
ShimClient shimClient;
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.unsubscribe((char*)"topic");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int main()
{
SUITE("Subscribe");
test_subscribe_no_qos();
test_subscribe_qos_1();
test_subscribe_not_connected();
test_subscribe_invalid_qos();
test_subscribe_too_long();
test_unsubscribe();
test_unsubscribe_not_connected();
FINISH
}

View File

@ -1,43 +1,39 @@
import unittest import unittest
import settings import settings
import time import time
import mosquitto import mosquitto
import serial
def on_message(mosq, obj, msg): def on_message(mosq, obj, msg):
obj.message_queue.append(msg) obj.message_queue.append(msg)
class mqtt_basic(unittest.TestCase): class mqtt_basic(unittest.TestCase):
message_queue = [] message_queue = []
@classmethod @classmethod
def setUpClass(self): def setUpClass(self):
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self) self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self)
self.client.connect(settings.server_ip) self.client.connect(settings.server_ip)
self.client.on_message = on_message self.client.on_message = on_message
self.client.subscribe("outTopic",0) self.client.subscribe("outTopic", 0)
@classmethod @classmethod
def tearDownClass(self): def tearDownClass(self):
self.client.disconnect() self.client.disconnect()
def test_one(self): def test_one(self):
i=30 i = 30
while len(self.message_queue) == 0 and i > 0: while len(self.message_queue) == 0 and i > 0:
self.client.loop() self.client.loop()
time.sleep(0.5) time.sleep(0.5)
i -= 1 i -= 1
self.assertTrue(i>0, "message receive timed-out") self.assertTrue(i > 0, "message receive timed-out")
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
msg = self.message_queue[0] msg = self.message_queue[0]
self.assertEqual(msg.mid,0,"message id not 0") self.assertEqual(msg.mid, 0, "message id not 0")
self.assertEqual(msg.topic,"outTopic","message topic incorrect") self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
self.assertEqual(msg.payload,"hello world") self.assertEqual(msg.payload, "hello world")
self.assertEqual(msg.qos,0,"message qos not 0") self.assertEqual(msg.qos, 0, "message qos not 0")
self.assertEqual(msg.retain,False,"message retain flag incorrect") self.assertEqual(msg.retain, False, "message retain flag incorrect")

View File

@ -1,64 +1,59 @@
import unittest import unittest
import settings import settings
import time import time
import mosquitto import mosquitto
import serial
def on_message(mosq, obj, msg): def on_message(mosq, obj, msg):
obj.message_queue.append(msg) obj.message_queue.append(msg)
class mqtt_publish_in_callback(unittest.TestCase): class mqtt_publish_in_callback(unittest.TestCase):
message_queue = [] message_queue = []
@classmethod @classmethod
def setUpClass(self): def setUpClass(self):
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self) self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self)
self.client.connect(settings.server_ip) self.client.connect(settings.server_ip)
self.client.on_message = on_message self.client.on_message = on_message
self.client.subscribe("outTopic",0) self.client.subscribe("outTopic", 0)
@classmethod @classmethod
def tearDownClass(self): def tearDownClass(self):
self.client.disconnect() self.client.disconnect()
def test_connect(self): def test_connect(self):
i=30 i = 30
while len(self.message_queue) == 0 and i > 0: while len(self.message_queue) == 0 and i > 0:
self.client.loop() self.client.loop()
time.sleep(0.5) time.sleep(0.5)
i -= 1 i -= 1
self.assertTrue(i>0, "message receive timed-out") self.assertTrue(i > 0, "message receive timed-out")
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
msg = self.message_queue.pop(0) msg = self.message_queue.pop(0)
self.assertEqual(msg.mid,0,"message id not 0") self.assertEqual(msg.mid, 0, "message id not 0")
self.assertEqual(msg.topic,"outTopic","message topic incorrect") self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
self.assertEqual(msg.payload,"hello world") self.assertEqual(msg.payload, "hello world")
self.assertEqual(msg.qos,0,"message qos not 0") self.assertEqual(msg.qos, 0, "message qos not 0")
self.assertEqual(msg.retain,False,"message retain flag incorrect") self.assertEqual(msg.retain, False, "message retain flag incorrect")
def test_publish(self): def test_publish(self):
self.assertEqual(len(self.message_queue), 0, "message queue not empty") self.assertEqual(len(self.message_queue), 0, "message queue not empty")
payload = "abcdefghij" payload = "abcdefghij"
self.client.publish("inTopic",payload) self.client.publish("inTopic", payload)
i=30 i = 30
while len(self.message_queue) == 0 and i > 0: while len(self.message_queue) == 0 and i > 0:
self.client.loop() self.client.loop()
time.sleep(0.5) time.sleep(0.5)
i -= 1 i -= 1
self.assertTrue(i>0, "message receive timed-out") self.assertTrue(i > 0, "message receive timed-out")
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received") self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
msg = self.message_queue.pop(0) msg = self.message_queue.pop(0)
self.assertEqual(msg.mid,0,"message id not 0") self.assertEqual(msg.mid, 0, "message id not 0")
self.assertEqual(msg.topic,"outTopic","message topic incorrect") self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
self.assertEqual(msg.payload,payload) self.assertEqual(msg.payload, payload)
self.assertEqual(msg.qos,0,"message qos not 0") self.assertEqual(msg.qos, 0, "message qos not 0")
self.assertEqual(msg.retain,False,"message retain flag incorrect") self.assertEqual(msg.retain, False, "message retain flag incorrect")

View File

@ -10,21 +10,22 @@ import re
from testcases import settings from testcases import settings
class Workspace(object): class Workspace(object):
def __init__(self): def __init__(self):
self.root_dir = os.getcwd() self.root_dir = os.getcwd()
self.build_dir = os.path.join(self.root_dir,"tmpbin"); self.build_dir = os.path.join(self.root_dir, "tmpbin")
self.log_dir = os.path.join(self.root_dir,"logs"); self.log_dir = os.path.join(self.root_dir, "logs")
self.tests_dir = os.path.join(self.root_dir,"testcases"); self.tests_dir = os.path.join(self.root_dir, "testcases")
self.examples_dir = os.path.join(self.root_dir,"../PubSubClient/examples") self.examples_dir = os.path.join(self.root_dir, "../PubSubClient/examples")
self.examples = [] self.examples = []
self.tests = [] self.tests = []
if not os.path.isdir("../PubSubClient"): if not os.path.isdir("../PubSubClient"):
raise Exception("Cannot find PubSubClient library") raise Exception("Cannot find PubSubClient library")
try: try:
import ino return __import__('ino')
except: except ImportError:
raise Exception("ino tool not installed") raise Exception("ino tool not installed")
def init(self): def init(self):
@ -36,50 +37,51 @@ class Workspace(object):
os.mkdir(self.log_dir) os.mkdir(self.log_dir)
os.chdir(self.build_dir) os.chdir(self.build_dir)
call(["ino","init"]) call(["ino", "init"])
shutil.copytree("../../PubSubClient","lib/PubSubClient") shutil.copytree("../../PubSubClient", "lib/PubSubClient")
filenames = [] filenames = []
for root, dirs, files in os.walk(self.examples_dir): for root, dirs, files in os.walk(self.examples_dir):
filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")] filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
filenames.sort() filenames.sort()
for e in filenames: for e in filenames:
self.examples.append(Sketch(self,e)) self.examples.append(Sketch(self, e))
filenames = [] filenames = []
for root, dirs, files in os.walk(self.tests_dir): for root, dirs, files in os.walk(self.tests_dir):
filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")] filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
filenames.sort() filenames.sort()
for e in filenames: for e in filenames:
self.tests.append(Sketch(self,e)) self.tests.append(Sketch(self, e))
def clean(self): def clean(self):
shutil.rmtree(self.build_dir) shutil.rmtree(self.build_dir)
class Sketch(object): class Sketch(object):
def __init__(self,wksp,fn): def __init__(self, wksp, fn):
self.w = wksp self.w = wksp
self.filename = fn self.filename = fn
self.basename = os.path.basename(self.filename) self.basename = os.path.basename(self.filename)
self.build_log = os.path.join(self.w.log_dir,"%s.log"%(os.path.basename(self.filename),)) self.build_log = os.path.join(self.w.log_dir, "%s.log" % (os.path.basename(self.filename),))
self.build_err_log = os.path.join(self.w.log_dir,"%s.err.log"%(os.path.basename(self.filename),)) self.build_err_log = os.path.join(self.w.log_dir, "%s.err.log" % (os.path.basename(self.filename),))
self.build_upload_log = os.path.join(self.w.log_dir,"%s.upload.log"%(os.path.basename(self.filename),)) self.build_upload_log = os.path.join(self.w.log_dir, "%s.upload.log" % (os.path.basename(self.filename),))
def build(self): def build(self):
sys.stdout.write(" Build: ") sys.stdout.write(" Build: ")
sys.stdout.flush() sys.stdout.flush()
# Copy sketch over, replacing IP addresses as necessary # Copy sketch over, replacing IP addresses as necessary
fin = open(self.filename,"r") fin = open(self.filename, "r")
lines = fin.readlines() lines = fin.readlines()
fin.close() fin.close()
fout = open(os.path.join(self.w.build_dir,"src","sketch.ino"),"w") fout = open(os.path.join(self.w.build_dir, "src", "sketch.ino"), "w")
for l in lines: for l in lines:
if re.match(r"^byte server\[\] = {",l): if re.match(r"^byte server\[\] = {", l):
fout.write("byte server[] = { %s };\n"%(settings.server_ip.replace(".",", "),)) fout.write("byte server[] = { %s };\n" % (settings.server_ip.replace(".", ", "),))
elif re.match(r"^byte ip\[\] = {",l): elif re.match(r"^byte ip\[\] = {", l):
fout.write("byte ip[] = { %s };\n"%(settings.arduino_ip.replace(".",", "),)) fout.write("byte ip[] = { %s };\n" % (settings.arduino_ip.replace(".", ", "),))
else: else:
fout.write(l) fout.write(l)
fout.flush() fout.flush()
@ -88,7 +90,7 @@ class Sketch(object):
# Run build # Run build
fout = open(self.build_log, "w") fout = open(self.build_log, "w")
ferr = open(self.build_err_log, "w") ferr = open(self.build_err_log, "w")
rc = call(["ino","build"],stdout=fout,stderr=ferr) rc = call(["ino", "build"], stdout=fout, stderr=ferr)
fout.close() fout.close()
ferr.close() ferr.close()
if rc == 0: if rc == 0:
@ -100,14 +102,14 @@ class Sketch(object):
sys.stdout.write("\n") sys.stdout.write("\n")
with open(self.build_err_log) as f: with open(self.build_err_log) as f:
for line in f: for line in f:
print " ",line, print(" " + line)
return False return False
def upload(self): def upload(self):
sys.stdout.write(" Upload: ") sys.stdout.write(" Upload: ")
sys.stdout.flush() sys.stdout.flush()
fout = open(self.build_upload_log, "w") fout = open(self.build_upload_log, "w")
rc = call(["ino","upload"],stdout=fout,stderr=fout) rc = call(["ino", "upload"], stdout=fout, stderr=fout)
fout.close() fout.close()
if rc == 0: if rc == 0:
sys.stdout.write("pass") sys.stdout.write("pass")
@ -118,20 +120,19 @@ class Sketch(object):
sys.stdout.write("\n") sys.stdout.write("\n")
with open(self.build_upload_log) as f: with open(self.build_upload_log) as f:
for line in f: for line in f:
print " ",line, print(" " + line)
return False return False
def test(self): def test(self):
# import the matching test case, if it exists # import the matching test case, if it exists
try: try:
basename = os.path.basename(self.filename)[:-4] basename = os.path.basename(self.filename)[:-4]
i = importlib.import_module("testcases."+basename) i = importlib.import_module("testcases." + basename)
except: except:
sys.stdout.write(" Test: no tests found") sys.stdout.write(" Test: no tests found")
sys.stdout.write("\n") sys.stdout.write("\n")
return return
c = getattr(i,basename) c = getattr(i, basename)
testmethods = [m for m in dir(c) if m.startswith("test_")] testmethods = [m for m in dir(c) if m.startswith("test_")]
testmethods.sort() testmethods.sort()
@ -146,19 +147,20 @@ class Sketch(object):
sys.stdout.flush() sys.stdout.flush()
for t in tests: for t in tests:
t.run(result) t.run(result)
print "%d/%d"%(result.testsRun-len(result.failures)-len(result.errors),result.testsRun) print(str(result.testsRun - len(result.failures) - len(result.errors)) + "/" + str(result.testsRun))
if not result.wasSuccessful(): if not result.wasSuccessful():
if len(result.failures) > 0: if len(result.failures) > 0:
for f in result.failures: for f in result.failures:
print "-- %s"%(str(f[0]),) print("-- " + str(f[0]))
print f[1] print(f[1])
if len(result.errors) > 0: if len(result.errors) > 0:
print " Errors:" print(" Errors:")
for f in result.errors: for f in result.errors:
print "-- %s"%(str(f[0]),) print("-- " + str(f[0]))
print f[1] print(f[1])
c.tearDownClass() c.tearDownClass()
if __name__ == '__main__': if __name__ == '__main__':
run_tests = True run_tests = True
@ -166,13 +168,13 @@ if __name__ == '__main__':
w.init() w.init()
for e in w.examples: for e in w.examples:
print "--------------------------------------" print("--------------------------------------")
print "[%s]"%(e.basename,) print("[" + e.basename + "]")
if e.build() and run_tests: if e.build() and run_tests:
e.test() e.test()
for e in w.tests: for e in w.tests:
print "--------------------------------------" print("--------------------------------------")
print "[%s]"%(e.basename,) print("[" + e.basename + "]")
if e.build() and run_tests: if e.build() and run_tests:
e.test() e.test()