├── NanodeMQTT.cpp ├── NanodeMQTT.h ├── README.md ├── examples ├── mqtt_publish │ └── mqtt_publish.ino └── mqtt_subscribe │ └── mqtt_subscribe.ino ├── internal-states.gv └── internal-states.png /NanodeMQTT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2012, Nicholas Humfrey. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote 14 | * products derived from this software without specific prior 15 | * written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 18 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | */ 30 | 31 | #include 32 | 33 | 34 | #ifdef MQTT_DEBUG 35 | static int serial_putc( char c, FILE * ) 36 | { 37 | Serial.write( c ); 38 | return c; 39 | } 40 | #endif 41 | 42 | NanodeMQTT::NanodeMQTT(NanodeUIP *uip) 43 | { 44 | this->uip = uip; 45 | memset(this->client_id, 0, MQTT_MAX_CLIENT_ID_LEN+1); 46 | this->port = MQTT_DEFAULT_PORT; 47 | this->keep_alive = MQTT_DEFAULT_KEEP_ALIVE; 48 | this->message_id = 0; 49 | this->state = MQTT_STATE_DISCONNECTED; 50 | this->ping_pending = 0; 51 | this->blocking_mode = 1; 52 | this->error_code = 0; 53 | 54 | this->payload_length = 0; 55 | this->payload_retain = 0; 56 | this->subscribe_topic = NULL; 57 | this->callback = NULL; 58 | 59 | #ifdef MQTT_DEBUG 60 | fdevopen( &serial_putc, 0 ); 61 | #endif 62 | } 63 | 64 | static void mqtt_appcall(void) 65 | { 66 | NanodeMQTT *mqtt = ((struct mqtt_app_state *)&(uip_conn->appstate))->mqtt; 67 | 68 | if (uip_aborted()) { 69 | MQTT_DEBUG_PRINTLN("TCP: Connection Aborted"); 70 | mqtt->tcp_closed(); 71 | } 72 | 73 | if (uip_timedout()) { 74 | MQTT_DEBUG_PRINTLN("TCP: Connection Timed Out"); 75 | mqtt->tcp_closed(); 76 | } 77 | 78 | if (uip_closed()) { 79 | MQTT_DEBUG_PRINTLN("TCP: Connection Closed"); 80 | mqtt->tcp_closed(); 81 | } 82 | 83 | if (uip_connected()) { 84 | MQTT_DEBUG_PRINTLN("TCP: Connection Established"); 85 | mqtt->tcp_connected(); 86 | } 87 | 88 | if (uip_acked()) { 89 | MQTT_DEBUG_PRINTLN("TCP: Acked"); 90 | mqtt->tcp_acked(); 91 | } 92 | 93 | if (uip_newdata()) { 94 | MQTT_DEBUG_PRINTLN("TCP: New Data"); 95 | mqtt->tcp_receive(); 96 | } 97 | 98 | if (uip_rexmit()) { 99 | MQTT_DEBUG_PRINTLN("TCP: Retransmit"); 100 | mqtt->tcp_transmit(); 101 | } 102 | 103 | if (uip_poll()) { 104 | mqtt->poll(); 105 | } 106 | 107 | mqtt->check_timeout(); 108 | } 109 | 110 | 111 | void NanodeMQTT::set_client_id(const char* client_id) 112 | { 113 | strncpy(this->client_id, client_id, MQTT_MAX_CLIENT_ID_LEN); 114 | } 115 | 116 | void NanodeMQTT::set_server_addr(byte a, byte b, byte c, byte d) 117 | { 118 | uip_ipaddr(&this->addr, a,b,c,d); 119 | } 120 | 121 | void NanodeMQTT::set_server_port(uint16_t port) 122 | { 123 | this->port = port; 124 | } 125 | 126 | void NanodeMQTT::set_keep_alive(uint16_t secs) 127 | { 128 | this->keep_alive = secs; 129 | } 130 | 131 | void NanodeMQTT::set_blocking_mode(uint8_t blocking) 132 | { 133 | this->blocking_mode = blocking; 134 | } 135 | 136 | void NanodeMQTT::set_callback(mqtt_callback_t callback) 137 | { 138 | this->callback = callback; 139 | } 140 | 141 | 142 | // FIXME: add support for WILL 143 | void NanodeMQTT::connect() 144 | { 145 | struct uip_conn *conn; 146 | 147 | // Set the client ID to the MAC address, if none is set 148 | if (this->client_id[0] == '\0') { 149 | uip->get_mac_str(this->client_id); 150 | } 151 | 152 | conn = uip_connect(&this->addr, UIP_HTONS(this->port), mqtt_appcall); 153 | if (conn) { 154 | struct mqtt_app_state *s = (struct mqtt_app_state *)&(conn->appstate); 155 | s->mqtt = this; 156 | this->state = MQTT_STATE_WAITING; 157 | 158 | // Set the keep-alive timers 159 | timer_set(&this->transmit_timer, CLOCK_SECOND * this->keep_alive); 160 | timer_set(&this->receive_timer, CLOCK_SECOND * this->keep_alive); 161 | 162 | // If in blocking mode - loop until we are connected 163 | if (this->blocking_mode) { 164 | while (this->state == MQTT_STATE_WAITING || 165 | this->state == MQTT_STATE_CONNECTING || 166 | this->state == MQTT_STATE_CONNECT_SENT) 167 | { 168 | uip->poll(); 169 | } 170 | } 171 | } 172 | } 173 | 174 | uint8_t NanodeMQTT::connected() 175 | { 176 | switch(this->state) { 177 | case MQTT_STATE_CONNECTED: 178 | case MQTT_STATE_PUBLISHING: 179 | case MQTT_STATE_SUBSCRIBING: 180 | case MQTT_STATE_SUBSCRIBE_SENT: 181 | case MQTT_STATE_PINGING: 182 | return 1; 183 | default: 184 | return 0; 185 | } 186 | } 187 | 188 | uint8_t NanodeMQTT::get_state() 189 | { 190 | return this->state; 191 | } 192 | 193 | int8_t NanodeMQTT::get_error_code() 194 | { 195 | return this->error_code; 196 | } 197 | 198 | void NanodeMQTT::disconnect() 199 | { 200 | MQTT_DEBUG_PRINTLN("disconnect()"); 201 | if (this->connected()) { 202 | this->state = MQTT_STATE_DISCONNECTING; 203 | this->tcp_transmit(); 204 | } 205 | } 206 | 207 | void NanodeMQTT::publish(const char* topic, const char* payload) 208 | { 209 | publish(topic, (uint8_t*)payload, strlen(payload)); 210 | } 211 | 212 | void NanodeMQTT::publish(const char* topic, const uint8_t* payload, uint8_t plength) 213 | { 214 | publish(topic, payload, plength, 0); 215 | } 216 | 217 | void NanodeMQTT::publish(const char* topic, const uint8_t* payload, uint8_t plength, uint8_t retained) 218 | { 219 | // FIXME: check that payload isn't bigger than UIP_APPDATASIZE (or 127 bytes) 220 | // FIXME: can we avoid this extra buffer? 221 | strcpy(this->payload_topic, topic); 222 | memcpy(this->payload, payload, plength); 223 | this->payload_retain = retained; 224 | this->payload_length = plength; 225 | 226 | // If in blocking mode - loop until message has been published 227 | if (this->blocking_mode) { 228 | while (this->connected() && this->payload_length != 0) { 229 | uip->poll(); 230 | } 231 | } 232 | } 233 | 234 | 235 | // ** End of the public API ** 236 | 237 | 238 | void NanodeMQTT::subscribe(const char* topic) 239 | { 240 | this->subscribe_topic = topic; 241 | 242 | // If in blocking mode - loop until we have subscribed 243 | if (this->blocking_mode) { 244 | while (this->connected() && this->subscribe_topic != NULL) { 245 | uip->poll(); 246 | } 247 | } 248 | } 249 | 250 | void NanodeMQTT::init_packet(uint8_t header) 251 | { 252 | buf = (uint8_t *)uip_appdata; 253 | pos = 0; 254 | buf[pos++] = header; 255 | buf[pos++] = 0x00; // Packet length 256 | } 257 | 258 | void NanodeMQTT::append_byte(uint8_t b) 259 | { 260 | buf[pos++] = b; 261 | } 262 | 263 | void NanodeMQTT::append_word(uint16_t s) 264 | { 265 | buf[pos++] = highByte(s); 266 | buf[pos++] = lowByte(s); 267 | } 268 | 269 | void NanodeMQTT::append_string(const char* str) 270 | { 271 | // FIXME: support strings longer than 255 bytes 272 | const char* ptr = str; 273 | uint8_t len = 0; 274 | pos += 2; 275 | while (*ptr) { 276 | buf[pos++] = *ptr++; 277 | len++; 278 | } 279 | buf[pos-len-2] = 0; 280 | buf[pos-len-1] = len; 281 | } 282 | 283 | void NanodeMQTT::append_data(uint8_t *data, uint8_t data_len) 284 | { 285 | memcpy(&buf[pos], data, data_len); 286 | pos += data_len; 287 | } 288 | 289 | void NanodeMQTT::send_packet() 290 | { 291 | // FIXME: support packets longer than 127 bytes 292 | // Store the size of the packet (minus the fixed header) 293 | buf[1] = pos - 2; 294 | uip_send(buf, pos); 295 | 296 | // Restart the packet send timer 297 | timer_restart(&this->transmit_timer); 298 | } 299 | 300 | void NanodeMQTT::tcp_connected() 301 | { 302 | this->state = MQTT_STATE_CONNECTING; 303 | this->tcp_transmit(); 304 | } 305 | 306 | void NanodeMQTT::tcp_acked() 307 | { 308 | switch(this->state) { 309 | case MQTT_STATE_CONNECTING: 310 | this->state = MQTT_STATE_CONNECT_SENT; 311 | break; 312 | case MQTT_STATE_PUBLISHING: 313 | this->state = MQTT_STATE_CONNECTED; 314 | this->payload_length = 0; 315 | break; 316 | case MQTT_STATE_PINGING: 317 | this->state = MQTT_STATE_CONNECTED; 318 | this->ping_pending = 0; 319 | break; 320 | case MQTT_STATE_SUBSCRIBING: 321 | this->state = MQTT_STATE_SUBSCRIBE_SENT; 322 | this->subscribe_topic = NULL; 323 | break; 324 | case MQTT_STATE_DISCONNECTING: 325 | this->state = MQTT_STATE_DISCONNECTED; 326 | uip_close(); 327 | break; 328 | default: 329 | MQTT_DEBUG_PRINTLN("TCP: ack in unknown state"); 330 | break; 331 | } 332 | } 333 | 334 | void NanodeMQTT::tcp_receive() 335 | { 336 | uint8_t *buf = (uint8_t *)uip_appdata; 337 | uint8_t type = buf[0] & 0xF0; 338 | 339 | if (uip_datalen() == 0) 340 | return; 341 | 342 | // FIXME: check that packet isn't too long? 343 | // FIXME: support packets longer than 127 bytes 344 | // FIXME: support multiple MQTT packets in single IP packet 345 | 346 | switch(type) { 347 | case MQTT_TYPE_CONNACK: { 348 | uint8_t code = buf[3]; 349 | if (code == 0) { 350 | MQTT_DEBUG_PRINTLN("MQTT: Connected!"); 351 | this->state = MQTT_STATE_CONNECTED; 352 | } else { 353 | MQTT_DEBUG_PRINTF("MQTT: Connection failed (%u)\n", code); 354 | uip_close(); 355 | this->state = MQTT_STATE_DISCONNECTED; 356 | this->error_code = code; 357 | } 358 | break; 359 | } 360 | case MQTT_TYPE_SUBACK: 361 | MQTT_DEBUG_PRINTLN("MQTT: Subscribed!"); 362 | // FIXME: check current state before changing state 363 | this->state = MQTT_STATE_CONNECTED; 364 | break; 365 | case MQTT_TYPE_PINGRESP: 366 | MQTT_DEBUG_PRINTLN("MQTT: Pong!"); 367 | this->ping_pending = 0; 368 | break; 369 | case MQTT_TYPE_PUBLISH: 370 | if (this->callback) { 371 | uint8_t tl = buf[3]; 372 | // FIXME: is there a way we can NULL-terminate the string in the packet buffer? 373 | char topic[tl+1]; 374 | memcpy(topic, &buf[4], tl); 375 | topic[tl] = 0; 376 | this->callback(topic, buf+4+tl, buf[1]-2-tl); 377 | } 378 | break; 379 | default: 380 | MQTT_DEBUG_PRINTF("MQTT: received unknown packet type (%u)\n", (type >> 4)); 381 | break; 382 | } 383 | 384 | // Restart the packet receive timer 385 | // FIXME: this should only be restarted when a valid packet is received 386 | timer_restart(&this->receive_timer); 387 | } 388 | 389 | void NanodeMQTT::tcp_closed() 390 | { 391 | this->state = MQTT_STATE_DISCONNECTED; 392 | uip_close(); 393 | 394 | // FIXME: re-establish connection automatically 395 | } 396 | 397 | void NanodeMQTT::poll() 398 | { 399 | if (this->state == MQTT_STATE_CONNECTED) { 400 | if (this->payload_length) { 401 | this->state = MQTT_STATE_PUBLISHING; 402 | this->tcp_transmit(); 403 | } else if (this->subscribe_topic) { 404 | this->state = MQTT_STATE_SUBSCRIBING; 405 | this->message_id++; 406 | this->tcp_transmit(); 407 | } else if (this->ping_pending) { 408 | this->state = MQTT_STATE_PINGING; 409 | this->tcp_transmit(); 410 | } 411 | } 412 | } 413 | 414 | void NanodeMQTT::check_timeout() 415 | { 416 | #ifdef MQTT_DEBUG 417 | if (timer_expired(&this->transmit_timer)) 418 | MQTT_DEBUG_PRINTF("MQTT: not transmitted for %u seconds\n", this->keep_alive); 419 | 420 | if (timer_expired(&this->receive_timer)) 421 | MQTT_DEBUG_PRINTF("MQTT: not received for %u seconds\n", this->keep_alive); 422 | #endif 423 | 424 | if (timer_expired(&this->transmit_timer) || timer_expired(&this->receive_timer)) { 425 | if (this->connected()) { 426 | if (!this->ping_pending) { 427 | // Send ping on the next poll 428 | this->ping_pending = 1; 429 | 430 | // Give some extra time to send and receive the ping 431 | // FIXME: think of a better way of doing this - takes too long to timeout 432 | timer_restart(&this->receive_timer); 433 | timer_restart(&this->transmit_timer); 434 | } else { 435 | MQTT_DEBUG_PRINTLN("MQTT: Timed out."); 436 | this->disconnect(); 437 | } 438 | } else { 439 | MQTT_DEBUG_PRINTLN("MQTT: Aborting after time-out."); 440 | this->state = MQTT_STATE_DISCONNECTED; 441 | uip_abort(); 442 | } 443 | } 444 | } 445 | 446 | 447 | // Transmit/re-transmit packet for the current state 448 | void NanodeMQTT::tcp_transmit() 449 | { 450 | switch(this->state) { 451 | case MQTT_STATE_CONNECTING: 452 | MQTT_DEBUG_PRINTLN("MQTT: sending CONNECT"); 453 | init_packet(MQTT_TYPE_CONNECT); 454 | append_string("MQIsdp"); 455 | append_byte(MQTT_PROTOCOL_VERSION); 456 | append_byte(MQTT_FLAG_CLEAN); // Connect flags 457 | append_word(this->keep_alive); 458 | append_string(this->client_id); 459 | send_packet(); 460 | break; 461 | case MQTT_STATE_PUBLISHING: { 462 | uint8_t header = MQTT_TYPE_PUBLISH; 463 | if (payload_retain) 464 | header |= MQTT_FLAG_RETAIN; 465 | 466 | MQTT_DEBUG_PRINTLN("MQTT: sending PUBLISH"); 467 | init_packet(header); 468 | append_string(this->payload_topic); 469 | append_data(this->payload, this->payload_length); 470 | send_packet(); 471 | break; 472 | } 473 | case MQTT_STATE_SUBSCRIBING: 474 | MQTT_DEBUG_PRINTLN("MQTT: sending SUBSCRIBE"); 475 | init_packet(MQTT_TYPE_SUBSCRIBE); 476 | append_word(this->message_id); 477 | append_string(this->subscribe_topic); 478 | append_byte(0x00); // We only support QOS 0 479 | send_packet(); 480 | break; 481 | case MQTT_STATE_PINGING: 482 | MQTT_DEBUG_PRINTLN("MQTT: sending PINGREQ"); 483 | init_packet(MQTT_TYPE_PINGREQ); 484 | send_packet(); 485 | break; 486 | case MQTT_STATE_DISCONNECTING: 487 | MQTT_DEBUG_PRINTLN("MQTT: sending DISCONNECT"); 488 | init_packet(MQTT_TYPE_DISCONNECT); 489 | send_packet(); 490 | break; 491 | default: 492 | MQTT_DEBUG_PRINTF("MQTT: Unable to transmit in state %u.\n", this->state); 493 | break; 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /NanodeMQTT.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2012, Nicholas Humfrey. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote 14 | * products derived from this software without specific prior 15 | * written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 18 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | */ 30 | 31 | #ifndef NanodeMQTT_h 32 | #define NanodeMQTT_h 33 | 34 | #include 35 | 36 | 37 | // Uncomment to enable debugging of NanodeMQTT 38 | //#define MQTT_DEBUG 1 39 | 40 | 41 | #define MQTT_DEFAULT_PORT (1883) 42 | #define MQTT_DEFAULT_KEEP_ALIVE (15) 43 | 44 | #define MQTT_MAX_CLIENT_ID_LEN (23) 45 | #define MQTT_MAX_PAYLOAD_LEN (127) 46 | 47 | 48 | // MQTT Packet Types 49 | #define MQTT_TYPE_CONNECT (1 << 4) // Client request to connect to Server 50 | #define MQTT_TYPE_CONNACK (2 << 4) // Connect Acknowledgment 51 | #define MQTT_TYPE_PUBLISH (3 << 4) // Publish message 52 | #define MQTT_TYPE_TYPE_PUBACK (4 << 4) // Publish Acknowledgment 53 | #define MQTT_TYPE_PUBREC (5 << 4) // Publish Received (assured delivery part 1) 54 | #define MQTT_TYPE_PUBREL (6 << 4) // Publish Release (assured delivery part 2) 55 | #define MQTT_TYPE_PUBCOMP (7 << 4) // Publish Complete (assured delivery part 3) 56 | #define MQTT_TYPE_SUBSCRIBE (8 << 4) // Client Subscribe request 57 | #define MQTT_TYPE_SUBACK (9 << 4) // Subscribe Acknowledgment 58 | #define MQTT_TYPE_UNSUBSCRIBE (10 << 4) // Client Unsubscribe request 59 | #define MQTT_TYPE_UNSUBACK (11 << 4) // Unsubscribe Acknowledgment 60 | #define MQTT_TYPE_PINGREQ (12 << 4) // PING Request 61 | #define MQTT_TYPE_PINGRESP (13 << 4) // PING Response 62 | #define MQTT_TYPE_DISCONNECT (14 << 4) // Client is Disconnecting 63 | 64 | // Fixed header flags (second nibble) 65 | #define MQTT_FLAG_DUP (0x08) 66 | #define MQTT_FLAG_QOS_0 (0x00) 67 | #define MQTT_FLAG_QOS_1 (0x02) 68 | #define MQTT_FLAG_QOS_2 (0x04) 69 | #define MQTT_FLAG_RETAIN (0x01) 70 | 71 | // CONNECT header flags 72 | #define MQTT_PROTOCOL_VERSION (3) 73 | #define MQTT_FLAG_USERNAME (0x80) 74 | #define MQTT_FLAG_PASSWORD (0x40) 75 | #define MQTT_FLAG_WILL_RETAIN (0x20) 76 | #define MQTT_FLAG_WILL_QOS_0 (0x00) 77 | #define MQTT_FLAG_WILL_QOS_1 (0x08) 78 | #define MQTT_FLAG_WILL_QOS_2 (0x10) 79 | #define MQTT_FLAG_WILL (0x04) 80 | #define MQTT_FLAG_CLEAN (0x02) 81 | 82 | 83 | enum mqtt_state { 84 | MQTT_STATE_WAITING, // Waiting for TCP to connect 85 | MQTT_STATE_CONNECTING, // TCP Connected and in middle of sending a CONNECT 86 | MQTT_STATE_CONNECT_SENT, // Waiting for CONNACK 87 | MQTT_STATE_CONNECTED, // Received CONNACK 88 | MQTT_STATE_PUBLISHING, // In the middle of sending a PUBLISH 89 | MQTT_STATE_SUBSCRIBING, // In the middle of sending a SUBSCRIBE 90 | MQTT_STATE_SUBSCRIBE_SENT, // Waiting for a SUBACK 91 | MQTT_STATE_PINGING, // In the middle of sending a PING 92 | MQTT_STATE_DISCONNECTING, // In the middle of sending a DISCONNECT packet 93 | MQTT_STATE_DISCONNECTED 94 | }; 95 | 96 | 97 | #ifdef MQTT_DEBUG 98 | #define MQTT_DEBUG_PRINTLN(str) printf_P(PSTR(str "\n")); 99 | #define MQTT_DEBUG_PRINTF(str, ...) printf_P(PSTR(str), __VA_ARGS__); 100 | #else 101 | #define MQTT_DEBUG_PRINTLN(str) 102 | #define MQTT_DEBUG_PRINTF(str, ...) 103 | #endif 104 | 105 | 106 | typedef void (*mqtt_callback_t) (const char* topic, uint8_t* payload, int payload_length); 107 | 108 | 109 | 110 | class NanodeMQTT { 111 | private: 112 | NanodeUIP *uip; 113 | char client_id[MQTT_MAX_CLIENT_ID_LEN+1]; 114 | uip_ipaddr_t addr; 115 | uint16_t port; 116 | uint16_t keep_alive; 117 | uint16_t message_id; 118 | uint8_t state; 119 | uint8_t ping_pending; 120 | uint8_t blocking_mode; 121 | int8_t error_code; 122 | 123 | uint8_t *buf; 124 | uint8_t pos; 125 | 126 | struct timer receive_timer; 127 | struct timer transmit_timer; 128 | 129 | // Publishing 130 | // FIXME: can we do without these buffers 131 | char payload_topic[32]; 132 | uint8_t payload[MQTT_MAX_PAYLOAD_LEN]; 133 | uint8_t payload_length; 134 | uint8_t payload_retain; 135 | 136 | // Subscribing 137 | const char *subscribe_topic; 138 | mqtt_callback_t callback; 139 | 140 | public: 141 | NanodeMQTT(NanodeUIP *uip); 142 | 143 | void set_client_id(const char* client_id); 144 | void set_server_addr(byte a, byte b, byte c, byte d); 145 | void set_server_port(uint16_t port); 146 | void set_keep_alive(uint16_t secs); 147 | void set_blocking_mode(uint8_t blocking); 148 | void set_callback(mqtt_callback_t callback); 149 | 150 | void connect(); 151 | void disconnect(); 152 | uint8_t connected(); 153 | uint8_t get_state(); 154 | int8_t get_error_code(); 155 | 156 | void publish(const char* topic, const char* payload); 157 | void publish(const char* topic, const uint8_t* payload, uint8_t plength); 158 | void publish(const char* topic, const uint8_t* payload, uint8_t plength, uint8_t retained); 159 | 160 | void subscribe(const char* topic); 161 | 162 | 163 | 164 | // uIP Callbacks (used internally) 165 | void tcp_closed(); 166 | void tcp_connected(); 167 | void tcp_acked(); 168 | void tcp_receive(); 169 | void tcp_transmit(); 170 | void poll(); 171 | void check_timeout(); 172 | 173 | 174 | private: 175 | // Packet assembly functions 176 | void init_packet(uint8_t header); 177 | void append_byte(uint8_t b); 178 | void append_word(uint16_t s); 179 | void append_string(const char* str); 180 | void append_data(uint8_t *data, uint8_t data_len); 181 | void send_packet(); 182 | }; 183 | 184 | 185 | struct mqtt_app_state { 186 | NanodeMQTT* mqtt; 187 | }; 188 | UIPASSERT(sizeof(struct mqtt_app_state)<=TCP_APP_STATE_SIZE) 189 | 190 | 191 | #endif 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note that I am no-longer working on this codebase. 2 | ================================================== 3 | 4 | 5 | NanodeMQTT 6 | ---------- 7 | 8 | Implementation of [MQTT] for the [Nanode]. MQTT is a lightweight PubSub protocol. 9 | Nanode is an open source Arduino-like board that has in-built internet connectivity. 10 | It is licensed under the [BSD 3-Clause License]. 11 | 12 | 13 | Requirements 14 | ------------ 15 | 16 | * [NanodeUIP] - [uIP] stack for Nanode 17 | * [NanodeUNIO] - MAC address reading for Nanode 18 | 19 | 20 | Limitations 21 | ----------- 22 | 23 | - Maximum packet size is 127 bytes 24 | - Only QOS 0 supported 25 | 26 | 27 | Thanks to 28 | --------- 29 | 30 | * Nicholas O'Leary (Author of [PubSubClient] for Arduino) 31 | * Stephen Early (Author of [NanodeUIP]) 32 | 33 | 34 | 35 | 36 | [MQTT]: http://mqtt.org/ 37 | [Nanode]: http://nanode.eu/ 38 | [NanodeUIP]: http://github.com/sde1000/NanodeUIP 39 | [NanodeUNIO]: http://github.com/sde1000/NanodeUNIO 40 | [uIP]: http://en.wikipedia.org/wiki/UIP_(micro_IP) 41 | [Contiki]: http://www.contiki-os.org/ 42 | [PubSubClient]: http://knolleary.net/arduino-client-for-mqtt/ 43 | [BSD 3-Clause License]: http://www.opensource.org/licenses/BSD-3-Clause 44 | -------------------------------------------------------------------------------- /examples/mqtt_publish/mqtt_publish.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NanodeMQTT mqtt(&uip); 6 | struct timer my_timer; 7 | 8 | void setup() { 9 | byte macaddr[6]; 10 | NanodeUNIO unio(NANODE_MAC_DEVICE); 11 | 12 | Serial.begin(38400); 13 | Serial.println("MQTT Publish test"); 14 | 15 | unio.read(macaddr, NANODE_MAC_ADDRESS, 6); 16 | uip.init(macaddr); 17 | 18 | // FIXME: use DHCP instead 19 | uip.set_ip_addr(10, 100, 10, 10); 20 | uip.set_netmask(255, 255, 255, 0); 21 | 22 | uip.wait_for_link(); 23 | Serial.println("Link is up"); 24 | 25 | // Setup a timer - publish every 5 seconds 26 | timer_set(&my_timer, CLOCK_SECOND * 5); 27 | 28 | // FIXME: resolve using DNS instead 29 | mqtt.set_server_addr(10, 100, 10, 1); 30 | mqtt.connect(); 31 | 32 | Serial.println("setup() done"); 33 | } 34 | 35 | void loop() { 36 | uip.poll(); 37 | 38 | if(timer_expired(&my_timer)) { 39 | timer_reset(&my_timer); 40 | if (mqtt.connected()) { 41 | Serial.println("Publishing..."); 42 | mqtt.publish("test", "Hello World!"); 43 | Serial.println("Published."); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/mqtt_subscribe/mqtt_subscribe.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NanodeMQTT mqtt(&uip); 6 | 7 | void message_callback(const char* topic, uint8_t* payload, int payload_length) 8 | { 9 | Serial.print(topic); 10 | Serial.print(" => "); 11 | Serial.write(payload, payload_length); 12 | Serial.println(); 13 | } 14 | 15 | void setup() { 16 | byte macaddr[6]; 17 | NanodeUNIO unio(NANODE_MAC_DEVICE); 18 | 19 | Serial.begin(38400); 20 | Serial.println("MQTT Subscribe test"); 21 | 22 | unio.read(macaddr, NANODE_MAC_ADDRESS, 6); 23 | uip.init(macaddr); 24 | 25 | // FIXME: use DHCP instead 26 | uip.set_ip_addr(10, 100, 10, 10); 27 | uip.set_netmask(255, 255, 255, 0); 28 | 29 | uip.wait_for_link(); 30 | Serial.println("Link is up"); 31 | 32 | // FIXME: resolve using DNS instead 33 | mqtt.set_server_addr(10, 100, 10, 1); 34 | mqtt.set_callback(message_callback); 35 | mqtt.connect(); 36 | Serial.println("Connected to MQTT server"); 37 | 38 | mqtt.subscribe("test"); 39 | Serial.println("Subscribed."); 40 | 41 | Serial.println("setup() done"); 42 | } 43 | 44 | void loop() { 45 | uip.poll(); 46 | } 47 | -------------------------------------------------------------------------------- /internal-states.gv: -------------------------------------------------------------------------------- 1 | digraph NanodeMQTT { 2 | MQTT_STATE_WAITING; 3 | MQTT_STATE_CONNECTING; 4 | MQTT_STATE_CONNECT_SENT; 5 | MQTT_STATE_CONNECTED; 6 | MQTT_STATE_PUBLISHING; 7 | MQTT_STATE_SUBSCRIBING; 8 | MQTT_STATE_SUBSCRIBE_SENT; 9 | MQTT_STATE_PINGING; 10 | MQTT_STATE_DISCONNECTING; 11 | MQTT_STATE_DISCONNECTED; 12 | 13 | MQTT_STATE_WAITING -> MQTT_STATE_CONNECTING [label="TCP Syn"]; 14 | MQTT_STATE_CONNECTING -> MQTT_STATE_CONNECT_SENT [label="TCP Ack"]; 15 | MQTT_STATE_CONNECT_SENT -> MQTT_STATE_CONNECTED [label="Receive CONNACK=0"]; 16 | MQTT_STATE_CONNECT_SENT -> MQTT_STATE_DISCONNECTED [label="Receive CONNACK!=0"]; 17 | 18 | MQTT_STATE_CONNECTED -> MQTT_STATE_PUBLISHING [label="publish()"]; 19 | MQTT_STATE_PUBLISHING -> MQTT_STATE_CONNECTED [label="TCP Ack"]; 20 | 21 | MQTT_STATE_CONNECTED -> MQTT_STATE_SUBSCRIBING [label="subscribe()"]; 22 | MQTT_STATE_SUBSCRIBING -> MQTT_STATE_SUBSCRIBE_SENT [label="TCP Ack"]; 23 | MQTT_STATE_SUBSCRIBE_SENT -> MQTT_STATE_CONNECTED [label="Receive SUBACK"]; 24 | 25 | MQTT_STATE_CONNECTED -> MQTT_STATE_PINGING [label="timer expire"]; 26 | MQTT_STATE_PINGING -> MQTT_STATE_CONNECTED [label="TCP Ack"]; 27 | 28 | MQTT_STATE_CONNECTED -> MQTT_STATE_DISCONNECTING [label="disconnect()"]; 29 | MQTT_STATE_DISCONNECTING -> MQTT_STATE_DISCONNECTED [label="TCP Ack"]; 30 | } 31 | -------------------------------------------------------------------------------- /internal-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njh/NanodeMQTT/10db5f069a58d7325ec30df17093d84c9c781db8/internal-states.png --------------------------------------------------------------------------------