37 Commits
v2.0 ... v2.5

Author SHA1 Message Date
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
27 changed files with 697 additions and 224 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,18 @@
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

View File

@ -8,7 +8,7 @@ a server that supports MQTT.
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: http://knolleary.github.io/pubsubclient/
Full API documentation is available here: http://pubsubclient.knolleary.net
## Limitations
@ -31,8 +31,10 @@ boards and shields, including:
- 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
- Sparkfun WiFly Shield when used with [this library](https://github.com/dpslwk/WiFly)
- 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

View File

@ -0,0 +1,126 @@
/*
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() {
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 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(".");
}
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 acive 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...");
// Attempt to connect
if (client.connect("ESP8266Client")) {
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 loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
snprintf (msg, 75, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("outTopic", msg);
}
}

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.5",
"exclude": "tests",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif"
]
}

View File

@ -1,9 +1,9 @@
name=PubSubClient
version=2.0
version=2.5
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 from a remote server. 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 and ESP8266.
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://knolleary.github.io/pubsubclient/
url=http://pubsubclient.knolleary.net
architectures=*

View File

@ -122,7 +122,7 @@ boolean PubSubClient::connect(const char *id, const char *user, const char *pass
} else {
result = _client->connect(this->ip, this->port);
}
if (result) {
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
@ -177,7 +177,7 @@ boolean PubSubClient::connect(const char *id, const char *user, const char *pass
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity > MQTT_KEEPALIVE*1000UL) {
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
@ -202,16 +202,36 @@ boolean PubSubClient::connect(const char *id, const char *user, const char *pass
}
return false;
}
return true;
}
uint8_t PubSubClient::readByte() {
while(!_client->available()) {}
return _client->read();
// 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;
buffer[len++] = readByte();
if(!readByte(buffer, &len)) return 0;
bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint16_t length = 0;
@ -220,7 +240,7 @@ uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
uint8_t start = 0;
do {
digit = readByte();
if(!readByte(&digit)) return 0;
buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier *= 128;
@ -229,8 +249,8 @@ uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
buffer[len++] = readByte();
buffer[len++] = readByte();
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) {
@ -240,7 +260,7 @@ uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
}
for (uint16_t i = start;i<length;i++) {
digit = readByte();
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && len-*lengthLength-2>skip) {
this->stream->write(digit);
@ -328,12 +348,20 @@ 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 < 5 + 2+strlen(topic) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
length = writeString(topic,buffer,length);
@ -400,7 +428,7 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint8_t rc;
uint16_t rc;
uint16_t len = length;
do {
digit = len % 128;
@ -416,10 +444,25 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
for (int i=0;i<llen;i++) {
buf[5-llen+i] = lenBuf[i];
}
rc = _client->write(buf+(4-llen),length+1+llen);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(4-llen);
uint16_t bytesRemaining = length+1+llen; //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+(4-llen),length+1+llen);
lastOutActivity = millis();
return (rc == 1+llen+length);
#endif
}
boolean PubSubClient::subscribe(const char* topic) {
@ -427,9 +470,13 @@ boolean PubSubClient::subscribe(const char* topic) {
}
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
if (qos < 0 || qos > 1)
return false;
if (qos < 0 || 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 = 5;
@ -447,6 +494,10 @@ boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
}
boolean PubSubClient::unsubscribe(const char* topic) {
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
uint16_t length = 5;
nextMsgId++;
@ -493,8 +544,9 @@ boolean PubSubClient::connected() {
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
_client->stop();
}
}
return rc;
@ -518,7 +570,7 @@ PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
return *this;
}
PubSubClient& PubSubClient::setCallback(void(*callback)(char*,uint8_t*,unsigned int)){
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback;
return *this;
}

View File

@ -17,14 +17,29 @@
// 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
@ -58,7 +73,12 @@
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*,uint8_t*,unsigned int)
#ifdef ESP8266
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, uint32_t)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, uint32_t)
#endif
class PubSubClient {
private:
@ -70,7 +90,8 @@ private:
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint16_t readPacket(uint8_t*);
uint8_t readByte();
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);
IPAddress ip;
@ -107,6 +128,7 @@ public:
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);

View File

@ -4,9 +4,9 @@ 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=../PubSubClient/PubSubClient.cpp
PSC_FILE=../src/PubSubClient.cpp
CC=g++
CFLAGS=-I${SRC_PATH}/lib -I../PubSubClient
CFLAGS=-I${SRC_PATH}/lib -I../src
all: $(TEST_BIN)
@ -16,3 +16,10 @@ ${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES}
clean:
@rm -rf ${OUT_PATH}
test:
@bin/connect_spec
@bin/publish_spec
@bin/receive_spec
@bin/subscribe_spec
@bin/keepalive_spec

View File

@ -19,6 +19,8 @@ int test_connect_fails_no_network() {
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
}
@ -29,6 +31,8 @@ int test_connect_fails_on_no_response() {
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
}
@ -39,17 +43,23 @@ int test_connect_properly_formatted() {
shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
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,28);
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
}
@ -81,6 +91,10 @@ int test_connect_fails_on_bad_rc() {
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
}
@ -89,9 +103,9 @@ int test_connect_accepts_username_password() {
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x26,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,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 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,0x28);
shimClient.expect(connect,0x26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
@ -107,13 +121,13 @@ int test_connect_accepts_username_no_password() {
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x20,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72};
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,0x22);
shimClient.expect(connect,0x20);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",'\0');
int rc = client.connect((char*)"client_test1",(char*)"user",0);
IS_TRUE(rc);
IS_FALSE(shimClient.error());
@ -125,13 +139,13 @@ int test_connect_ignores_password_no_username() {
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
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,28);
shimClient.expect(connect,26);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",'\0',(char*)"pass");
int rc = client.connect((char*)"client_test1",0,(char*)"pass");
IS_TRUE(rc);
IS_FALSE(shimClient.error());
@ -143,9 +157,9 @@ int test_connect_with_will() {
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x32,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,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 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,0x34);
shimClient.expect(connect,0x32);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
@ -161,9 +175,9 @@ int test_connect_with_will_username_password() {
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x42,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,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 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,0x44);
shimClient.expect(connect,0x42);
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
@ -181,17 +195,24 @@ int test_connect_disconnect_connect() {
shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
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,28);
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);
@ -201,28 +222,35 @@ int test_connect_disconnect_connect() {
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_password();
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

@ -3,7 +3,7 @@
#include "Buffer.h"
#include "BDDTest.h"
#include "trace.h"
#include <unistd.h>
byte server[] = { 172, 16, 0, 2 };
@ -13,7 +13,7 @@ void callback(char* topic, byte* payload, unsigned int length) {
int test_keepalive_pings_idle() {
IT("keeps an idle connection alive");
IT("keeps an idle connection alive (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -32,6 +32,10 @@ int test_keepalive_pings_idle() {
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);
}
@ -42,7 +46,7 @@ int test_keepalive_pings_idle() {
}
int test_keepalive_pings_with_outbound_qos0() {
IT("keeps a connection alive that only sends qos0");
IT("keeps a connection alive that only sends qos0 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -78,7 +82,7 @@ int test_keepalive_pings_with_outbound_qos0() {
}
int test_keepalive_pings_with_inbound_qos0() {
IT("keeps a connection alive that only receives qos0");
IT("keeps a connection alive that only receives qos0 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -111,7 +115,7 @@ int test_keepalive_pings_with_inbound_qos0() {
}
int test_keepalive_no_pings_inbound_qos1() {
IT("does not send pings for connections with inbound qos1");
IT("does not send pings for connections with inbound qos1 (takes 1 minute)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -139,7 +143,7 @@ int test_keepalive_no_pings_inbound_qos1() {
}
int test_keepalive_disconnects_hung() {
IT("disconnects a hung connection");
IT("disconnects a hung connection (takes 30 seconds)");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -160,6 +164,9 @@ int test_keepalive_disconnects_hung() {
}
IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
IS_FALSE(shimClient.error());
END_IT
@ -167,6 +174,7 @@ int test_keepalive_disconnects_hung() {
int main()
{
SUITE("Keep-alive");
test_keepalive_pings_idle();
test_keepalive_pings_with_outbound_qos0();
test_keepalive_pings_with_inbound_qos0();

View File

@ -11,23 +11,27 @@ 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("F");
LOG("\n");
std::ostringstream os;
os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
failureList.push_back(os.str());
}
return result;
}
void bddtest_start(const char* description) {
TRACE(" - "<<description << "\n");
LOG(" - "<<description<<" ");
testDescription = description;
testCount ++;
}
void bddtest_end() {
LOG(".");
LOG("\n");
testPasses ++;
}
@ -35,8 +39,10 @@ int bddtest_summary() {
for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) {
LOG("\n");
LOG(*it);
LOG("\n");
}
LOG("\n" << std::dec << testPasses << "/" << testCount << " tests passed\n");
LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n");
if (testPasses == testCount) {
return 0;
}

View File

@ -1,11 +1,13 @@
#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); }

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;
}

View File

@ -1,11 +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
extern "C" {
#define IPAddress uint8_t*
// 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

View File

@ -26,10 +26,10 @@ int ShimClient::connect(IPAddress ip, uint16_t port) {
this->_connected = true;
}
if (this->_expectedPort !=0) {
if (memcmp(ip,this->_expectedIP,4) != 0) {
TRACE( "ip mismatch\n");
this->_error = true;
}
// 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;
@ -151,4 +151,3 @@ void ShimClient::expectConnect(const char *host, uint16_t port) {
this->_expectedHost = host;
this->_expectedPort = port;
}

View File

@ -63,7 +63,7 @@ int test_publish_bytes() {
int test_publish_retained() {
IT("publishes retained");
IT("publishes retained - 1");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -88,6 +88,29 @@ int test_publish_retained() {
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;
@ -102,6 +125,26 @@ int test_publish_not_connected() {
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");
@ -130,12 +173,17 @@ int test_publish_P() {
}
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

View File

@ -105,7 +105,7 @@ int test_receive_max_sized_message() {
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE;
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);
@ -141,7 +141,7 @@ int test_receive_oversized_message() {
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE+1;
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);
@ -176,7 +176,7 @@ int test_receive_oversized_stream_message() {
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE+1;
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];
@ -237,6 +237,7 @@ int test_receive_qos1() {
int main()
{
SUITE("Receive");
test_receive_callback();
test_receive_stream();
test_receive_max_sized_message();

View File

@ -11,8 +11,6 @@ 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;
@ -78,7 +76,7 @@ int test_subscribe_not_connected() {
}
int test_subscribe_invalid_qos() {
IT("subscribe fails when not connected");
IT("subscribe fails with invalid qos values");
ShimClient shimClient;
shimClient.setAllowConnect(true);
@ -99,6 +97,33 @@ int test_subscribe_invalid_qos() {
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;
@ -140,10 +165,12 @@ int test_unsubscribe_not_connected() {
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