diff --git a/examples/MQTT-Publisher.ino b/examples/MQTT-Publisher.ino new file mode 100644 index 0000000..c3ebe47 --- /dev/null +++ b/examples/MQTT-Publisher.ino @@ -0,0 +1,68 @@ +#include +#include + +#define TOPIC "test" + +MQTTSN mqttsn; + +uint16_t u16TopicID; + +void setup() { + Serial.begin(115200); + Serial1.begin(9600); +} + +void loop() { + uint8_t index; + + CheckSerial(); + delay(2000); + + if (mqttsn.wait_for_response()) { + return; + } + + if (!mqttsn.connected()) { + mqttsn.connect(0, 10, "arduino"); + return; + } + + u16TopicID = mqttsn.find_topic_id(TOPIC, &index); + if (u16TopicID == 0xffff) { + mqttsn.register_topic(TOPIC); + return; + } + + char str[50] = "Hello World!"; + mqttsn.publish(0, u16TopicID, str, strlen(str)); +} + +void MQTTSN_serial_send(uint8_t *message_buffer, int length) { + Serial1.write(message_buffer, length); + Serial1.flush(); +} + +void MQTTSN_publish_handler(const msg_publish *msg) { + +} + +void MQTTSN_gwinfo_handler(const msg_gwinfo *msg) { +} + +void CheckSerial() { + uint16_t cnt = 0; + uint8_t buffer[512]; + uint8_t *buf = &buffer[0]; + + while (Serial1.available()) { + buffer[cnt++] = Serial1.read(); + } + + if (cnt > 0) { + for (int i = 0; i < cnt ; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(" "); + } + Serial.println(""); + mqttsn.parse_stream(buf, cnt); + } +} diff --git a/examples/MQTT-Subscriber.ino b/examples/MQTT-Subscriber.ino new file mode 100644 index 0000000..de46558 --- /dev/null +++ b/examples/MQTT-Subscriber.ino @@ -0,0 +1,71 @@ +/* + (c) Diego Fernández + Originally published on https://github.com/ESIBot/MQTT-SN-Arduino +*/ + +#include +#include + +#define TOPIC "test" + +MQTTSN mqttsn; + +uint16_t u16TopicID; + +void setup() { + Serial.begin(115200); + Serial1.begin(9600); +} + +void loop() { + uint8_t index; + + CheckSerial(); + delay(2000); + + if (mqttsn.wait_for_response()) { + return; + } + + if (!mqttsn.connected()) { + mqttsn.connect(0, 10, "arduino"); + return; + } + + u16TopicID = mqttsn.find_topic_id(TOPIC, &index); + if (u16TopicID == 0xffff) { + mqttsn.register_topic(TOPIC); + mqttsn.subscribe_by_name(0, TOPIC); + return; + } +} + +void MQTTSN_serial_send(uint8_t *message_buffer, int length) { + Serial1.write(message_buffer, length); + Serial1.flush(); +} + +void MQTTSN_publish_handler(const msg_publish *msg) { + +} + +void MQTTSN_gwinfo_handler(const msg_gwinfo *msg) { +} + +void CheckSerial() { + uint16_t cnt = 0; + uint8_t buffer[512]; + uint8_t *buf = &buffer[0]; + + while (Serial1.available()) { + buffer[cnt++] = Serial1.read(); + } + + if (cnt > 0) { + for (int i = 0; i < cnt ; i++) { + Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(" "); + } + Serial.println(""); + mqttsn.parse_stream(buf, cnt); + } +} diff --git a/examples/config.h b/examples/config.h new file mode 100644 index 0000000..069bd6a --- /dev/null +++ b/examples/config.h @@ -0,0 +1,3 @@ +#define API_DATA_LEN 20 +#define API_PAY_LEN (API_DATA_LEN + 5) +#define API_FRAME_LEN (API_DATA_LEN + 9) diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..6e89375 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,31 @@ +MQTTSN KEYWORD1 + +find_topic_id KEYWORD2 +wait_for_response KEYWORD2 +connected KEYWORD2 +parse_stream KEYWORD2 +searchgw KEYWORD2 +connect KEYWORD2 +willtopic KEYWORD2 +willmsg KEYWORD2 +register_topic KEYWORD2 +publish KEYWORD2 +subscribe_by_name KEYWORD2 +subscribe_by_id KEYWORD2 +unsubscribe_by_name KEYWORD2 +unsubscribe_by_id KEYWORD2 +pingreq KEYWORD2 +pingresp KEYWORD2 +disconnect KEYWORD2 + +FLAG_DUP LITERAL1 +FLAG_QOS_0 LITERAL1 +FLAG_QOS_1 LITERAL1 +FLAG_QOS_2 LITERAL1 +FLAG_QOS_M1 LITERAL1 +FLAG_RETAIN LITERAL1 +FLAG_WILL LITERAL1 +FLAG_CLEAN LITERAL1 +FLAG_TOPIC_NAME LITERAL1 +FLAG_TOPIC_PREDEFINED_ID LITERAL1 +FLAG_TOPIC_SHORT_NAME LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..18d7702 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=MQTT-SN for Arduino +version=0.1 +author=John Donovan , Boris , Diego Fernández , Giampaolo Mancini +maintainer=Giampaolo Mancini +sentence=Simple MQTT-SN library for Arduino. +paragraph=An implementation of the MQTT-SN client protocol for AVR-based microcontrollers. +category=Communication +url=https://github.com/kiotlog/mqttsn-arduino +architectures=* +includes=mqttsn-messages.h \ No newline at end of file diff --git a/src/mqttsn-messages.cpp b/src/mqttsn-messages.cpp new file mode 100644 index 0000000..91326a5 --- /dev/null +++ b/src/mqttsn-messages.cpp @@ -0,0 +1,564 @@ +/* +mqttsn-messages.cpp + +The MIT License (MIT) + +Copyright (C) 2014 John Donovan + +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. +*/ + +#include + +#include "mqttsn-messages.h" +#include "mqttsn.h" + +#ifdef USE_RF12 +#include +#endif + +#if !(USE_RF12 || USE_SERIAL) +#error "You really should define one or both of USE_RF12 or USE_SERIAL." +#endif + +MQTTSN::MQTTSN() : +waiting_for_response(false), +_connected(false), +_message_id(0), +topic_count(0), +_gateway_id(0), +_response_timer(0), +_response_retries(0) +{ + memset(topic_table, 0, sizeof(topic) * MAX_TOPICS); + memset(message_buffer, 0, MAX_BUFFER_SIZE); + memset(response_buffer, 0, MAX_BUFFER_SIZE); +} + +MQTTSN::~MQTTSN() { +} + +bool MQTTSN::wait_for_response() { + if (waiting_for_response) { + // TODO: Watch out for overflow. + if ((millis() - _response_timer) > (T_RETRY * 1000L)) { + _response_timer = millis(); + + if (_response_retries == 0) { + waiting_for_response = false; + disconnect_handler(NULL); + } else { + send_message(); + } + + --_response_retries; + } + } + + return waiting_for_response; +} + +bool MQTTSN::connected() { + return _connected; +} + +uint16_t MQTTSN::bswap(const uint16_t val) { + return (val << 8) | (val >> 8); +} + +uint16_t MQTTSN::find_topic_id(const char* name, uint8_t* index) { + for (uint8_t i = 0; i < topic_count; ++i) { + if (strcmp(topic_table[i].name, name) == 0 && topic_table[i].id != 0xffff) { + *index = i; + return topic_table[i].id; + } + } + + return 0xffff; +} + +#ifdef USE_SERIAL +void MQTTSN::parse_stream(uint8_t* buf, uint16_t len) { + memcpy(response_buffer, (const void*)buf, len); + dispatch(); +} +#endif + +#ifdef USE_RF12 +void MQTTSN::parse_rf12() { + memcpy(response_buffer, (const void*)rf12_data, RF12_MAXDATA < MAX_BUFFER_SIZE ? RF12_MAXDATA : MAX_BUFFER_SIZE); + dispatch(); +} +#endif + +void MQTTSN::dispatch() { + message_header* response_message = (message_header*)response_buffer; + + switch (response_message->type) { + case ADVERTISE: + advertise_handler((msg_advertise*)response_buffer); + break; + + case GWINFO: + gwinfo_handler((msg_gwinfo*)response_buffer); + break; + + case CONNACK: + connack_handler((msg_connack*)response_buffer); + break; + + case WILLTOPICREQ: + willtopicreq_handler(response_message); + break; + + case WILLMSGREQ: + willmsgreq_handler(response_message); + break; + + case REGISTER: + register_handler((msg_register*)response_buffer); + break; + + case REGACK: + regack_handler((msg_regack*)response_buffer); + break; + + case PUBLISH: + publish_handler((msg_publish*)response_buffer); + break; + + case PUBACK: + puback_handler((msg_puback*)response_buffer); + break; + + case SUBACK: + suback_handler((msg_suback*)response_buffer); + break; + + case UNSUBACK: + unsuback_handler((msg_unsuback*)response_buffer); + break; + + case PINGREQ: + pingreq_handler((msg_pingreq*)response_buffer); + break; + + case PINGRESP: + pingresp_handler(); + break; + + case DISCONNECT: + disconnect_handler((msg_disconnect*)response_buffer); + break; + + case WILLTOPICRESP: + willtopicresp_handler((msg_willtopicresp*)response_buffer); + break; + + case WILLMSGRESP: + willmsgresp_handler((msg_willmsgresp*)response_buffer); + break; + + default: + return; + } + + waiting_for_response = false; +} + +void MQTTSN::send_message() { + message_header* hdr = reinterpret_cast(message_buffer); + +#ifdef USE_RF12 + while (!rf12_canSend()) { + rf12_recvDone(); + Sleepy::loseSomeTime(32); + } + rf12_sendStart(_gateway_id, message_buffer, hdr->length); + rf12_sendWait(2); +#endif +#ifdef USE_SERIAL + extern void MQTTSN_serial_send(uint8_t* message_buffer, int length); + MQTTSN_serial_send(message_buffer, hdr->length); +#endif + + if (!waiting_for_response) { + _response_timer = millis(); + _response_retries = N_RETRY; + } +} + +void MQTTSN::advertise_handler(const msg_advertise* msg) { + _gateway_id = msg->gw_id; +} + +extern void MQTTSN_gwinfo_handler(const msg_gwinfo* msg); +void MQTTSN::gwinfo_handler(const msg_gwinfo* msg) { + MQTTSN_gwinfo_handler(msg); +} + +void MQTTSN::connack_handler(const msg_connack* msg) { + _connected = 1; +} + +void MQTTSN::willtopicreq_handler(const message_header* msg) { +} + +void MQTTSN::willmsgreq_handler(const message_header* msg) { +} + +void MQTTSN::regack_handler(const msg_regack* msg) { + if (msg->return_code == 0 && topic_count < MAX_TOPICS && bswap(msg->message_id) == _message_id) { + topic_table[topic_count].id = bswap(msg->topic_id); + ++topic_count; + } +} + +void MQTTSN::puback_handler(const msg_puback* msg) { +} + +#ifdef USE_QOS2 +void MQTTSN::pubrec_handler(const msg_pubqos2* msg) { +} + +void MQTTSN::pubrel_handler(const msg_pubqos2* msg) { +} + +void MQTTSN::pubcomp_handler(const msg_pubqos2* msg) { +} +#endif + +void MQTTSN::pingreq_handler(const msg_pingreq* msg) { + pingresp(); +} + +void MQTTSN::suback_handler(const msg_suback* msg) { +} + +void MQTTSN::unsuback_handler(const msg_unsuback* msg) { +} + +void MQTTSN::disconnect_handler(const msg_disconnect* msg) { + _connected = false; +} + +void MQTTSN::pingresp_handler() { +} + +extern void MQTTSN_publish_handler(const msg_publish* msg); +void MQTTSN::publish_handler(const msg_publish* msg) { + if (msg->flags & FLAG_QOS_1) { + return_code_t ret = REJECTED_INVALID_TOPIC_ID; + const uint16_t topic_id = bswap(msg->topic_id); + + for (uint8_t i = 0; i < topic_count; ++i) { + if (topic_table[i].id == topic_id) { + ret = ACCEPTED; + MQTTSN_publish_handler(msg); + break; + } + } + + puback(msg->topic_id, msg->message_id, ret); + } +} + +void MQTTSN::register_handler(const msg_register* msg) { + return_code_t ret = REJECTED_INVALID_TOPIC_ID; + uint8_t index; + uint16_t topic_id = find_topic_id(msg->topic_name, &index); + + if (topic_id != 0xffff) { + topic_table[index].id = bswap(msg->topic_id); + ret = ACCEPTED; + } + + regack(msg->topic_id, msg->message_id, ret); +} + +void MQTTSN::willtopicresp_handler(const msg_willtopicresp* msg) { +} + +void MQTTSN::willmsgresp_handler(const msg_willmsgresp* msg) { +} + +void MQTTSN::searchgw(const uint8_t radius) { + msg_searchgw* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_searchgw); + msg->type = SEARCHGW; + msg->radius = radius; + + send_message(); + waiting_for_response = true; +} + +void MQTTSN::connect(const uint8_t flags, const uint16_t duration, const char* client_id) { + msg_connect* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_connect) + strlen(client_id); + msg->type = CONNECT; + msg->flags = flags; + msg->protocol_id = PROTOCOL_ID; + msg->duration = bswap(duration); + strcpy(msg->client_id, client_id); + + send_message(); + _connected = false; + waiting_for_response = true; +} + +void MQTTSN::willtopic(const uint8_t flags, const char* will_topic, const bool update) { + if (will_topic == NULL) { + message_header* msg = reinterpret_cast(message_buffer); + + msg->type = update ? WILLTOPICUPD : WILLTOPIC; + msg->length = sizeof(message_header); + } else { + msg_willtopic* msg = reinterpret_cast(message_buffer); + + msg->type = update ? WILLTOPICUPD : WILLTOPIC; + msg->flags = flags; + strcpy(msg->will_topic, will_topic); + } + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +void MQTTSN::willmsg(const void* will_msg, const uint8_t will_msg_len, const bool update) { + msg_willmsg* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_willmsg) + will_msg_len; + msg->type = update ? WILLMSGUPD : WILLMSG; + memcpy(msg->willmsg, will_msg, will_msg_len); + + send_message(); +} + +void MQTTSN::disconnect(const uint16_t duration) { + msg_disconnect* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(message_header); + msg->type = DISCONNECT; + + if (duration > 0) { + msg->length += sizeof(duration); + msg->duration = bswap(duration); + } + + send_message(); + waiting_for_response = true; +} + +bool MQTTSN::register_topic(const char* name) { + if (!waiting_for_response && topic_count < (MAX_TOPICS - 1)) { + ++_message_id; + + // Fill in the next table entry, but we only increment the counter to + // the next topic when we get a REGACK from the broker. So don't issue + // another REGISTER until we have resolved this one. + topic_table[topic_count].name = name; + topic_table[topic_count].id = 0xffff; + + msg_register* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_register) + strlen(name); + msg->type = REGISTER; + msg->topic_id = 0; + msg->message_id = bswap(_message_id); + strcpy(msg->topic_name, name); + + send_message(); + waiting_for_response = true; + return true; + } + + return false; +} + +void MQTTSN::regack(const uint16_t topic_id, const uint16_t message_id, const return_code_t return_code) { + msg_regack* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_regack); + msg->type = REGACK; + msg->topic_id = bswap(topic_id); + msg->message_id = bswap(message_id); + msg->return_code = return_code; + + send_message(); +} + +void MQTTSN::publish(const uint8_t flags, const uint16_t topic_id, const void* data, const uint8_t data_len) { + ++_message_id; + + msg_publish* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_publish) + data_len; + msg->type = PUBLISH; + msg->flags = flags; + msg->topic_id = bswap(topic_id); + msg->message_id = bswap(_message_id); + memcpy(msg->data, data, data_len); + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +#ifdef USE_QOS2 +void MQTTSN::pubrec() { + msg_pubqos2* msg = reinterpret_cast(message_buffer); + msg->length = sizeof(msg_pubqos2); + msg->type = PUBREC; + msg->message_id = bswap(_message_id); + + send_message(); +} + +void MQTTSN::pubrel() { + msg_pubqos2* msg = reinterpret_cast(message_buffer); + msg->length = sizeof(msg_pubqos2); + msg->type = PUBREL; + msg->message_id = bswap(_message_id); + + send_message(); +} + +void MQTTSN::pubcomp() { + msg_pubqos2* msg = reinterpret_cast(message_buffer); + msg->length = sizeof(msg_pubqos2); + msg->type = PUBCOMP; + msg->message_id = bswap(_message_id); + + send_message(); +} +#endif + +void MQTTSN::puback(const uint16_t topic_id, const uint16_t message_id, const return_code_t return_code) { + msg_puback* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_puback); + msg->type = PUBACK; + msg->topic_id = bswap(topic_id); + msg->message_id = bswap(message_id); + msg->return_code = return_code; + + send_message(); +} + +void MQTTSN::subscribe_by_name(const uint8_t flags, const char* topic_name) { + ++_message_id; + + msg_subscribe* msg = reinterpret_cast(message_buffer); + + // The -2 here is because we're unioning a 0-length member (topic_name) + // with a uint16_t in the msg_subscribe struct. + msg->length = sizeof(msg_subscribe) + strlen(topic_name) - 2; + msg->type = SUBSCRIBE; + msg->flags = (flags & QOS_MASK) | FLAG_TOPIC_NAME; + msg->message_id = bswap(_message_id); + strcpy(msg->topic_name, topic_name); + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +void MQTTSN::subscribe_by_id(const uint8_t flags, const uint16_t topic_id) { + ++_message_id; + + msg_subscribe* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_subscribe); + msg->type = SUBSCRIBE; + msg->flags = (flags & QOS_MASK) | FLAG_TOPIC_PREDEFINED_ID; + msg->message_id = bswap(_message_id); + msg->topic_id = bswap(topic_id); + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +void MQTTSN::unsubscribe_by_name(const uint8_t flags, const char* topic_name) { + ++_message_id; + + msg_unsubscribe* msg = reinterpret_cast(message_buffer); + + // The -2 here is because we're unioning a 0-length member (topic_name) + // with a uint16_t in the msg_unsubscribe struct. + msg->length = sizeof(msg_unsubscribe) + strlen(topic_name) - 2; + msg->type = UNSUBSCRIBE; + msg->flags = (flags & QOS_MASK) | FLAG_TOPIC_NAME; + msg->message_id = bswap(_message_id); + strcpy(msg->topic_name, topic_name); + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +void MQTTSN::unsubscribe_by_id(const uint8_t flags, const uint16_t topic_id) { + ++_message_id; + + msg_unsubscribe* msg = reinterpret_cast(message_buffer); + + msg->length = sizeof(msg_unsubscribe); + msg->type = UNSUBSCRIBE; + msg->flags = (flags & QOS_MASK) | FLAG_TOPIC_PREDEFINED_ID; + msg->message_id = bswap(_message_id); + msg->topic_id = bswap(topic_id); + + send_message(); + + if ((flags & QOS_MASK) == FLAG_QOS_1 || (flags & QOS_MASK) == FLAG_QOS_2) { + waiting_for_response = true; + } +} + +void MQTTSN::pingreq(const char* client_id) { + msg_pingreq* msg = reinterpret_cast(message_buffer); + msg->length = sizeof(msg_pingreq) + strlen(client_id); + msg->type = PINGREQ; + strcpy(msg->client_id, client_id); + + send_message(); + + waiting_for_response = true; +} + +void MQTTSN::pingresp() { + message_header* msg = reinterpret_cast(message_buffer); + msg->length = sizeof(message_header); + msg->type = PINGRESP; + + send_message(); +} diff --git a/src/mqttsn-messages.h b/src/mqttsn-messages.h new file mode 100644 index 0000000..bc012e8 --- /dev/null +++ b/src/mqttsn-messages.h @@ -0,0 +1,121 @@ +/* +mqttsn-messages.h + +The MIT License (MIT) + +Copyright (C) 2014 John Donovan + +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. +*/ + +#ifndef __MQTTSN_MESSAGES_H__ +#define __MQTTSN_MESSAGES_H__ + +#include "mqttsn.h" + +#define MAX_TOPICS 10 +#define MAX_BUFFER_SIZE 66 + +class MQTTSN { +public: + MQTTSN(); + virtual ~MQTTSN(); + + uint16_t find_topic_id(const char* name, uint8_t* index); + bool wait_for_response(); + bool connected(); +#ifdef USE_SERIAL + void parse_stream(uint8_t* buf, uint16_t len); +#endif +#ifdef USE_RF12 + void parse_rf12(); +#endif + + void searchgw(const uint8_t radius); + void connect(const uint8_t flags, const uint16_t duration, const char* client_id); + void willtopic(const uint8_t flags, const char* will_topic, const bool update = false); + void willmsg(const void* will_msg, const uint8_t will_msg_len, const bool update = false); + bool register_topic(const char* name); + void publish(const uint8_t flags, const uint16_t topic_id, const void* data, const uint8_t data_len); +#ifdef USE_QOS2 + void pubrec(); + void pubrel(); + void pubcomp(); +#endif + void subscribe_by_name(const uint8_t flags, const char* topic_name); + void subscribe_by_id(const uint8_t flags, const uint16_t topic_id); + void unsubscribe_by_name(const uint8_t flags, const char* topic_name); + void unsubscribe_by_id(const uint8_t flags, const uint16_t topic_id); + void pingreq(const char* client_id); + void pingresp(); + void disconnect(const uint16_t duration); + +protected: + virtual void advertise_handler(const msg_advertise* msg); + virtual void gwinfo_handler(const msg_gwinfo* msg); + virtual void connack_handler(const msg_connack* msg); + virtual void willtopicreq_handler(const message_header* msg); + virtual void willmsgreq_handler(const message_header* msg); + virtual void regack_handler(const msg_regack* msg); + virtual void publish_handler(const msg_publish* msg); + virtual void register_handler(const msg_register* msg); + virtual void puback_handler(const msg_puback* msg); +#ifdef USE_QOS2 + virtual void pubrec_handler(const msg_pubqos2* msg); + virtual void pubrel_handler(const msg_pubqos2* msg); + virtual void pubcomp_handler(const msg_pubqos2* msg); +#endif + virtual void suback_handler(const msg_suback* msg); + virtual void unsuback_handler(const msg_unsuback* msg); + virtual void pingreq_handler(const msg_pingreq* msg); + virtual void pingresp_handler(); + virtual void disconnect_handler(const msg_disconnect* msg); + virtual void willtopicresp_handler(const msg_willtopicresp* msg); + virtual void willmsgresp_handler(const msg_willmsgresp* msg); + + void regack(const uint16_t topic_id, const uint16_t message_id, const return_code_t return_code); + void puback(const uint16_t topic_id, const uint16_t message_id, const return_code_t return_code); + +private: + struct topic { + const char* name; + uint16_t id; + }; + + void dispatch(); + uint16_t bswap(const uint16_t val); + void send_message(); + + // Set to true when we're waiting for some sort of acknowledgement from the + //server that will transition our state. + bool waiting_for_response; + bool _connected; + uint16_t _message_id; + uint8_t topic_count; + + uint8_t message_buffer[MAX_BUFFER_SIZE]; + uint8_t response_buffer[MAX_BUFFER_SIZE]; + topic topic_table[MAX_TOPICS]; + + uint8_t _gateway_id; + uint32_t _response_timer; + uint8_t _response_retries; +}; + +#endif diff --git a/src/mqttsn.h b/src/mqttsn.h new file mode 100644 index 0000000..746c7ce --- /dev/null +++ b/src/mqttsn.h @@ -0,0 +1,209 @@ +/* +mqttsn.h + +The MIT License (MIT) + +Copyright (C) 2014 John Donovan + +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. +*/ + +#ifndef __MQTTSN_H__ +#define __MQTTSN_H__ + +//#define USE_RF12 1 +#define USE_SERIAL 1 + +#define PROTOCOL_ID 0x01 + +#define FLAG_DUP 0x80 +#define FLAG_QOS_0 0x00 +#define FLAG_QOS_1 0x20 +#define FLAG_QOS_2 0x40 +#define FLAG_QOS_M1 0x60 +#define FLAG_RETAIN 0x10 +#define FLAG_WILL 0x08 +#define FLAG_CLEAN 0x04 +#define FLAG_TOPIC_NAME 0x00 +#define FLAG_TOPIC_PREDEFINED_ID 0x01 +#define FLAG_TOPIC_SHORT_NAME 0x02 + +#define QOS_MASK (FLAG_QOS_0 | FLAG_QOS_1 | FLAG_QOS_2 | FLAG_QOS_M1) +#define TOPIC_MASK (FLAG_TOPIC_NAME | FLAG_TOPIC_PREDEFINED_ID | FLAG_TOPIC_SHORT_NAME) + +// Recommended values for timers and counters. All timers are in seconds. +#define T_ADV 960 +#define N_ADV 3 +#define T_SEARCH_GW 5 +#define T_GW_INFO 5 +#define T_WAIT 360 +#define T_RETRY 15 +#define N_RETRY 5 + +enum return_code_t { + ACCEPTED, + REJECTED_CONGESTION, + REJECTED_INVALID_TOPIC_ID, + REJECTED_NOT_SUPPORTED +}; + +enum message_type { + ADVERTISE, + SEARCHGW, + GWINFO, + CONNECT = 0x04, + CONNACK, + WILLTOPICREQ, + WILLTOPIC, + WILLMSGREQ, + WILLMSG, + REGISTER, + REGACK, + PUBLISH, + PUBACK, + PUBCOMP, + PUBREC, + PUBREL, + SUBSCRIBE = 0x12, + SUBACK, + UNSUBSCRIBE, + UNSUBACK, + PINGREQ, + PINGRESP, + DISCONNECT, + WILLTOPICUPD = 0x1a, + WILLTOPICRESP, + WILLMSGUPD, + WILLMSGRESP +}; + +struct message_header { + uint8_t length; + uint8_t type; +}; + +struct msg_advertise : public message_header { + uint8_t gw_id; + uint16_t duration; +}; + +struct msg_searchgw : public message_header { + uint8_t radius; +}; + +struct msg_gwinfo : public message_header { + uint8_t gw_id; + char gw_add[0]; +}; + +struct msg_connect : public message_header { + uint8_t flags; + uint8_t protocol_id; + uint16_t duration; + char client_id[0]; +}; + +struct msg_connack : public message_header { + return_code_t return_code; +}; + +struct msg_willtopic : public message_header { + uint8_t flags; + char will_topic[0]; +}; + +struct msg_willmsg : public message_header { + char willmsg[0]; +}; + +struct msg_register : public message_header { + uint16_t topic_id; + uint16_t message_id; + char topic_name[0]; +}; + +struct msg_regack : public message_header { + uint16_t topic_id; + uint16_t message_id; + uint8_t return_code; +}; + +struct msg_publish : public message_header { + uint8_t flags; + uint16_t topic_id; + uint16_t message_id; + char data[0]; +}; + +struct msg_puback : public message_header { + uint16_t topic_id; + uint16_t message_id; + uint8_t return_code; +}; + +struct msg_pubqos2 : public message_header { + uint16_t message_id; +}; + +struct msg_subscribe : public message_header { + uint8_t flags; + uint16_t message_id; + union { + char topic_name[0]; + uint16_t topic_id; + }; +}; + +struct msg_suback : public message_header { + uint8_t flags; + uint16_t topic_id; + uint16_t message_id; + uint8_t return_code; +}; + +struct msg_unsubscribe : public message_header { + uint8_t flags; + uint16_t message_id; + union { + char topic_name[0]; + uint16_t topic_id; + }; +}; + +struct msg_unsuback : public message_header { + uint16_t message_id; +}; + +struct msg_pingreq : public message_header { + char client_id[0]; +}; + +struct msg_disconnect : public message_header { + uint16_t duration; +}; + +struct msg_willtopicresp : public message_header { + uint8_t return_code; +}; + +struct msg_willmsgresp : public message_header { + uint8_t return_code; +}; + +#endif