├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── Makefile ├── README.md ├── examples ├── AdafruitHuzzahESP8266 │ └── AdafruitHuzzahESP8266.ino ├── AdafruitHuzzahESP8266Secure │ └── AdafruitHuzzahESP8266Secure.ino ├── ArduinoEthernetShield │ └── ArduinoEthernetShield.ino ├── ArduinoMKRGSM1400 │ └── ArduinoMKRGSM1400.ino ├── ArduinoMKRGSM1400Secure │ └── ArduinoMKRGSM1400Secure.ino ├── ArduinoMKRNB1500 │ └── ArduinoMKRNB1500.ino ├── ArduinoWiFi101 │ └── ArduinoWiFi101.ino ├── ArduinoWiFi101Secure │ └── ArduinoWiFi101Secure.ino ├── ArduinoWiFiShield │ └── ArduinoWiFiShield.ino ├── ArduinoYun │ └── ArduinoYun.ino ├── ArduinoYunSecure │ └── ArduinoYunSecure.ino ├── ESP32DevelopmentBoard │ └── ESP32DevelopmentBoard.ino └── ESP32DevelopmentBoardSecure │ └── ESP32DevelopmentBoardSecure.ino ├── library.properties └── src ├── MQTT.h ├── MQTTClient.cpp ├── MQTTClient.h └── lwmqtt ├── client.c ├── helpers.c ├── helpers.h ├── lwmqtt.h ├── packet.c ├── packet.h └── string.c /.editorconfig: -------------------------------------------------------------------------------- 1 | [Makefile] 2 | indent_style = tab 3 | indent_size = 4 4 | 5 | [src/*.h,src/*.cpp,examples/**.ino] 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v3 9 | - name: Append Path 10 | run: echo "$HOME/.local/bin" >> $GITHUB_PATH 11 | - name: Test 12 | run: | 13 | # ensure bin directory 14 | mkdir -p "$HOME/.local/bin" 15 | 16 | # install arduino-cli into "$HOME/.local/bin" 17 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR="$HOME/.local/bin" sh 18 | 19 | # prepare config 20 | arduino-cli config init 21 | arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json 22 | arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 23 | 24 | # install 25 | make install 26 | 27 | # link library 28 | mkdir -p ~/Arduino/libraries 29 | ln -s $PWD ~/Arduino/libraries/. 30 | 31 | # build examples 32 | make build 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | cmake-build-debug/ 3 | examples/*/debug.cfg 4 | examples/*/debug_custom.json 5 | examples/*/esp32.svd 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Uncompilable CMake File to enable project editing with CLion IDE 2 | 3 | cmake_minimum_required(VERSION 3.13) 4 | project(arduino-mqtt) 5 | 6 | include_directories( 7 | /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino 8 | /Users/256dpi/Documents/Arduino/libraries/Ethernet/src 9 | /Users/256dpi/Documents/Arduino/libraries/WiFi101/src 10 | /Users/256dpi/Documents/Arduino/libraries/MKRGSM/src 11 | /Users/256dpi/Documents/Arduino/libraries/MKRNB/src 12 | /Applications/Arduino.app/Contents/Java/libraries/Bridge/src 13 | /Users/256dpi/Library/Arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/ESP8266WiFi/src 14 | /Users/256dpi/Library/Arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFi/src 15 | /Users/256dpi/Library/Arduino15/packages/esp32/hardware/esp32/2.0.11/libraries/WiFiClientSecure/src 16 | src) 17 | 18 | include_directories(src/) 19 | 20 | set(CMAKE_CXX_FLAGS -std=c++11) 21 | 22 | set(SOURCE_FILES 23 | examples/AdafruitHuzzahESP8266/AdafruitHuzzahESP8266.ino 24 | examples/AdafruitHuzzahESP8266Secure/AdafruitHuzzahESP8266Secure.ino 25 | examples/ArduinoEthernetShield/ArduinoEthernetShield.ino 26 | examples/ArduinoMKRGSM1400/ArduinoMKRGSM1400.ino 27 | examples/ArduinoMKRGSM1400Secure/ArduinoMKRGSM1400Secure.ino 28 | examples/ArduinoMKRNB1500/ArduinoMKRNB1500.ino 29 | examples/ArduinoWiFi101/ArduinoWiFi101.ino 30 | examples/ArduinoWiFi101Secure/ArduinoWiFi101Secure.ino 31 | examples/ArduinoWiFiShield/ArduinoWiFiShield.ino 32 | examples/ArduinoYun/ArduinoYun.ino 33 | examples/ArduinoYunSecure/ArduinoYunSecure.ino 34 | examples/ESP32DevelopmentBoard/ESP32DevelopmentBoard.ino 35 | examples/ESP32DevelopmentBoardSecure/ESP32DevelopmentBoardSecure.ino 36 | src/lwmqtt 37 | src/MQTT.h 38 | src/MQTTClient.h 39 | src/MQTTClient.cpp) 40 | 41 | add_executable(arduino-mqtt ${SOURCE_FILES}) 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Joël Gähwiler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: fmt 2 | 3 | fmt: 4 | clang-format -i src/*.h src/*.cpp -style="{BasedOnStyle: Google, ColumnLimit: 120}" 5 | 6 | update: 7 | rm -rf ./lwmqtt 8 | git clone --branch v0.9.0 https://github.com/256dpi/lwmqtt.git ./lwmqtt 9 | mkdir -p ./src/lwmqtt 10 | cp -r ./lwmqtt/src/*.c ./src/lwmqtt/ 11 | cp -r ./lwmqtt/src/*.h ./src/lwmqtt/ 12 | cp -r ./lwmqtt/include/*.h ./src/lwmqtt/ 13 | rm ./src/lwmqtt/posix.c 14 | rm -rf ./lwmqtt 15 | sed -i '' "s//\"lwmqtt.h\"/g" ./src/lwmqtt/* 16 | 17 | install: 18 | # expects arduino-cli to be installed and configured with esp8266 and esp32 cores 19 | arduino-cli update 20 | # ensure cores 21 | arduino-cli core install esp8266:esp8266 22 | arduino-cli core install esp32:esp32 23 | arduino-cli core install arduino:samd 24 | arduino-cli core install arduino:avr 25 | # ensure libraries 26 | arduino-cli lib install WiFi 27 | arduino-cli lib install WiFi101 28 | arduino-cli lib install MKRGSM 29 | arduino-cli lib install MKRNB 30 | arduino-cli lib install Ethernet 31 | arduino-cli lib install Bridge 32 | 33 | test: 34 | # expects repository to be linked to libraries 35 | arduino-cli compile --fqbn "esp32:esp32:esp32:FlashFreq=80" ./examples/ESP32DevelopmentBoard 36 | 37 | build: 38 | # expects repository to be linked to libraries 39 | arduino-cli compile --fqbn "esp8266:esp8266:huzzah:eesz=4M3M,xtal=80" ./examples/AdafruitHuzzahESP8266 40 | arduino-cli compile --fqbn "esp8266:esp8266:huzzah:eesz=4M3M,xtal=80" ./examples/AdafruitHuzzahESP8266Secure 41 | arduino-cli compile --fqbn "arduino:avr:uno" ./examples/ArduinoEthernetShield 42 | arduino-cli compile --fqbn "arduino:samd:mkrgsm1400" ./examples/ArduinoMKRGSM1400 43 | arduino-cli compile --fqbn "arduino:samd:mkrgsm1400" ./examples/ArduinoMKRGSM1400Secure 44 | arduino-cli compile --fqbn "arduino:samd:mkrnb1500" ./examples/ArduinoMKRNB1500 45 | arduino-cli compile --fqbn "arduino:avr:uno" ./examples/ArduinoWiFi101 46 | arduino-cli compile --fqbn "arduino:avr:uno" ./examples/ArduinoWiFi101Secure 47 | arduino-cli compile --fqbn "arduino:avr:uno" ./examples/ArduinoWiFiShield 48 | arduino-cli compile --fqbn "arduino:avr:yun" ./examples/ArduinoYun 49 | arduino-cli compile --fqbn "arduino:avr:yun" ./examples/ArduinoYunSecure 50 | arduino-cli compile --fqbn "esp32:esp32:esp32:FlashFreq=80" ./examples/ESP32DevelopmentBoard 51 | arduino-cli compile --fqbn "esp32:esp32:esp32:FlashFreq=80" ./examples/ESP32DevelopmentBoardSecure 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arduino-mqtt 2 | 3 | [![Test](https://github.com/256dpi/arduino-mqtt/actions/workflows/test.yml/badge.svg)](https://github.com/256dpi/arduino-mqtt/actions/workflows/test.yml) 4 | [![GitHub release](https://img.shields.io/github/release/256dpi/arduino-mqtt.svg)](https://github.com/256dpi/arduino-mqtt/releases) 5 | 6 | This library bundles the [lwmqtt](https://github.com/256dpi/lwmqtt) MQTT 3.1.1 client and adds a thin wrapper to get an Arduino like API. 7 | 8 | Download the latest version from the [release](https://github.com/256dpi/arduino-mqtt/releases) section. Or even better use the built-in Library Manager in the Arduino IDE and search for "lwmqtt". 9 | 10 | The library is also available on [PlatformIO](https://platformio.org/lib/show/617/MQTT). You can install it by running: `pio lib install "256dpi/MQTT"`. 11 | 12 | ## Compatibility 13 | 14 | The following examples show how you can use the library with various Arduino compatible hardware: 15 | 16 | - [Arduino Yun & Yun-Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoYun/ArduinoYun.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoYunSecure/ArduinoYunSecure.ino)) 17 | - [Arduino Ethernet Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoEthernetShield/ArduinoEthernetShield.ino) 18 | - [Arduino WiFi Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFiShield/ArduinoWiFiShield.ino) 19 | - [Adafruit HUZZAH ESP8266](https://github.com/256dpi/arduino-mqtt/blob/master/examples/AdafruitHuzzahESP8266/AdafruitHuzzahESP8266.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/AdafruitHuzzahESP8266Secure/AdafruitHuzzahESP8266Secure.ino)) 20 | - [Arduino WiFi101 Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFi101/ArduinoWiFi101.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFi101Secure/ArduinoWiFi101Secure.ino)) 21 | - [Arduino MKR GSM 1400](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoMKRGSM1400/ArduinoMKRGSM1400.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoMKRGSM1400Secure/ArduinoMKRGSM1400Secure.ino)) 22 | - [Arduino MKR NB 1500](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoMKRNB1500/ArduinoMKRNB1500.ino) 23 | - [ESP32 Development Board](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ESP32DevelopmentBoard/ESP32DevelopmentBoard.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ESP32DevelopmentBoardSecure/ESP32DevelopmentBoardSecure.ino)) 24 | 25 | Other shields and boards should also work if they provide a [Client](https://www.arduino.cc/en/Reference/ClientConstructor) based network implementation. 26 | 27 | **Check out the [Wiki](https://github.com/256dpi/arduino-mqtt/wiki) to find more examples.** 28 | 29 | ## Notes 30 | 31 | - The maximum size for packets being published and received is set by default to 128 bytes. To change the buffer sizes, you need to use `MQTTClient client(256)` or `MQTTClient client(256, 512)` instead of just `MQTTClient client` at the top of your sketch. A single value denotes both the read and write buffer size, two values specify them separately. **Beginning with version 2.5.2, the message payload is sent directly during publishing. Therefore, the write buffer is only needed to encode the packet header and topic, for which the default 128 bytes should be enough. However, the receiving of messages is still fully constrained by the read buffer, which may be increased if necessary.** 32 | 33 | - On the ESP8266 it has been reported that an additional `delay(10);` after `client.loop();` fixes many stability issues with WiFi connections. 34 | 35 | - To use the library with shiftr.io, you need to provide the instance name (username) and token secret (password) as the second and third argument to `client.connect(client_id, username, password)`. 36 | 37 | ## Example 38 | 39 | The following example uses an Arduino MKR1000 to connect to the public shiftr.io instance. You can check on your device after a successful connection here: https://www.shiftr.io/try. 40 | 41 | ```c++ 42 | #include 43 | #include 44 | #include 45 | 46 | const char ssid[] = "ssid"; 47 | const char pass[] = "pass"; 48 | 49 | WiFiClient net; 50 | MQTTClient client; 51 | 52 | unsigned long lastMillis = 0; 53 | 54 | void connect() { 55 | Serial.print("checking wifi..."); 56 | while (WiFi.status() != WL_CONNECTED) { 57 | Serial.print("."); 58 | delay(1000); 59 | } 60 | 61 | Serial.print("\nconnecting..."); 62 | while (!client.connect("arduino", "public", "public")) { 63 | Serial.print("."); 64 | delay(1000); 65 | } 66 | 67 | Serial.println("\nconnected!"); 68 | 69 | client.subscribe("/hello"); 70 | // client.unsubscribe("/hello"); 71 | } 72 | 73 | void messageReceived(String &topic, String &payload) { 74 | Serial.println("incoming: " + topic + " - " + payload); 75 | 76 | // Note: Do not use the client in the callback to publish, subscribe or 77 | // unsubscribe as it may cause deadlocks when other things arrive while 78 | // sending and receiving acknowledgments. Instead, change a global variable, 79 | // or push to a queue and handle it in the loop after calling `client.loop()`. 80 | } 81 | 82 | void setup() { 83 | Serial.begin(115200); 84 | WiFi.begin(ssid, pass); 85 | 86 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 87 | // by Arduino. You need to set the IP address directly. 88 | client.begin("public.cloud.shiftr.io", net); 89 | client.onMessage(messageReceived); 90 | 91 | connect(); 92 | } 93 | 94 | void loop() { 95 | client.loop(); 96 | 97 | if (!client.connected()) { 98 | connect(); 99 | } 100 | 101 | // publish a message roughly every second. 102 | if (millis() - lastMillis > 1000) { 103 | lastMillis = millis(); 104 | client.publish("/hello", "world"); 105 | } 106 | } 107 | ``` 108 | 109 | ## API 110 | 111 | Create the object with: 112 | 113 | ```c++ 114 | MQTTClient() 115 | MQTTClient(int bufSize) 116 | MQTTClient(int readBufSize, int writeBufSize) 117 | ``` 118 | 119 | - `MQTTClient` has two buffers. One for read and one for write. Default buffer size is 128 bytes. In summary are 256 bytes are used for buffers. 120 | - The `bufSize` option sets `readBufSize` and `writeBufSize` to the same value. 121 | 122 | Initialize the object using the hostname of the broker, the brokers port (default: `1883`) and the underlying Client class for network transport: 123 | 124 | ```c++ 125 | void begin(Client &client); 126 | void begin(const char hostname[], Client &client); 127 | void begin(const char hostname[], int port, Client &client); 128 | void begin(IPAddress address, Client &client); 129 | void begin(IPAddress address, int port, Client &client); 130 | ``` 131 | 132 | - Specify port `8883` when using secure clients for encrypted connections. 133 | - Local domain names (e.g. `Computer.local` on OSX) are not supported by Arduino. You need to set the IP address directly. 134 | 135 | The hostname and port can also be changed after calling `begin()`: 136 | 137 | ```c++ 138 | void setHost(const char hostname[]); 139 | void setHost(const char hostname[], int port); 140 | void setHost(IPAddress address); 141 | void setHost(IPAddress address, int port); 142 | ``` 143 | 144 | Set a will message (last testament) that gets registered on the broker after connecting. `setWill()` has to be called before calling `connect()`: 145 | 146 | ```c++ 147 | void setWill(const char topic[]); 148 | void setWill(const char topic[], const char payload[]); 149 | void setWill(const char topic[], const char payload[], bool retained, int qos); 150 | void clearWill(); 151 | ``` 152 | 153 | Register a callback to receive messages: 154 | 155 | ```c++ 156 | void onMessage(MQTTClientCallbackSimple); 157 | // Callback signature: void messageReceived(String &topic, String &payload) {} 158 | 159 | void onMessage(MQTTClientCallbackSimpleFunction cb); 160 | // Callback signature: std::function 161 | 162 | void onMessageAdvanced(MQTTClientCallbackAdvanced); 163 | // Callback signature: void messageReceived(MQTTClient *client, char topic[], char bytes[], int length) {} 164 | 165 | void onMessageAdvanced(MQTTClientCallbackAdvancedFunction cb); 166 | // Callback signature: std::function 167 | ``` 168 | 169 | - The set callback is mostly called during a call to `loop()` but may also be called during a call to `subscribe()`, `unsubscribe()` or `publish() // QoS > 0` if messages have been received before receiving the required acknowledgement. Therefore, it is strongly recommended to not call `subscribe()`, `unsubscribe()` or `publish() // QoS > 0` directly in the callback. 170 | - In case you need a reference to an object that manages the client, use the `void * ref` property on the client to store a pointer, and access it directly from the advanced callback. 171 | - If the platform supports `` you can directly register a function wrapper. 172 | 173 | Set more advanced options: 174 | 175 | ```c++ 176 | void setKeepAlive(int keepAlive); 177 | void setCleanSession(bool cleanSession); 178 | void setTimeout(int timeout); 179 | void setOptions(int keepAlive, bool cleanSession, int timeout); 180 | ``` 181 | 182 | - The `keepAlive` option controls the keep alive interval in seconds (default: 10). 183 | - The `cleanSession` option controls the session retention on the broker side (default: true). 184 | - The `timeout` option controls the default timeout for all commands in milliseconds (default: 1000). 185 | 186 | Set a custom clock source "custom millis" callback to enable deep sleep applications: 187 | 188 | ```c++ 189 | void setClockSource(MQTTClientClockSource); 190 | // Callback signature: uint32_t clockSource() {} 191 | ``` 192 | 193 | - The specified callback is used by the internal timers to get a monotonic time in milliseconds. Since the clock source for the built-in `millis` is stopped when the Arduino goes into deep sleep, you need to provide a custom callback that first syncs with a built-in or external Real Time Clock (RTC). You can pass `NULL` to reset to the default implementation. 194 | 195 | Connect to broker using the supplied client ID and an optional username and password: 196 | 197 | ```c++ 198 | bool connect(const char clientID[], bool skip = false); 199 | bool connect(const char clientID[], const char username[], bool skip = false); 200 | bool connect(const char clientID[], const char username[], const char password[], bool skip = false); 201 | ``` 202 | 203 | - If `password` is present but `username` is absent, the client will fall back to an empty username. 204 | - If the `skip` option is set to true, the client will skip the network level connection and jump to the MQTT level connection. This option can be used in order to establish and verify TLS connections manually before giving control to the MQTT client. 205 | - The functions return a boolean that indicates if the connection has been established successfully (true). 206 | 207 | Publish a message to the broker with an optional payload, which can be a string or binary: 208 | 209 | ```c++ 210 | bool publish(const String &topic); 211 | bool publish(const char topic[]); 212 | bool publish(const String &topic, const String &payload); 213 | bool publish(const String &topic, const String &payload, bool retained, int qos); 214 | bool publish(const char topic[], const String &payload); 215 | bool publish(const char topic[], const String &payload, bool retained, int qos); 216 | bool publish(const char topic[], const char payload[]); 217 | bool publish(const char topic[], const char payload[], bool retained, int qos); 218 | bool publish(const char topic[], const char payload[], int length); 219 | bool publish(const char topic[], const char payload[], int length, bool retained, int qos); 220 | ``` 221 | 222 | - Beginning with version 2.5.2, payloads of arbitrary length may be published, see [Notes](#notes). 223 | - The functions return a boolean that indicates if the publishing has been successful (true). 224 | 225 | Obtain the last used packet ID and prepare the publication of a duplicate message using the specified packet ID: 226 | 227 | ```c++ 228 | uint16_t lastPacketID(); 229 | void prepareDuplicate(uint16_t packetID); 230 | ``` 231 | 232 | - These functions may be used to implement a retry logic for failed publications of QoS1 and QoS2 messages. 233 | - The `lastPacketID()` function can be used after calling `publish()` to obtain the used packet ID. 234 | - The `prepareDuplicate()` function may be called before `publish()` to temporarily change the next used packet ID and flag the message as a duplicate. 235 | 236 | Subscribe to a topic: 237 | 238 | ```c++ 239 | bool subscribe(const String &topic); 240 | bool subscribe(const String &topic, int qos); 241 | bool subscribe(const char topic[]); 242 | bool subscribe(const char topic[], int qos); 243 | ``` 244 | 245 | - The functions return a boolean that indicates if the subscription has been successful (true). 246 | 247 | Unsubscribe from a topic: 248 | 249 | ```c++ 250 | bool unsubscribe(const String &topic); 251 | bool unsubscribe(const char topic[]); 252 | ``` 253 | 254 | - The functions return a boolean that indicates if the unsubscription has been successful (true). 255 | 256 | Sends and receives packets: 257 | 258 | ```c++ 259 | bool loop(); 260 | ``` 261 | 262 | - This function should be called in every `loop`. 263 | - The function returns a boolean that indicates if the loop has been successful (true). 264 | 265 | Check if the client is currently connected: 266 | 267 | ```c++ 268 | bool connected(); 269 | ``` 270 | 271 | Check whether a session was present at the time of the last connect: 272 | 273 | ```c++ 274 | bool sessionPresent(); 275 | ``` 276 | 277 | Configure dropping of overflowing messages (exceeding read buffer) and checking the count of dropped messages: 278 | 279 | ```c++ 280 | void dropOverflow(bool enabled); 281 | uint32_t droppedMessages(); 282 | ``` 283 | 284 | Access low-level information for debugging: 285 | 286 | ```c++ 287 | lwmqtt_err_t lastError(); 288 | lwmqtt_return_code_t returnCode(); 289 | ``` 290 | 291 | - The error codes can be found [here](https://github.com/256dpi/lwmqtt/blob/master/include/lwmqtt.h#L15). 292 | - The return codes can be found [here](https://github.com/256dpi/lwmqtt/blob/master/include/lwmqtt.h#L260). 293 | 294 | Disconnect from the broker: 295 | 296 | ```c++ 297 | bool disconnect(); 298 | ``` 299 | 300 | - The function returns a boolean that indicates if the disconnect has been successful (true). 301 | 302 | ## Release Management 303 | 304 | - Update version in `library.properties`. 305 | - Create release on GitHub. 306 | -------------------------------------------------------------------------------- /examples/AdafruitHuzzahESP8266/AdafruitHuzzahESP8266.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Adafruit Huzzah ESP8266 2 | // to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char ssid[] = "ssid"; 14 | const char pass[] = "pass"; 15 | 16 | WiFiClient net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("checking wifi..."); 23 | while (WiFi.status() != WL_CONNECTED) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | Serial.print("\nconnecting..."); 29 | while (!client.connect("arduino", "public", "public")) { 30 | Serial.print("."); 31 | delay(1000); 32 | } 33 | 34 | Serial.println("\nconnected!"); 35 | 36 | client.subscribe("/hello"); 37 | // client.unsubscribe("/hello"); 38 | } 39 | 40 | void messageReceived(String &topic, String &payload) { 41 | Serial.println("incoming: " + topic + " - " + payload); 42 | 43 | // Note: Do not use the client in the callback to publish, subscribe or 44 | // unsubscribe as it may cause deadlocks when other things arrive while 45 | // sending and receiving acknowledgments. Instead, change a global variable, 46 | // or push to a queue and handle it in the loop after calling `client.loop()`. 47 | } 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | WiFi.begin(ssid, pass); 52 | 53 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 54 | // by Arduino. You need to set the IP address directly. 55 | client.begin("public.cloud.shiftr.io", net); 56 | client.onMessage(messageReceived); 57 | 58 | connect(); 59 | } 60 | 61 | void loop() { 62 | client.loop(); 63 | delay(10); // <- fixes some issues with WiFi stability 64 | 65 | if (!client.connected()) { 66 | connect(); 67 | } 68 | 69 | // publish a message roughly every second. 70 | if (millis() - lastMillis > 1000) { 71 | lastMillis = millis(); 72 | client.publish("/hello", "world"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/AdafruitHuzzahESP8266Secure/AdafruitHuzzahESP8266Secure.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Adafruit Huzzah ESP8266 2 | // to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char ssid[] = "ssid"; 14 | const char pass[] = "pass"; 15 | 16 | WiFiClientSecure net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("checking wifi..."); 23 | while (WiFi.status() != WL_CONNECTED) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | // do not verify tls certificate 29 | // check the following example for methods to verify the server: 30 | // https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/BearSSL_Validation/BearSSL_Validation.ino 31 | net.setInsecure(); 32 | 33 | Serial.print("\nconnecting..."); 34 | while (!client.connect("arduino", "public", "public")) { 35 | Serial.print("."); 36 | delay(1000); 37 | } 38 | 39 | Serial.println("\nconnected!"); 40 | 41 | client.subscribe("/hello"); 42 | // client.unsubscribe("/hello"); 43 | } 44 | 45 | void messageReceived(String &topic, String &payload) { 46 | Serial.println("incoming: " + topic + " - " + payload); 47 | 48 | // Note: Do not use the client in the callback to publish, subscribe or 49 | // unsubscribe as it may cause deadlocks when other things arrive while 50 | // sending and receiving acknowledgments. Instead, change a global variable, 51 | // or push to a queue and handle it in the loop after calling `client.loop()`. 52 | } 53 | 54 | void setup() { 55 | Serial.begin(115200); 56 | WiFi.begin(ssid, pass); 57 | 58 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 59 | // by Arduino. You need to set the IP address directly. 60 | // 61 | // MQTT brokers usually use port 8883 for secure connections. 62 | client.begin("public.cloud.shiftr.io", 8883, net); 63 | client.onMessage(messageReceived); 64 | 65 | connect(); 66 | } 67 | 68 | void loop() { 69 | client.loop(); 70 | delay(10); // <- fixes some issues with WiFi stability 71 | 72 | if (!client.connected()) { 73 | connect(); 74 | } 75 | 76 | // publish a message roughly every second. 77 | if (millis() - lastMillis > 1000) { 78 | lastMillis = millis(); 79 | client.publish("/hello", "world"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/ArduinoEthernetShield/ArduinoEthernetShield.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino Uno together with 2 | // an Ethernet Shield to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; 14 | byte ip[] = {192, 168, 1, 177}; // <- change to match your network 15 | 16 | EthernetClient net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("connecting..."); 23 | while (!client.connect("arduino", "public", "public")) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | Serial.println("\nconnected!"); 29 | 30 | client.subscribe("/hello"); 31 | // client.unsubscribe("/hello"); 32 | } 33 | 34 | void messageReceived(String &topic, String &payload) { 35 | Serial.println("incoming: " + topic + " - " + payload); 36 | 37 | // Note: Do not use the client in the callback to publish, subscribe or 38 | // unsubscribe as it may cause deadlocks when other things arrive while 39 | // sending and receiving acknowledgments. Instead, change a global variable, 40 | // or push to a queue and handle it in the loop after calling `client.loop()`. 41 | } 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | Ethernet.begin(mac, ip); 46 | 47 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 48 | // by Arduino. You need to set the IP address directly. 49 | client.begin("public.cloud.shiftr.io", net); 50 | client.onMessage(messageReceived); 51 | 52 | connect(); 53 | } 54 | 55 | void loop() { 56 | client.loop(); 57 | 58 | if (!client.connected()) { 59 | connect(); 60 | } 61 | 62 | // publish a message roughly every second. 63 | if (millis() - lastMillis > 1000) { 64 | lastMillis = millis(); 65 | client.publish("/hello", "world"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/ArduinoMKRGSM1400/ArduinoMKRGSM1400.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino MKR GSM 1400 board 2 | // to connect to shiftr.io. 3 | // 4 | // IMPORTANT: This example uses the new MKRGSM library. 5 | // 6 | // You can check on your device after a successful 7 | // connection here: https://www.shiftr.io/try. 8 | // 9 | // by Sandeep Mistry 10 | // https://github.com/256dpi/arduino-mqtt 11 | 12 | #include 13 | #include 14 | 15 | const char pin[] = ""; 16 | const char apn[] = "apn"; 17 | const char login[] = "login"; 18 | const char password[] = "password"; 19 | 20 | GSMClient net; 21 | GPRS gprs; 22 | GSM gsmAccess; 23 | MQTTClient client; 24 | 25 | unsigned long lastMillis = 0; 26 | 27 | void connect() { 28 | // connection state 29 | bool connected = false; 30 | 31 | Serial.print("connecting to cellular network ..."); 32 | 33 | // After starting the modem with gsmAccess.begin() 34 | // attach to the GPRS network with the APN, login and password 35 | while (!connected) { 36 | if ((gsmAccess.begin(pin) == GSM_READY) && 37 | (gprs.attachGPRS(apn, login, password) == GPRS_READY)) { 38 | connected = true; 39 | } else { 40 | Serial.print("."); 41 | delay(1000); 42 | } 43 | } 44 | 45 | Serial.print("\nconnecting..."); 46 | while (!client.connect("arduino", "public", "public")) { 47 | Serial.print("."); 48 | delay(1000); 49 | } 50 | 51 | Serial.println("\nconnected!"); 52 | 53 | client.subscribe("/hello"); 54 | // client.unsubscribe("/hello"); 55 | } 56 | 57 | void messageReceived(String &topic, String &payload) { 58 | Serial.println("incoming: " + topic + " - " + payload); 59 | 60 | // Note: Do not use the client in the callback to publish, subscribe or 61 | // unsubscribe as it may cause deadlocks when other things arrive while 62 | // sending and receiving acknowledgments. Instead, change a global variable, 63 | // or push to a queue and handle it in the loop after calling `client.loop()`. 64 | } 65 | 66 | void setup() { 67 | Serial.begin(115200); 68 | 69 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 70 | // by Arduino. You need to set the IP address directly. 71 | client.begin("public.cloud.shiftr.io", net); 72 | client.onMessage(messageReceived); 73 | 74 | connect(); 75 | } 76 | 77 | void loop() { 78 | client.loop(); 79 | 80 | if (!client.connected()) { 81 | connect(); 82 | } 83 | 84 | // publish a message roughly every second. 85 | if (millis() - lastMillis > 1000) { 86 | lastMillis = millis(); 87 | client.publish("/hello", "world"); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/ArduinoMKRGSM1400Secure/ArduinoMKRGSM1400Secure.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino MKR GSM 1400 board 2 | // to securely connect to shiftr.io. 3 | // 4 | // IMPORTANT: This example uses the new MKRGSM library. 5 | // 6 | // You can check on your device after a successful 7 | // connection here: https://www.shiftr.io/try. 8 | // 9 | // by Sandeep Mistry 10 | // https://github.com/256dpi/arduino-mqtt 11 | 12 | #include 13 | #include 14 | 15 | const char pin[] = ""; 16 | const char apn[] = "apn"; 17 | const char login[] = "login"; 18 | const char password[] = "password"; 19 | 20 | GSMSSLClient net; 21 | GPRS gprs; 22 | GSM gsmAccess; 23 | MQTTClient client; 24 | 25 | unsigned long lastMillis = 0; 26 | 27 | void connect() { 28 | // connection state 29 | bool connected = false; 30 | 31 | Serial.print("connecting to cellular network ..."); 32 | 33 | // After starting the modem with gsmAccess.begin() 34 | // attach to the GPRS network with the APN, login and password 35 | while (!connected) { 36 | if ((gsmAccess.begin(pin) == GSM_READY) && 37 | (gprs.attachGPRS(apn, login, password) == GPRS_READY)) { 38 | connected = true; 39 | } else { 40 | Serial.print("."); 41 | delay(1000); 42 | } 43 | } 44 | 45 | Serial.print("\nconnecting..."); 46 | while (!client.connect("arduino", "public", "public")) { 47 | Serial.print("."); 48 | delay(1000); 49 | } 50 | 51 | Serial.println("\nconnected!"); 52 | 53 | client.subscribe("/hello"); 54 | // client.unsubscribe("/hello"); 55 | } 56 | 57 | void messageReceived(String &topic, String &payload) { 58 | Serial.println("incoming: " + topic + " - " + payload); 59 | 60 | // Note: Do not use the client in the callback to publish, subscribe or 61 | // unsubscribe as it may cause deadlocks when other things arrive while 62 | // sending and receiving acknowledgments. Instead, change a global variable, 63 | // or push to a queue and handle it in the loop after calling `client.loop()`. 64 | } 65 | 66 | void setup() { 67 | Serial.begin(115200); 68 | 69 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 70 | // by Arduino. You need to set the IP address directly. 71 | // 72 | // MQTT brokers usually use port 8883 for secure connections. 73 | client.begin("public.cloud.shiftr.io", 8883, net); 74 | client.onMessage(messageReceived); 75 | 76 | connect(); 77 | } 78 | 79 | void loop() { 80 | client.loop(); 81 | 82 | if (!client.connected()) { 83 | connect(); 84 | } 85 | 86 | // publish a message roughly every second. 87 | if (millis() - lastMillis > 1000) { 88 | lastMillis = millis(); 89 | client.publish("/hello", "world"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/ArduinoMKRNB1500/ArduinoMKRNB1500.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino MKR NB 1500 board 2 | // to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Max Kriegleder 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char pin[] = ""; 14 | const char apn[] = "apn"; 15 | const char login[] = "login"; 16 | const char password[] = "password"; 17 | 18 | NBClient net; 19 | NB nbAccess; 20 | MQTTClient client; 21 | 22 | unsigned long lastMillis = 0; 23 | 24 | void connect() { 25 | // connection state 26 | bool connected = false; 27 | 28 | Serial.print("connecting to cellular network ..."); 29 | 30 | while (!connected) { 31 | if ((nbAccess.begin(pin, apn, login, password) == NB_READY)) { 32 | connected = true; 33 | } else { 34 | Serial.print("."); 35 | delay(1000); 36 | } 37 | } 38 | 39 | Serial.print("\nconnecting..."); 40 | while (!client.connect("arduino", "public", "public")) { 41 | Serial.print("."); 42 | delay(1000); 43 | } 44 | 45 | Serial.println("\nconnected!"); 46 | 47 | client.subscribe("/hello"); 48 | // client.unsubscribe("/hello"); 49 | } 50 | 51 | void messageReceived(String &topic, String &payload) { 52 | Serial.println("incoming: " + topic + " - " + payload); 53 | 54 | // Note: Do not use the client in the callback to publish, subscribe or 55 | // unsubscribe as it may cause deadlocks when other things arrive while 56 | // sending and receiving acknowledgments. Instead, change a global variable, 57 | // or push to a queue and handle it in the loop after calling `client.loop()`. 58 | } 59 | 60 | void setup() { 61 | Serial.begin(115200); 62 | 63 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 64 | // by Arduino. You need to set the IP address directly. 65 | client.begin("public.cloud.shiftr.io", net); 66 | client.onMessage(messageReceived); 67 | 68 | connect(); 69 | } 70 | 71 | void loop() { 72 | client.loop(); 73 | 74 | if (!client.connected()) { 75 | connect(); 76 | } 77 | 78 | // publish a message roughly every second. 79 | if (millis() - lastMillis > 1000) { 80 | lastMillis = millis(); 81 | client.publish("/hello", "world"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/ArduinoWiFi101/ArduinoWiFi101.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino/Genuino Zero together with 2 | // a WiFi101 Shield or a MKR1000 to connect to shiftr.io. 3 | // 4 | // IMPORTANT: This example uses the new WiFi101 library. 5 | // 6 | // You can check on your device after a successful 7 | // connection here: https://www.shiftr.io/try. 8 | // 9 | // by Gilberto Conti 10 | // https://github.com/256dpi/arduino-mqtt 11 | 12 | #include 13 | #include 14 | 15 | const char ssid[] = "ssid"; 16 | const char pass[] = "pass"; 17 | 18 | WiFiClient net; 19 | MQTTClient client; 20 | 21 | unsigned long lastMillis = 0; 22 | 23 | void connect() { 24 | Serial.print("checking wifi..."); 25 | while (WiFi.status() != WL_CONNECTED) { 26 | Serial.print("."); 27 | delay(1000); 28 | } 29 | 30 | Serial.print("\nconnecting..."); 31 | while (!client.connect("arduino", "public", "public")) { 32 | Serial.print("."); 33 | delay(1000); 34 | } 35 | 36 | Serial.println("\nconnected!"); 37 | 38 | client.subscribe("/hello"); 39 | // client.unsubscribe("/hello"); 40 | } 41 | 42 | void messageReceived(String &topic, String &payload) { 43 | Serial.println("incoming: " + topic + " - " + payload); 44 | 45 | // Note: Do not use the client in the callback to publish, subscribe or 46 | // unsubscribe as it may cause deadlocks when other things arrive while 47 | // sending and receiving acknowledgments. Instead, change a global variable, 48 | // or push to a queue and handle it in the loop after calling `client.loop()`. 49 | } 50 | 51 | void setup() { 52 | Serial.begin(115200); 53 | WiFi.begin(ssid, pass); 54 | 55 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 56 | // by Arduino. You need to set the IP address directly. 57 | client.begin("public.cloud.shiftr.io", net); 58 | client.onMessage(messageReceived); 59 | 60 | connect(); 61 | } 62 | 63 | void loop() { 64 | client.loop(); 65 | 66 | if (!client.connected()) { 67 | connect(); 68 | } 69 | 70 | // publish a message roughly every second. 71 | if (millis() - lastMillis > 1000) { 72 | lastMillis = millis(); 73 | client.publish("/hello", "world"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/ArduinoWiFi101Secure/ArduinoWiFi101Secure.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino/Genuino Zero together with 2 | // a WiFi101 Shield or a MKR1000 to connect to shiftr.io. 3 | // 4 | // IMPORTANT: This example uses the new WiFi101 library. 5 | // 6 | // IMPORTANT: You need to install/update the SSL certificates first: 7 | // https://github.com/arduino-libraries/WiFi101-FirmwareUpdater#to-update-ssl-certificates 8 | // 9 | // You can check on your device after a successful 10 | // connection here: https://www.shiftr.io/try. 11 | // 12 | // by Gilberto Conti 13 | // https://github.com/256dpi/arduino-mqtt 14 | 15 | #include 16 | #include 17 | 18 | const char ssid[] = "ssid"; 19 | const char pass[] = "pass"; 20 | 21 | WiFiSSLClient net; 22 | MQTTClient client; 23 | 24 | unsigned long lastMillis = 0; 25 | 26 | void connect() { 27 | Serial.print("checking wifi..."); 28 | while (WiFi.status() != WL_CONNECTED) { 29 | Serial.print("."); 30 | delay(1000); 31 | } 32 | 33 | Serial.print("\nconnecting..."); 34 | while (!client.connect("arduino", "public", "public")) { 35 | Serial.print("."); 36 | delay(1000); 37 | } 38 | 39 | Serial.println("\nconnected!"); 40 | 41 | client.subscribe("/hello"); 42 | // client.unsubscribe("/hello"); 43 | } 44 | 45 | void messageReceived(String &topic, String &payload) { 46 | Serial.println("incoming: " + topic + " - " + payload); 47 | 48 | // Note: Do not use the client in the callback to publish, subscribe or 49 | // unsubscribe as it may cause deadlocks when other things arrive while 50 | // sending and receiving acknowledgments. Instead, change a global variable, 51 | // or push to a queue and handle it in the loop after calling `client.loop()`. 52 | } 53 | 54 | void setup() { 55 | Serial.begin(115200); 56 | WiFi.begin(ssid, pass); 57 | 58 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 59 | // by Arduino. You need to set the IP address directly. 60 | // 61 | // MQTT brokers usually use port 8883 for secure connections. 62 | client.begin("public.cloud.shiftr.io", 8883, net); 63 | client.onMessage(messageReceived); 64 | 65 | connect(); 66 | } 67 | 68 | void loop() { 69 | client.loop(); 70 | 71 | if (!client.connected()) { 72 | connect(); 73 | } 74 | 75 | // publish a message roughly every second. 76 | if (millis() - lastMillis > 1000) { 77 | lastMillis = millis(); 78 | client.publish("/hello", "world"); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /examples/ArduinoWiFiShield/ArduinoWiFiShield.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino Uno together with 2 | // a WiFi Shield to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char ssid[] = "ssid"; 14 | const char pass[] = "pass"; 15 | 16 | WiFiClient net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("checking wifi..."); 23 | while (WiFi.status() != WL_CONNECTED) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | Serial.print("\nconnecting..."); 29 | while (!client.connect("arduino", "public", "public")) { 30 | Serial.print("."); 31 | delay(1000); 32 | } 33 | 34 | Serial.println("\nconnected!"); 35 | 36 | client.subscribe("/hello"); 37 | // client.unsubscribe("/hello"); 38 | } 39 | 40 | void messageReceived(String &topic, String &payload) { 41 | Serial.println("incoming: " + topic + " - " + payload); 42 | 43 | // Note: Do not use the client in the callback to publish, subscribe or 44 | // unsubscribe as it may cause deadlocks when other things arrive while 45 | // sending and receiving acknowledgments. Instead, change a global variable, 46 | // or push to a queue and handle it in the loop after calling `client.loop()`. 47 | } 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | WiFi.begin(ssid, pass); 52 | 53 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 54 | // by Arduino. You need to set the IP address directly. 55 | client.begin("public.cloud.shiftr.io", net); 56 | client.onMessage(messageReceived); 57 | 58 | connect(); 59 | } 60 | 61 | void loop() { 62 | client.loop(); 63 | 64 | if (!client.connected()) { 65 | connect(); 66 | } 67 | 68 | // publish a message roughly every second. 69 | if (millis() - lastMillis > 1000) { 70 | lastMillis = millis(); 71 | client.publish("/hello", "world"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/ArduinoYun/ArduinoYun.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino Yun or a Yun-Shield 2 | // and the MQTTClient to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | BridgeClient net; 15 | MQTTClient client; 16 | 17 | unsigned long lastMillis = 0; 18 | 19 | void connect() { 20 | Serial.print("connecting..."); 21 | while (!client.connect("arduino", "public", "public")) { 22 | Serial.print("."); 23 | delay(1000); 24 | } 25 | 26 | Serial.println("\nconnected!"); 27 | 28 | client.subscribe("/hello"); 29 | // client.unsubscribe("/hello"); 30 | } 31 | 32 | void messageReceived(String &topic, String &payload) { 33 | Serial.println("incoming: " + topic + " - " + payload); 34 | 35 | // Note: Do not use the client in the callback to publish, subscribe or 36 | // unsubscribe as it may cause deadlocks when other things arrive while 37 | // sending and receiving acknowledgments. Instead, change a global variable, 38 | // or push to a queue and handle it in the loop after calling `client.loop()`. 39 | } 40 | 41 | void setup() { 42 | Bridge.begin(); 43 | Serial.begin(115200); 44 | 45 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 46 | // by Arduino. You need to set the IP address directly. 47 | client.begin("public.cloud.shiftr.io", net); 48 | client.onMessage(messageReceived); 49 | 50 | connect(); 51 | } 52 | 53 | void loop() { 54 | client.loop(); 55 | 56 | if (!client.connected()) { 57 | connect(); 58 | } 59 | 60 | // publish a message roughly every second. 61 | if (millis() - lastMillis > 1000) { 62 | lastMillis = millis(); 63 | client.publish("/hello", "world"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/ArduinoYunSecure/ArduinoYunSecure.ino: -------------------------------------------------------------------------------- 1 | // This example uses an Arduino Yun or a Yun-Shield 2 | // and the MQTTClient to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | BridgeSSLClient net; 15 | MQTTClient client; 16 | 17 | unsigned long lastMillis = 0; 18 | 19 | void connect() { 20 | Serial.print("connecting..."); 21 | while (!client.connect("arduino", "public", "public")) { 22 | Serial.print("."); 23 | delay(1000); 24 | } 25 | 26 | Serial.println("\nconnected!"); 27 | 28 | client.subscribe("/hello"); 29 | // client.unsubscribe("/hello"); 30 | } 31 | 32 | void messageReceived(String &topic, String &payload) { 33 | Serial.println("incoming: " + topic + " - " + payload); 34 | 35 | // Note: Do not use the client in the callback to publish, subscribe or 36 | // unsubscribe as it may cause deadlocks when other things arrive while 37 | // sending and receiving acknowledgments. Instead, change a global variable, 38 | // or push to a queue and handle it in the loop after calling `client.loop()`. 39 | } 40 | 41 | void setup() { 42 | Bridge.begin(); 43 | Serial.begin(115200); 44 | 45 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 46 | // by Arduino. You need to set the IP address directly. 47 | // 48 | // MQTT brokers usually use port 8883 for secure connections. 49 | client.begin("public.cloud.shiftr.io", 8883, net); 50 | client.onMessage(messageReceived); 51 | 52 | connect(); 53 | } 54 | 55 | void loop() { 56 | client.loop(); 57 | 58 | if (!client.connected()) { 59 | connect(); 60 | } 61 | 62 | // publish a message roughly every second. 63 | if (millis() - lastMillis > 1000) { 64 | lastMillis = millis(); 65 | client.publish("/hello", "world"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/ESP32DevelopmentBoard/ESP32DevelopmentBoard.ino: -------------------------------------------------------------------------------- 1 | // This example uses an ESP32 Development Board 2 | // to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char ssid[] = "ssid"; 14 | const char pass[] = "pass"; 15 | 16 | WiFiClient net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("checking wifi..."); 23 | while (WiFi.status() != WL_CONNECTED) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | Serial.print("\nconnecting..."); 29 | while (!client.connect("arduino", "public", "public")) { 30 | Serial.print("."); 31 | delay(1000); 32 | } 33 | 34 | Serial.println("\nconnected!"); 35 | 36 | client.subscribe("/hello"); 37 | // client.unsubscribe("/hello"); 38 | } 39 | 40 | void messageReceived(String &topic, String &payload) { 41 | Serial.println("incoming: " + topic + " - " + payload); 42 | 43 | // Note: Do not use the client in the callback to publish, subscribe or 44 | // unsubscribe as it may cause deadlocks when other things arrive while 45 | // sending and receiving acknowledgments. Instead, change a global variable, 46 | // or push to a queue and handle it in the loop after calling `client.loop()`. 47 | } 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | WiFi.begin(ssid, pass); 52 | 53 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 54 | // by Arduino. You need to set the IP address directly. 55 | client.begin("public.cloud.shiftr.io", net); 56 | client.onMessage(messageReceived); 57 | 58 | connect(); 59 | } 60 | 61 | void loop() { 62 | client.loop(); 63 | delay(10); // <- fixes some issues with WiFi stability 64 | 65 | if (!client.connected()) { 66 | connect(); 67 | } 68 | 69 | // publish a message roughly every second. 70 | if (millis() - lastMillis > 1000) { 71 | lastMillis = millis(); 72 | client.publish("/hello", "world"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/ESP32DevelopmentBoardSecure/ESP32DevelopmentBoardSecure.ino: -------------------------------------------------------------------------------- 1 | // This example uses an ESP32 Development Board 2 | // to connect to shiftr.io. 3 | // 4 | // You can check on your device after a successful 5 | // connection here: https://www.shiftr.io/try. 6 | // 7 | // by Joël Gähwiler 8 | // https://github.com/256dpi/arduino-mqtt 9 | 10 | #include 11 | #include 12 | 13 | const char ssid[] = "ssid"; 14 | const char pass[] = "pass"; 15 | 16 | WiFiClientSecure net; 17 | MQTTClient client; 18 | 19 | unsigned long lastMillis = 0; 20 | 21 | void connect() { 22 | Serial.print("checking wifi..."); 23 | while (WiFi.status() != WL_CONNECTED) { 24 | Serial.print("."); 25 | delay(1000); 26 | } 27 | 28 | Serial.print("\nconnecting..."); 29 | // do not verify tls certificate 30 | // check the following example for methods to verify the server: 31 | // https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFiClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino 32 | net.setInsecure(); 33 | while (!client.connect("arduino", "public", "public")) { 34 | Serial.print("."); 35 | delay(1000); 36 | } 37 | 38 | Serial.println("\nconnected!"); 39 | 40 | client.subscribe("/hello"); 41 | // client.unsubscribe("/hello"); 42 | } 43 | 44 | void messageReceived(String &topic, String &payload) { 45 | Serial.println("incoming: " + topic + " - " + payload); 46 | 47 | // Note: Do not use the client in the callback to publish, subscribe or 48 | // unsubscribe as it may cause deadlocks when other things arrive while 49 | // sending and receiving acknowledgments. Instead, change a global variable, 50 | // or push to a queue and handle it in the loop after calling `client.loop()`. 51 | } 52 | 53 | void setup() { 54 | Serial.begin(115200); 55 | WiFi.begin(ssid, pass); 56 | 57 | // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported 58 | // by Arduino. You need to set the IP address directly. 59 | // 60 | // MQTT brokers usually use port 8883 for secure connections. 61 | client.begin("public.cloud.shiftr.io", 8883, net); 62 | client.onMessage(messageReceived); 63 | 64 | connect(); 65 | } 66 | 67 | void loop() { 68 | client.loop(); 69 | delay(10); // <- fixes some issues with WiFi stability 70 | 71 | if (!client.connected()) { 72 | connect(); 73 | } 74 | 75 | // publish a message roughly every second. 76 | if (millis() - lastMillis > 1000) { 77 | lastMillis = millis(); 78 | client.publish("/hello", "world"); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MQTT 2 | version=2.5.2 3 | author=Joel Gaehwiler 4 | maintainer=Joel Gaehwiler 5 | sentence=MQTT library for Arduino 6 | paragraph=This library bundles the lwmqtt client and adds a thin wrapper to get an Arduino like API. 7 | category=Communication 8 | url=https://github.com/256dpi/arduino-mqtt 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/MQTT.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_H 2 | #define MQTT_H 3 | 4 | #include "MQTTClient.h" 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/MQTTClient.cpp: -------------------------------------------------------------------------------- 1 | #include "MQTTClient.h" 2 | 3 | inline void lwmqtt_arduino_timer_set(void *ref, uint32_t timeout) { 4 | // cast timer reference 5 | auto t = (lwmqtt_arduino_timer_t *)ref; 6 | 7 | // set timeout 8 | t->timeout = timeout; 9 | 10 | // set start 11 | if (t->millis != nullptr) { 12 | t->start = t->millis(); 13 | } else { 14 | t->start = millis(); 15 | } 16 | } 17 | 18 | inline int32_t lwmqtt_arduino_timer_get(void *ref) { 19 | // cast timer reference 20 | auto t = (lwmqtt_arduino_timer_t *)ref; 21 | 22 | // get now 23 | uint32_t now; 24 | if (t->millis != nullptr) { 25 | now = t->millis(); 26 | } else { 27 | now = millis(); 28 | } 29 | 30 | // get difference (account for roll-overs) 31 | uint32_t diff; 32 | if (now < t->start) { 33 | diff = UINT32_MAX - t->start + now; 34 | } else { 35 | diff = now - t->start; 36 | } 37 | 38 | // return relative time 39 | if (diff > t->timeout) { 40 | return -(diff - t->timeout); 41 | } else { 42 | return t->timeout - diff; 43 | } 44 | } 45 | 46 | inline lwmqtt_err_t lwmqtt_arduino_network_read(void *ref, uint8_t *buffer, size_t len, size_t *read, 47 | uint32_t timeout) { 48 | // cast network reference 49 | auto n = (lwmqtt_arduino_network_t *)ref; 50 | 51 | // set timeout 52 | uint32_t start = millis(); 53 | 54 | // reset counter 55 | *read = 0; 56 | 57 | // read until all bytes have been read or timeout has been reached 58 | while (len > 0 && (millis() - start < timeout)) { 59 | // read from connection 60 | int r = n->client->read(buffer, len); 61 | 62 | // handle read data 63 | if (r > 0) { 64 | buffer += r; 65 | *read += r; 66 | len -= r; 67 | continue; 68 | } 69 | 70 | // wait/unblock for some time (RTOS based boards may otherwise fail since 71 | // the wifi task cannot provide the data) 72 | delay(1); 73 | 74 | // otherwise check status 75 | if (!n->client->connected()) { 76 | return LWMQTT_NETWORK_FAILED_READ; 77 | } 78 | } 79 | 80 | // check counter 81 | if (*read == 0) { 82 | return LWMQTT_NETWORK_TIMEOUT; 83 | } 84 | 85 | return LWMQTT_SUCCESS; 86 | } 87 | 88 | inline lwmqtt_err_t lwmqtt_arduino_network_write(void *ref, uint8_t *buffer, size_t len, size_t *sent, 89 | uint32_t /*timeout*/) { 90 | // cast network reference 91 | auto n = (lwmqtt_arduino_network_t *)ref; 92 | 93 | // write bytes 94 | *sent = n->client->write(buffer, len); 95 | if (*sent <= 0) { 96 | return LWMQTT_NETWORK_FAILED_WRITE; 97 | } 98 | 99 | return LWMQTT_SUCCESS; 100 | } 101 | 102 | static void MQTTClientHandler(lwmqtt_client_t * /*client*/, void *ref, lwmqtt_string_t topic, 103 | lwmqtt_message_t message) { 104 | // get callback 105 | auto cb = (MQTTClientCallback *)ref; 106 | 107 | // null terminate topic 108 | char terminated_topic[topic.len + 1]; 109 | memcpy(terminated_topic, topic.data, topic.len); 110 | terminated_topic[topic.len] = '\0'; 111 | 112 | // null terminate payload if available 113 | if (message.payload != nullptr) { 114 | message.payload[message.payload_len] = '\0'; 115 | } 116 | 117 | // call the advanced callback and return if available 118 | if (cb->advanced != nullptr) { 119 | cb->advanced(cb->client, terminated_topic, (char *)message.payload, (int)message.payload_len); 120 | return; 121 | } 122 | #if MQTT_HAS_FUNCTIONAL 123 | if (cb->functionAdvanced != nullptr) { 124 | cb->functionAdvanced(cb->client, terminated_topic, (char *)message.payload, (int)message.payload_len); 125 | return; 126 | } 127 | #endif 128 | 129 | // return if simple callback is not set 130 | #if MQTT_HAS_FUNCTIONAL 131 | if (cb->simple == nullptr && cb->functionSimple == nullptr) { 132 | return; 133 | } 134 | #else 135 | if (cb->simple == nullptr) { 136 | return; 137 | } 138 | #endif 139 | 140 | // create topic string 141 | String str_topic = String(terminated_topic); 142 | 143 | // create payload string 144 | String str_payload; 145 | if (message.payload != nullptr) { 146 | str_payload = String((const char *)message.payload); 147 | } 148 | 149 | // call simple callback 150 | #if MQTT_HAS_FUNCTIONAL 151 | if (cb->functionSimple != nullptr) { 152 | cb->functionSimple(str_topic, str_payload); 153 | } else { 154 | cb->simple(str_topic, str_payload); 155 | } 156 | #else 157 | cb->simple(str_topic, str_payload); 158 | #endif 159 | } 160 | 161 | MQTTClient::MQTTClient(int readBufSize, int writeBufSize) { 162 | // allocate buffers 163 | this->readBufSize = (size_t)readBufSize; 164 | this->writeBufSize = (size_t)writeBufSize; 165 | this->readBuf = (uint8_t *)malloc((size_t)readBufSize + 1); 166 | this->writeBuf = (uint8_t *)malloc((size_t)writeBufSize); 167 | } 168 | 169 | MQTTClient::~MQTTClient() { 170 | // free will 171 | this->clearWill(); 172 | 173 | // free hostname 174 | if (this->hostname != nullptr) { 175 | free((void *)this->hostname); 176 | } 177 | 178 | // free buffers 179 | free(this->readBuf); 180 | free(this->writeBuf); 181 | } 182 | 183 | void MQTTClient::begin(Client &_client) { 184 | // set client 185 | this->netClient = &_client; 186 | 187 | // initialize client 188 | lwmqtt_init(&this->client, this->writeBuf, this->writeBufSize, this->readBuf, this->readBufSize); 189 | 190 | // set timers 191 | lwmqtt_set_timers(&this->client, &this->timer1, &this->timer2, lwmqtt_arduino_timer_set, lwmqtt_arduino_timer_get); 192 | 193 | // set network 194 | lwmqtt_set_network(&this->client, &this->network, lwmqtt_arduino_network_read, lwmqtt_arduino_network_write); 195 | 196 | // set callback 197 | lwmqtt_set_callback(&this->client, (void *)&this->callback, MQTTClientHandler); 198 | } 199 | 200 | void MQTTClient::onMessage(MQTTClientCallbackSimple cb) { 201 | // set callback 202 | this->callback.client = this; 203 | this->callback.simple = cb; 204 | this->callback.advanced = nullptr; 205 | #if MQTT_HAS_FUNCTIONAL 206 | this->callback.functionSimple = nullptr; 207 | this->callback.functionAdvanced = nullptr; 208 | #endif 209 | } 210 | 211 | void MQTTClient::onMessageAdvanced(MQTTClientCallbackAdvanced cb) { 212 | // set callback 213 | this->callback.client = this; 214 | this->callback.simple = nullptr; 215 | this->callback.advanced = cb; 216 | #if MQTT_HAS_FUNCTIONAL 217 | this->callback.functionSimple = nullptr; 218 | this->callback.functionAdvanced = nullptr; 219 | #endif 220 | } 221 | 222 | #if MQTT_HAS_FUNCTIONAL 223 | void MQTTClient::onMessage(MQTTClientCallbackSimpleFunction cb) { 224 | // set callback 225 | this->callback.client = this; 226 | this->callback.simple = nullptr; 227 | this->callback.functionSimple = cb; 228 | this->callback.advanced = nullptr; 229 | this->callback.functionAdvanced = nullptr; 230 | } 231 | 232 | void MQTTClient::onMessageAdvanced(MQTTClientCallbackAdvancedFunction cb) { 233 | // set callback 234 | this->callback.client = this; 235 | this->callback.simple = nullptr; 236 | this->callback.functionSimple = nullptr; 237 | this->callback.advanced = nullptr; 238 | this->callback.functionAdvanced = cb; 239 | } 240 | #endif 241 | 242 | void MQTTClient::setClockSource(MQTTClientClockSource cb) { 243 | this->timer1.millis = cb; 244 | this->timer2.millis = cb; 245 | } 246 | 247 | void MQTTClient::setHost(IPAddress _address, int _port) { 248 | // set address and port 249 | this->address = _address; 250 | this->port = _port; 251 | } 252 | 253 | void MQTTClient::setHost(const char _hostname[], int _port) { 254 | // free hostname if set 255 | if (this->hostname != nullptr) { 256 | free((void *)this->hostname); 257 | } 258 | 259 | // set hostname and port 260 | this->hostname = strdup(_hostname); 261 | this->port = _port; 262 | } 263 | 264 | void MQTTClient::setWill(const char topic[], const char payload[], bool retained, int qos) { 265 | // return if topic is missing 266 | if (topic == nullptr || strlen(topic) == 0) { 267 | return; 268 | } 269 | 270 | // clear existing will 271 | this->clearWill(); 272 | 273 | // allocate will 274 | this->will = (lwmqtt_will_t *)malloc(sizeof(lwmqtt_will_t)); 275 | memset(this->will, 0, sizeof(lwmqtt_will_t)); 276 | 277 | // set topic 278 | this->will->topic = lwmqtt_string(strdup(topic)); 279 | 280 | // set payload if available 281 | if (payload != nullptr && strlen(payload) > 0) { 282 | this->will->payload = lwmqtt_string(strdup(payload)); 283 | } 284 | 285 | // set flags 286 | this->will->retained = retained; 287 | this->will->qos = (lwmqtt_qos_t)qos; 288 | } 289 | 290 | void MQTTClient::clearWill() { 291 | // return if not set 292 | if (this->will == nullptr) { 293 | return; 294 | } 295 | 296 | // free payload if set 297 | if (this->will->payload.len > 0) { 298 | free(this->will->payload.data); 299 | } 300 | 301 | // free topic if set 302 | if (this->will->topic.len > 0) { 303 | free(this->will->topic.data); 304 | } 305 | 306 | // free will 307 | free(this->will); 308 | this->will = nullptr; 309 | } 310 | 311 | void MQTTClient::setKeepAlive(int _keepAlive) { this->keepAlive = _keepAlive; } 312 | 313 | void MQTTClient::setCleanSession(bool _cleanSession) { this->cleanSession = _cleanSession; } 314 | 315 | void MQTTClient::setTimeout(int _timeout) { this->timeout = _timeout; } 316 | 317 | void MQTTClient::dropOverflow(bool enabled) { 318 | // configure drop overflow 319 | lwmqtt_drop_overflow(&this->client, enabled, &this->_droppedMessages); 320 | } 321 | 322 | bool MQTTClient::connect(const char clientID[], const char username[], const char password[], bool skip) { 323 | // close left open connection if still connected 324 | if (!skip && this->connected()) { 325 | this->close(); 326 | } 327 | 328 | // save client 329 | this->network.client = this->netClient; 330 | 331 | // connect to host 332 | if (!skip) { 333 | int ret; 334 | if (this->hostname != nullptr) { 335 | ret = this->netClient->connect(this->hostname, (uint16_t)this->port); 336 | } else { 337 | ret = this->netClient->connect(this->address, (uint16_t)this->port); 338 | } 339 | if (ret <= 0) { 340 | this->_lastError = LWMQTT_NETWORK_FAILED_CONNECT; 341 | return false; 342 | } 343 | } 344 | 345 | // prepare options 346 | lwmqtt_connect_options_t options = lwmqtt_default_connect_options; 347 | options.keep_alive = this->keepAlive; 348 | options.clean_session = this->cleanSession; 349 | options.client_id = lwmqtt_string(clientID); 350 | 351 | // set username and password if available 352 | if (username != nullptr) { 353 | options.username = lwmqtt_string(username); 354 | } 355 | if (password != nullptr) { 356 | options.password = lwmqtt_string(password); 357 | } 358 | 359 | // connect to broker 360 | this->_lastError = lwmqtt_connect(&this->client, &options, this->will, this->timeout); 361 | 362 | // copy return code 363 | this->_returnCode = options.return_code; 364 | 365 | // handle error 366 | if (this->_lastError != LWMQTT_SUCCESS) { 367 | // close connection 368 | this->close(); 369 | 370 | return false; 371 | } 372 | 373 | // copy session present flag 374 | this->_sessionPresent = options.session_present; 375 | 376 | // set flag 377 | this->_connected = true; 378 | 379 | return true; 380 | } 381 | 382 | bool MQTTClient::publish(const char topic[], const char payload[], int length, bool retained, int qos) { 383 | // return immediately if not connected 384 | if (!this->connected()) { 385 | return false; 386 | } 387 | 388 | // prepare message 389 | lwmqtt_message_t message = lwmqtt_default_message; 390 | message.payload = (uint8_t *)payload; 391 | message.payload_len = (size_t)length; 392 | message.retained = retained; 393 | message.qos = lwmqtt_qos_t(qos); 394 | 395 | // prepare options 396 | lwmqtt_publish_options_t options = lwmqtt_default_publish_options; 397 | 398 | // set duplicate packet id if available 399 | if (this->nextDupPacketID > 0) { 400 | options.dup_id = &this->nextDupPacketID; 401 | this->nextDupPacketID = 0; 402 | } 403 | 404 | // publish message 405 | this->_lastError = lwmqtt_publish(&this->client, &options, lwmqtt_string(topic), message, this->timeout); 406 | if (this->_lastError != LWMQTT_SUCCESS) { 407 | // close connection 408 | this->close(); 409 | 410 | return false; 411 | } 412 | 413 | return true; 414 | } 415 | 416 | uint16_t MQTTClient::lastPacketID() { 417 | // get last packet id from client 418 | return this->client.last_packet_id; 419 | } 420 | 421 | void MQTTClient::prepareDuplicate(uint16_t packetID) { 422 | // set next duplicate packet id 423 | this->nextDupPacketID = packetID; 424 | } 425 | 426 | bool MQTTClient::subscribe(const char topic[], int qos) { 427 | // return immediately if not connected 428 | if (!this->connected()) { 429 | return false; 430 | } 431 | 432 | // subscribe to topic 433 | this->_lastError = lwmqtt_subscribe_one(&this->client, lwmqtt_string(topic), (lwmqtt_qos_t)qos, this->timeout); 434 | if (this->_lastError != LWMQTT_SUCCESS) { 435 | // close connection 436 | this->close(); 437 | 438 | return false; 439 | } 440 | 441 | return true; 442 | } 443 | 444 | bool MQTTClient::unsubscribe(const char topic[]) { 445 | // return immediately if not connected 446 | if (!this->connected()) { 447 | return false; 448 | } 449 | 450 | // unsubscribe from topic 451 | this->_lastError = lwmqtt_unsubscribe_one(&this->client, lwmqtt_string(topic), this->timeout); 452 | if (this->_lastError != LWMQTT_SUCCESS) { 453 | // close connection 454 | this->close(); 455 | 456 | return false; 457 | } 458 | 459 | return true; 460 | } 461 | 462 | bool MQTTClient::loop() { 463 | // return immediately if not connected 464 | if (!this->connected()) { 465 | return false; 466 | } 467 | 468 | // get available bytes on the network 469 | int available = this->netClient->available(); 470 | 471 | // yield if data is available 472 | if (available > 0) { 473 | this->_lastError = lwmqtt_yield(&this->client, available, this->timeout); 474 | if (this->_lastError != LWMQTT_SUCCESS) { 475 | // close connection 476 | this->close(); 477 | 478 | return false; 479 | } 480 | } 481 | 482 | // keep the connection alive 483 | this->_lastError = lwmqtt_keep_alive(&this->client, this->timeout); 484 | if (this->_lastError != LWMQTT_SUCCESS) { 485 | // close connection 486 | this->close(); 487 | 488 | return false; 489 | } 490 | 491 | return true; 492 | } 493 | 494 | bool MQTTClient::connected() { 495 | // a client is connected if the network is connected, a client is available and 496 | // the connection has been properly initiated 497 | return this->netClient != nullptr && this->netClient->connected() == 1 && this->_connected; 498 | } 499 | 500 | bool MQTTClient::disconnect() { 501 | // return immediately if not connected anymore 502 | if (!this->connected()) { 503 | return false; 504 | } 505 | 506 | // cleanly disconnect 507 | this->_lastError = lwmqtt_disconnect(&this->client, this->timeout); 508 | 509 | // close 510 | this->close(); 511 | 512 | return this->_lastError == LWMQTT_SUCCESS; 513 | } 514 | 515 | void MQTTClient::close() { 516 | // set flag 517 | this->_connected = false; 518 | 519 | // close network 520 | this->netClient->stop(); 521 | } 522 | -------------------------------------------------------------------------------- /src/MQTTClient.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_CLIENT_H 2 | #define MQTT_CLIENT_H 3 | 4 | // include functional API if possible. remove min and max macros for some 5 | // platforms as they will be defined again by Arduino later 6 | #if defined(ESP8266) || (defined ESP32) 7 | #include 8 | #define MQTT_HAS_FUNCTIONAL 1 9 | #elif defined(__has_include) 10 | #if __has_include() 11 | #if defined(min) 12 | #undef min 13 | #endif 14 | #if defined(max) 15 | #undef max 16 | #endif 17 | #include 18 | #define MQTT_HAS_FUNCTIONAL 1 19 | #else 20 | #define MQTT_HAS_FUNCTIONAL 0 21 | #endif 22 | #else 23 | #define MQTT_HAS_FUNCTIONAL 0 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | extern "C" { 31 | #include "lwmqtt/lwmqtt.h" 32 | } 33 | 34 | typedef uint32_t (*MQTTClientClockSource)(); 35 | 36 | typedef struct { 37 | uint32_t start; 38 | uint32_t timeout; 39 | MQTTClientClockSource millis; 40 | } lwmqtt_arduino_timer_t; 41 | 42 | typedef struct { 43 | Client *client; 44 | } lwmqtt_arduino_network_t; 45 | 46 | class MQTTClient; 47 | 48 | typedef void (*MQTTClientCallbackSimple)(String &topic, String &payload); 49 | typedef void (*MQTTClientCallbackAdvanced)(MQTTClient *client, char topic[], char bytes[], int length); 50 | #if MQTT_HAS_FUNCTIONAL 51 | typedef std::function MQTTClientCallbackSimpleFunction; 52 | typedef std::function 53 | MQTTClientCallbackAdvancedFunction; 54 | #endif 55 | 56 | typedef struct { 57 | MQTTClient *client = nullptr; 58 | MQTTClientCallbackSimple simple = nullptr; 59 | MQTTClientCallbackAdvanced advanced = nullptr; 60 | #if MQTT_HAS_FUNCTIONAL 61 | MQTTClientCallbackSimpleFunction functionSimple = nullptr; 62 | MQTTClientCallbackAdvancedFunction functionAdvanced = nullptr; 63 | #endif 64 | } MQTTClientCallback; 65 | 66 | class MQTTClient { 67 | private: 68 | size_t readBufSize = 0; 69 | size_t writeBufSize = 0; 70 | uint8_t *readBuf = nullptr; 71 | uint8_t *writeBuf = nullptr; 72 | 73 | uint16_t keepAlive = 10; 74 | bool cleanSession = true; 75 | uint32_t timeout = 1000; 76 | bool _sessionPresent = false; 77 | 78 | Client *netClient = nullptr; 79 | const char *hostname = nullptr; 80 | IPAddress address; 81 | int port = 0; 82 | lwmqtt_will_t *will = nullptr; 83 | MQTTClientCallback callback; 84 | 85 | lwmqtt_arduino_network_t network = {nullptr}; 86 | lwmqtt_arduino_timer_t timer1 = {0, 0, nullptr}; 87 | lwmqtt_arduino_timer_t timer2 = {0, 0, nullptr}; 88 | lwmqtt_client_t client = lwmqtt_client_t(); 89 | 90 | bool _connected = false; 91 | uint16_t nextDupPacketID = 0; 92 | lwmqtt_return_code_t _returnCode = (lwmqtt_return_code_t)0; 93 | lwmqtt_err_t _lastError = (lwmqtt_err_t)0; 94 | uint32_t _droppedMessages = 0; 95 | 96 | public: 97 | void *ref = nullptr; 98 | 99 | explicit MQTTClient(int bufSize = 128) : MQTTClient(bufSize, bufSize) {} 100 | MQTTClient(int readBufSize, int writeBufSize); 101 | 102 | ~MQTTClient(); 103 | 104 | void begin(Client &_client); 105 | void begin(const char _hostname[], Client &_client) { this->begin(_hostname, 1883, _client); } 106 | void begin(const char _hostname[], int _port, Client &_client) { 107 | this->begin(_client); 108 | this->setHost(_hostname, _port); 109 | } 110 | void begin(IPAddress _address, Client &_client) { this->begin(_address, 1883, _client); } 111 | void begin(IPAddress _address, int _port, Client &_client) { 112 | this->begin(_client); 113 | this->setHost(_address, _port); 114 | } 115 | 116 | void onMessage(MQTTClientCallbackSimple cb); 117 | void onMessageAdvanced(MQTTClientCallbackAdvanced cb); 118 | #if MQTT_HAS_FUNCTIONAL 119 | void onMessage(MQTTClientCallbackSimpleFunction cb); 120 | void onMessageAdvanced(MQTTClientCallbackAdvancedFunction cb); 121 | #endif 122 | 123 | void setClockSource(MQTTClientClockSource cb); 124 | 125 | void setHost(const char _hostname[]) { this->setHost(_hostname, 1883); } 126 | void setHost(const char hostname[], int port); 127 | void setHost(IPAddress _address) { this->setHost(_address, 1883); } 128 | void setHost(IPAddress _address, int port); 129 | 130 | void setWill(const char topic[]) { this->setWill(topic, ""); } 131 | void setWill(const char topic[], const char payload[]) { this->setWill(topic, payload, false, 0); } 132 | void setWill(const char topic[], const char payload[], bool retained, int qos); 133 | void clearWill(); 134 | 135 | void setKeepAlive(int keepAlive); 136 | void setCleanSession(bool cleanSession); 137 | void setTimeout(int timeout); 138 | void setOptions(int _keepAlive, bool _cleanSession, int _timeout) { 139 | this->setKeepAlive(_keepAlive); 140 | this->setCleanSession(_cleanSession); 141 | this->setTimeout(_timeout); 142 | } 143 | 144 | void dropOverflow(bool enabled); 145 | uint32_t droppedMessages() { return this->_droppedMessages; } 146 | 147 | bool connect(const char clientId[], bool skip = false) { return this->connect(clientId, nullptr, nullptr, skip); } 148 | bool connect(const char clientId[], const char username[], bool skip = false) { 149 | return this->connect(clientId, username, nullptr, skip); 150 | } 151 | bool connect(const char clientID[], const char username[], const char password[], bool skip = false); 152 | 153 | bool publish(const String &topic) { return this->publish(topic.c_str(), ""); } 154 | bool publish(const char topic[]) { return this->publish(topic, ""); } 155 | bool publish(const String &topic, const String &payload) { return this->publish(topic.c_str(), payload.c_str()); } 156 | bool publish(const String &topic, const String &payload, bool retained, int qos) { 157 | return this->publish(topic.c_str(), payload.c_str(), retained, qos); 158 | } 159 | bool publish(const char topic[], const String &payload) { return this->publish(topic, payload.c_str()); } 160 | bool publish(const char topic[], const String &payload, bool retained, int qos) { 161 | return this->publish(topic, payload.c_str(), retained, qos); 162 | } 163 | bool publish(const char topic[], const char payload[]) { 164 | return this->publish(topic, (char *)payload, (int)strlen(payload)); 165 | } 166 | bool publish(const char topic[], const char payload[], bool retained, int qos) { 167 | return this->publish(topic, (char *)payload, (int)strlen(payload), retained, qos); 168 | } 169 | bool publish(const char topic[], const char payload[], int length) { 170 | return this->publish(topic, payload, length, false, 0); 171 | } 172 | bool publish(const char topic[], const char payload[], int length, bool retained, int qos); 173 | 174 | uint16_t lastPacketID(); 175 | void prepareDuplicate(uint16_t packetID); 176 | 177 | bool subscribe(const String &topic) { return this->subscribe(topic.c_str()); } 178 | bool subscribe(const String &topic, int qos) { return this->subscribe(topic.c_str(), qos); } 179 | bool subscribe(const char topic[]) { return this->subscribe(topic, 0); } 180 | bool subscribe(const char topic[], int qos); 181 | 182 | bool unsubscribe(const String &topic) { return this->unsubscribe(topic.c_str()); } 183 | bool unsubscribe(const char topic[]); 184 | 185 | bool loop(); 186 | bool connected(); 187 | bool sessionPresent() { return this->_sessionPresent; } 188 | 189 | lwmqtt_err_t lastError() { return this->_lastError; } 190 | lwmqtt_return_code_t returnCode() { return this->_returnCode; } 191 | 192 | bool disconnect(); 193 | 194 | private: 195 | void close(); 196 | }; 197 | 198 | #endif 199 | -------------------------------------------------------------------------------- /src/lwmqtt/client.c: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | 3 | void lwmqtt_init(lwmqtt_client_t *client, uint8_t *write_buf, size_t write_buf_size, uint8_t *read_buf, 4 | size_t read_buf_size) { 5 | client->last_packet_id = 1; 6 | client->keep_alive_interval = 0; 7 | client->pong_pending = false; 8 | 9 | client->write_buf = write_buf; 10 | client->write_buf_size = write_buf_size; 11 | client->read_buf = read_buf; 12 | client->read_buf_size = read_buf_size; 13 | 14 | client->callback = NULL; 15 | client->callback_ref = NULL; 16 | 17 | client->network = NULL; 18 | client->network_read = NULL; 19 | client->network_write = NULL; 20 | 21 | client->keep_alive_timer = NULL; 22 | client->command_timer = NULL; 23 | client->timer_set = NULL; 24 | client->timer_get = NULL; 25 | 26 | client->drop_overflow = false; 27 | client->overflow_counter = NULL; 28 | } 29 | 30 | void lwmqtt_set_network(lwmqtt_client_t *client, void *ref, lwmqtt_network_read_t read, lwmqtt_network_write_t write) { 31 | client->network = ref; 32 | client->network_read = read; 33 | client->network_write = write; 34 | } 35 | 36 | void lwmqtt_set_timers(lwmqtt_client_t *client, void *keep_alive_timer, void *command_timer, lwmqtt_timer_set_t set, 37 | lwmqtt_timer_get_t get) { 38 | client->keep_alive_timer = keep_alive_timer; 39 | client->command_timer = command_timer; 40 | client->timer_set = set; 41 | client->timer_get = get; 42 | 43 | client->timer_set(client->keep_alive_timer, 0); 44 | client->timer_set(client->command_timer, 0); 45 | } 46 | 47 | void lwmqtt_set_callback(lwmqtt_client_t *client, void *ref, lwmqtt_callback_t cb) { 48 | client->callback_ref = ref; 49 | client->callback = cb; 50 | } 51 | 52 | void lwmqtt_drop_overflow(lwmqtt_client_t *client, bool enabled, uint32_t *counter) { 53 | client->drop_overflow = enabled; 54 | client->overflow_counter = counter; 55 | } 56 | 57 | static uint16_t lwmqtt_get_next_packet_id(lwmqtt_client_t *client) { 58 | // check overflow 59 | if (client->last_packet_id == 65535) { 60 | client->last_packet_id = 1; 61 | return 1; 62 | } 63 | 64 | // increment packet id 65 | client->last_packet_id++; 66 | 67 | return client->last_packet_id; 68 | } 69 | 70 | static lwmqtt_err_t lwmqtt_read_from_network(lwmqtt_client_t *client, size_t offset, size_t len) { 71 | // check read buffer capacity 72 | if (client->read_buf_size < offset + len) { 73 | return LWMQTT_BUFFER_TOO_SHORT; 74 | } 75 | 76 | // prepare counter 77 | size_t read = 0; 78 | 79 | // read while data is missing 80 | while (read < len) { 81 | // check remaining time 82 | int32_t remaining_time = client->timer_get(client->command_timer); 83 | if (remaining_time <= 0) { 84 | return LWMQTT_NETWORK_TIMEOUT; 85 | } 86 | 87 | // read 88 | size_t partial_read = 0; 89 | lwmqtt_err_t err = client->network_read(client->network, client->read_buf + offset + read, len - read, 90 | &partial_read, (uint32_t)remaining_time); 91 | if (err != LWMQTT_SUCCESS) { 92 | return err; 93 | } 94 | 95 | // increment counter 96 | read += partial_read; 97 | } 98 | 99 | return LWMQTT_SUCCESS; 100 | } 101 | 102 | static lwmqtt_err_t lwmqtt_drain_network(lwmqtt_client_t *client, size_t amount) { 103 | // read while data is left 104 | while (amount > 0) { 105 | // check remaining time 106 | int32_t remaining_time = client->timer_get(client->command_timer); 107 | if (remaining_time <= 0) { 108 | return LWMQTT_NETWORK_TIMEOUT; 109 | } 110 | 111 | // get max read 112 | size_t max_read = amount; 113 | if (max_read > client->read_buf_size) { 114 | max_read = client->read_buf_size; 115 | } 116 | 117 | // read 118 | size_t partial_read = 0; 119 | lwmqtt_err_t err = 120 | client->network_read(client->network, client->read_buf, max_read, &partial_read, (uint32_t)remaining_time); 121 | if (err != LWMQTT_SUCCESS) { 122 | return err; 123 | } 124 | 125 | // decrement counter 126 | amount -= partial_read; 127 | } 128 | 129 | return LWMQTT_SUCCESS; 130 | } 131 | 132 | static lwmqtt_err_t lwmqtt_write_to_network(lwmqtt_client_t *client, uint8_t *buf, size_t len) { 133 | // prepare counter 134 | size_t written = 0; 135 | 136 | // write while data is left 137 | while (written < len) { 138 | // check remaining time 139 | int32_t remaining_time = client->timer_get(client->command_timer); 140 | if (remaining_time <= 0) { 141 | return LWMQTT_NETWORK_TIMEOUT; 142 | } 143 | 144 | // write 145 | size_t partial_write = 0; 146 | lwmqtt_err_t err = 147 | client->network_write(client->network, buf + written, len - written, &partial_write, (uint32_t)remaining_time); 148 | if (err != LWMQTT_SUCCESS) { 149 | return err; 150 | } 151 | 152 | // increment counter 153 | written += partial_write; 154 | } 155 | 156 | return LWMQTT_SUCCESS; 157 | } 158 | 159 | static lwmqtt_err_t lwmqtt_read_packet_in_buffer(lwmqtt_client_t *client, size_t *read, 160 | lwmqtt_packet_type_t *packet_type) { 161 | // preset packet type 162 | *packet_type = LWMQTT_NO_PACKET; 163 | 164 | // read or wait for header byte 165 | lwmqtt_err_t err = lwmqtt_read_from_network(client, 0, 1); 166 | if (err == LWMQTT_NETWORK_TIMEOUT) { 167 | // this is ok as no data has been read at all 168 | return LWMQTT_SUCCESS; 169 | } else if (err != LWMQTT_SUCCESS) { 170 | return err; 171 | } 172 | 173 | // detect packet type 174 | err = lwmqtt_detect_packet_type(client->read_buf, 1, packet_type); 175 | if (err != LWMQTT_SUCCESS) { 176 | return err; 177 | } 178 | 179 | // prepare variables 180 | size_t len = 0; 181 | uint32_t rem_len = 0; 182 | 183 | do { 184 | // adjust len 185 | len++; 186 | 187 | // read next byte 188 | err = lwmqtt_read_from_network(client, len, 1); 189 | if (err != LWMQTT_SUCCESS) { 190 | return err; 191 | } 192 | 193 | // attempt to detect remaining length 194 | err = lwmqtt_detect_remaining_length(client->read_buf + 1, len, &rem_len); 195 | } while (err == LWMQTT_BUFFER_TOO_SHORT); 196 | 197 | // check final error 198 | if (err != LWMQTT_SUCCESS) { 199 | return err; 200 | } 201 | 202 | // handle overflow 203 | if (client->drop_overflow && 1 + len + rem_len > client->read_buf_size) { 204 | // drain network 205 | err = lwmqtt_drain_network(client, rem_len); 206 | if (err != LWMQTT_SUCCESS) { 207 | return err; 208 | } 209 | 210 | // unset packet 211 | *packet_type = LWMQTT_NO_PACKET; 212 | *read = 0; 213 | 214 | // increment if counter is available 215 | if (client->overflow_counter != NULL) { 216 | *client->overflow_counter += 1; 217 | } 218 | 219 | return LWMQTT_SUCCESS; 220 | } 221 | 222 | // read the rest of the buffer if needed 223 | if (rem_len > 0) { 224 | err = lwmqtt_read_from_network(client, 1 + len, rem_len); 225 | if (err != LWMQTT_SUCCESS) { 226 | return err; 227 | } 228 | } 229 | 230 | // adjust counter 231 | *read += 1 + len + rem_len; 232 | 233 | return LWMQTT_SUCCESS; 234 | } 235 | 236 | static lwmqtt_err_t lwmqtt_send_packet_in_buffer(lwmqtt_client_t *client, size_t length) { 237 | // write to network 238 | lwmqtt_err_t err = lwmqtt_write_to_network(client, client->write_buf, length); 239 | if (err != LWMQTT_SUCCESS) { 240 | return err; 241 | } 242 | 243 | // reset keep alive timer 244 | client->timer_set(client->keep_alive_timer, client->keep_alive_interval); 245 | 246 | return LWMQTT_SUCCESS; 247 | } 248 | 249 | static lwmqtt_err_t lwmqtt_cycle_once(lwmqtt_client_t *client, size_t *read, lwmqtt_packet_type_t *packet_type) { 250 | // read next packet from the network 251 | lwmqtt_err_t err = lwmqtt_read_packet_in_buffer(client, read, packet_type); 252 | if (err != LWMQTT_SUCCESS) { 253 | return err; 254 | } else if (*packet_type == LWMQTT_NO_PACKET) { 255 | return LWMQTT_SUCCESS; 256 | } 257 | 258 | switch (*packet_type) { 259 | // handle publish packets 260 | case LWMQTT_PUBLISH_PACKET: { 261 | // decode publish packet 262 | bool dup; 263 | uint16_t packet_id; 264 | lwmqtt_string_t topic; 265 | lwmqtt_message_t msg; 266 | err = lwmqtt_decode_publish(client->read_buf, client->read_buf_size, &dup, &packet_id, &topic, &msg); 267 | if (err != LWMQTT_SUCCESS) { 268 | return err; 269 | } 270 | 271 | // call callback if set 272 | if (client->callback != NULL) { 273 | client->callback(client, client->callback_ref, topic, msg); 274 | } 275 | 276 | // break early on qos zero 277 | if (msg.qos == LWMQTT_QOS0) { 278 | break; 279 | } 280 | 281 | // define ack packet 282 | lwmqtt_packet_type_t ack_type = LWMQTT_NO_PACKET; 283 | if (msg.qos == LWMQTT_QOS1) { 284 | ack_type = LWMQTT_PUBACK_PACKET; 285 | } else if (msg.qos == LWMQTT_QOS2) { 286 | ack_type = LWMQTT_PUBREC_PACKET; 287 | } 288 | 289 | // encode ack packet 290 | size_t len; 291 | err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, ack_type, packet_id); 292 | if (err != LWMQTT_SUCCESS) { 293 | return err; 294 | } 295 | 296 | // send ack packet 297 | err = lwmqtt_send_packet_in_buffer(client, len); 298 | if (err != LWMQTT_SUCCESS) { 299 | return err; 300 | } 301 | 302 | break; 303 | } 304 | 305 | // handle pubrec packets 306 | case LWMQTT_PUBREC_PACKET: { 307 | // decode pubrec packet 308 | uint16_t packet_id; 309 | err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_PUBREC_PACKET, &packet_id); 310 | if (err != LWMQTT_SUCCESS) { 311 | return err; 312 | } 313 | 314 | // encode pubrel packet 315 | size_t len; 316 | err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, LWMQTT_PUBREL_PACKET, packet_id); 317 | if (err != LWMQTT_SUCCESS) { 318 | return err; 319 | } 320 | 321 | // send pubrel packet 322 | err = lwmqtt_send_packet_in_buffer(client, len); 323 | if (err != LWMQTT_SUCCESS) { 324 | return err; 325 | } 326 | 327 | break; 328 | } 329 | 330 | // handle pubrel packets 331 | case LWMQTT_PUBREL_PACKET: { 332 | // decode pubrec packet 333 | uint16_t packet_id; 334 | err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_PUBREL_PACKET, &packet_id); 335 | if (err != LWMQTT_SUCCESS) { 336 | return err; 337 | } 338 | 339 | // encode pubcomp packet 340 | size_t len; 341 | err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, LWMQTT_PUBCOMP_PACKET, packet_id); 342 | if (err != LWMQTT_SUCCESS) { 343 | return err; 344 | } 345 | 346 | // send pubcomp packet 347 | err = lwmqtt_send_packet_in_buffer(client, len); 348 | if (err != LWMQTT_SUCCESS) { 349 | return err; 350 | } 351 | 352 | break; 353 | } 354 | 355 | // handle pingresp packets 356 | case LWMQTT_PINGRESP_PACKET: { 357 | // set flag 358 | client->pong_pending = false; 359 | 360 | break; 361 | } 362 | 363 | // handle all other packets 364 | default: { 365 | break; 366 | } 367 | } 368 | 369 | return LWMQTT_SUCCESS; 370 | } 371 | 372 | static lwmqtt_err_t lwmqtt_cycle_until(lwmqtt_client_t *client, lwmqtt_packet_type_t *packet_type, size_t available, 373 | lwmqtt_packet_type_t needle) { 374 | // prepare counter 375 | size_t read = 0; 376 | 377 | // loop until timeout has been reached 378 | do { 379 | // do one cycle 380 | lwmqtt_err_t err = lwmqtt_cycle_once(client, &read, packet_type); 381 | if (err != LWMQTT_SUCCESS) { 382 | return err; 383 | } 384 | 385 | // return when one packet has been successfully read when no availability has been given 386 | if (needle == LWMQTT_NO_PACKET && available == 0) { 387 | return LWMQTT_SUCCESS; 388 | } 389 | 390 | // otherwise check if needle has been found 391 | if (*packet_type == needle) { 392 | return LWMQTT_SUCCESS; 393 | } 394 | } while (client->timer_get(client->command_timer) > 0 && (available == 0 || read < available)); 395 | 396 | return LWMQTT_SUCCESS; 397 | } 398 | 399 | lwmqtt_err_t lwmqtt_connect(lwmqtt_client_t *client, lwmqtt_connect_options_t *options, lwmqtt_will_t *will, 400 | uint32_t timeout) { 401 | // ensure default options 402 | static lwmqtt_connect_options_t def_options = lwmqtt_default_connect_options; 403 | if (options == NULL) { 404 | options = &def_options; 405 | } 406 | 407 | // set command timer 408 | client->timer_set(client->command_timer, timeout); 409 | 410 | // save keep alive interval 411 | client->keep_alive_interval = (uint32_t)(options->keep_alive) * 1000; 412 | 413 | // set keep alive timer 414 | client->timer_set(client->keep_alive_timer, client->keep_alive_interval); 415 | 416 | // reset pong pending flag 417 | client->pong_pending = false; 418 | 419 | // reset return code and session present 420 | options->return_code = LWMQTT_UNKNOWN_RETURN_CODE; 421 | options->session_present = false; 422 | 423 | // encode connect packet 424 | size_t len; 425 | lwmqtt_err_t err = lwmqtt_encode_connect(client->write_buf, client->write_buf_size, &len, options, will); 426 | if (err != LWMQTT_SUCCESS) { 427 | return err; 428 | } 429 | 430 | // send packet 431 | err = lwmqtt_send_packet_in_buffer(client, len); 432 | if (err != LWMQTT_SUCCESS) { 433 | return err; 434 | } 435 | 436 | // wait for connack packet 437 | lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET; 438 | err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_CONNACK_PACKET); 439 | if (err != LWMQTT_SUCCESS) { 440 | return err; 441 | } else if (packet_type != LWMQTT_CONNACK_PACKET) { 442 | return LWMQTT_MISSING_OR_WRONG_PACKET; 443 | } 444 | 445 | // decode connack packet 446 | err = 447 | lwmqtt_decode_connack(client->read_buf, client->read_buf_size, &options->session_present, &options->return_code); 448 | if (err != LWMQTT_SUCCESS) { 449 | return err; 450 | } 451 | 452 | // return error if connection was not accepted 453 | if (options->return_code != LWMQTT_CONNECTION_ACCEPTED) { 454 | return LWMQTT_CONNECTION_DENIED; 455 | } 456 | 457 | return LWMQTT_SUCCESS; 458 | } 459 | 460 | lwmqtt_err_t lwmqtt_publish(lwmqtt_client_t *client, lwmqtt_publish_options_t *options, lwmqtt_string_t topic, 461 | lwmqtt_message_t msg, uint32_t timeout) { 462 | // ensure default options 463 | static lwmqtt_publish_options_t def_options = lwmqtt_default_publish_options; 464 | if (options == NULL) { 465 | options = &def_options; 466 | } 467 | 468 | // set command timer 469 | client->timer_set(client->command_timer, timeout); 470 | 471 | // add packet id if at least qos 1 472 | bool dup = false; 473 | uint16_t packet_id = 0; 474 | if (msg.qos == LWMQTT_QOS1 || msg.qos == LWMQTT_QOS2) { 475 | if (options->dup_id != NULL && *options->dup_id > 0) { 476 | dup = true; 477 | packet_id = *options->dup_id; 478 | } else { 479 | packet_id = lwmqtt_get_next_packet_id(client); 480 | if (options->dup_id != NULL) { 481 | *options->dup_id = packet_id; 482 | } 483 | } 484 | } 485 | 486 | // encode publish packet 487 | size_t len = 0; 488 | lwmqtt_err_t err = lwmqtt_encode_publish(client->write_buf, client->write_buf_size, &len, dup, packet_id, topic, msg); 489 | if (err != LWMQTT_SUCCESS) { 490 | return err; 491 | } 492 | 493 | // send packet (without payload) 494 | err = lwmqtt_send_packet_in_buffer(client, len); 495 | if (err != LWMQTT_SUCCESS) { 496 | return err; 497 | } 498 | 499 | // send payload if available 500 | if (msg.payload_len > 0) { 501 | err = lwmqtt_write_to_network(client, msg.payload, msg.payload_len); 502 | if (err != LWMQTT_SUCCESS) { 503 | return err; 504 | } 505 | } 506 | 507 | // immediately return on qos zero 508 | if (msg.qos == LWMQTT_QOS0) { 509 | return LWMQTT_SUCCESS; 510 | } 511 | 512 | // skip if requested 513 | if (options->skip_ack) { 514 | return LWMQTT_SUCCESS; 515 | } 516 | 517 | // define ack packet 518 | lwmqtt_packet_type_t ack_type = LWMQTT_NO_PACKET; 519 | if (msg.qos == LWMQTT_QOS1) { 520 | ack_type = LWMQTT_PUBACK_PACKET; 521 | } else if (msg.qos == LWMQTT_QOS2) { 522 | ack_type = LWMQTT_PUBCOMP_PACKET; 523 | } 524 | 525 | // wait for ack packet 526 | lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET; 527 | err = lwmqtt_cycle_until(client, &packet_type, 0, ack_type); 528 | if (err != LWMQTT_SUCCESS) { 529 | return err; 530 | } else if (packet_type != ack_type) { 531 | return LWMQTT_MISSING_OR_WRONG_PACKET; 532 | } 533 | 534 | // decode ack packet 535 | err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, ack_type, &packet_id); 536 | if (err != LWMQTT_SUCCESS) { 537 | return err; 538 | } 539 | 540 | return LWMQTT_SUCCESS; 541 | } 542 | 543 | lwmqtt_err_t lwmqtt_subscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, lwmqtt_qos_t *qos, 544 | uint32_t timeout) { 545 | // set command timer 546 | client->timer_set(client->command_timer, timeout); 547 | 548 | // encode subscribe packet 549 | size_t len; 550 | lwmqtt_err_t err = lwmqtt_encode_subscribe(client->write_buf, client->write_buf_size, &len, 551 | lwmqtt_get_next_packet_id(client), count, topic_filter, qos); 552 | if (err != LWMQTT_SUCCESS) { 553 | return err; 554 | } 555 | 556 | // send packet 557 | err = lwmqtt_send_packet_in_buffer(client, len); 558 | if (err != LWMQTT_SUCCESS) { 559 | return err; 560 | } 561 | 562 | // wait for suback packet 563 | lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET; 564 | err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_SUBACK_PACKET); 565 | if (err != LWMQTT_SUCCESS) { 566 | return err; 567 | } else if (packet_type != LWMQTT_SUBACK_PACKET) { 568 | return LWMQTT_MISSING_OR_WRONG_PACKET; 569 | } 570 | 571 | // decode packet 572 | int suback_count = 0; 573 | lwmqtt_qos_t granted_qos[count]; 574 | uint16_t packet_id; 575 | err = lwmqtt_decode_suback(client->read_buf, client->read_buf_size, &packet_id, count, &suback_count, granted_qos); 576 | if (err != LWMQTT_SUCCESS) { 577 | return err; 578 | } 579 | 580 | // check suback codes 581 | for (int i = 0; i < suback_count; i++) { 582 | if (granted_qos[i] == LWMQTT_QOS_FAILURE) { 583 | return LWMQTT_FAILED_SUBSCRIPTION; 584 | } 585 | } 586 | 587 | return LWMQTT_SUCCESS; 588 | } 589 | 590 | lwmqtt_err_t lwmqtt_subscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, lwmqtt_qos_t qos, 591 | uint32_t timeout) { 592 | return lwmqtt_subscribe(client, 1, &topic_filter, &qos, timeout); 593 | } 594 | 595 | lwmqtt_err_t lwmqtt_unsubscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, uint32_t timeout) { 596 | // set command timer 597 | client->timer_set(client->command_timer, timeout); 598 | 599 | // encode unsubscribe packet 600 | size_t len; 601 | lwmqtt_err_t err = lwmqtt_encode_unsubscribe(client->write_buf, client->write_buf_size, &len, 602 | lwmqtt_get_next_packet_id(client), count, topic_filter); 603 | if (err != LWMQTT_SUCCESS) { 604 | return err; 605 | } 606 | 607 | // send unsubscribe packet 608 | err = lwmqtt_send_packet_in_buffer(client, len); 609 | if (err != LWMQTT_SUCCESS) { 610 | return err; 611 | } 612 | 613 | // wait for unsuback packet 614 | lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET; 615 | err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_UNSUBACK_PACKET); 616 | if (err != LWMQTT_SUCCESS) { 617 | return err; 618 | } else if (packet_type != LWMQTT_UNSUBACK_PACKET) { 619 | return LWMQTT_MISSING_OR_WRONG_PACKET; 620 | } 621 | 622 | // decode unsuback packet 623 | uint16_t packet_id; 624 | err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_UNSUBACK_PACKET, &packet_id); 625 | if (err != LWMQTT_SUCCESS) { 626 | return err; 627 | } 628 | 629 | return LWMQTT_SUCCESS; 630 | } 631 | 632 | lwmqtt_err_t lwmqtt_unsubscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, uint32_t timeout) { 633 | return lwmqtt_unsubscribe(client, 1, &topic_filter, timeout); 634 | } 635 | 636 | lwmqtt_err_t lwmqtt_disconnect(lwmqtt_client_t *client, uint32_t timeout) { 637 | // set command timer 638 | client->timer_set(client->command_timer, timeout); 639 | 640 | // encode disconnect packet 641 | size_t len; 642 | lwmqtt_err_t err = lwmqtt_encode_zero(client->write_buf, client->write_buf_size, &len, LWMQTT_DISCONNECT_PACKET); 643 | if (err != LWMQTT_SUCCESS) { 644 | return err; 645 | } 646 | 647 | // send disconnected packet 648 | err = lwmqtt_send_packet_in_buffer(client, len); 649 | if (err != LWMQTT_SUCCESS) { 650 | return err; 651 | } 652 | 653 | return LWMQTT_SUCCESS; 654 | } 655 | 656 | lwmqtt_err_t lwmqtt_yield(lwmqtt_client_t *client, size_t available, uint32_t timeout) { 657 | // set command timer 658 | client->timer_set(client->command_timer, timeout); 659 | 660 | // cycle until timeout has been reached 661 | lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET; 662 | lwmqtt_err_t err = lwmqtt_cycle_until(client, &packet_type, available, LWMQTT_NO_PACKET); 663 | if (err != LWMQTT_SUCCESS) { 664 | return err; 665 | } 666 | 667 | return LWMQTT_SUCCESS; 668 | } 669 | 670 | lwmqtt_err_t lwmqtt_keep_alive(lwmqtt_client_t *client, uint32_t timeout) { 671 | // set command timer 672 | client->timer_set(client->command_timer, timeout); 673 | 674 | // return immediately if keep alive interval is zero 675 | if (client->keep_alive_interval == 0) { 676 | return LWMQTT_SUCCESS; 677 | } 678 | 679 | // return immediately if no ping is due 680 | if (client->timer_get(client->keep_alive_timer) > 0) { 681 | return LWMQTT_SUCCESS; 682 | } 683 | 684 | // a ping is due 685 | 686 | // fail immediately if a pong is already pending 687 | if (client->pong_pending) { 688 | return LWMQTT_PONG_TIMEOUT; 689 | } 690 | 691 | // encode pingreq packet 692 | size_t len; 693 | lwmqtt_err_t err = lwmqtt_encode_zero(client->write_buf, client->write_buf_size, &len, LWMQTT_PINGREQ_PACKET); 694 | if (err != LWMQTT_SUCCESS) { 695 | return err; 696 | } 697 | 698 | // send packet 699 | err = lwmqtt_send_packet_in_buffer(client, len); 700 | if (err != LWMQTT_SUCCESS) { 701 | return err; 702 | } 703 | 704 | // set flag 705 | client->pong_pending = true; 706 | 707 | return LWMQTT_SUCCESS; 708 | } 709 | -------------------------------------------------------------------------------- /src/lwmqtt/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "helpers.h" 4 | 5 | uint8_t lwmqtt_read_bits(uint8_t byte, int pos, int num) { 6 | return (byte & (uint8_t)((~(0xFF << (uint8_t)num)) << (uint8_t)pos)) >> (uint8_t)pos; 7 | } 8 | 9 | void lwmqtt_write_bits(uint8_t *byte, uint8_t value, int pos, int num) { 10 | *byte = (*byte & ~(uint8_t)((~(0xFFu << (uint8_t)num)) << (uint8_t)pos)) | (value << (uint8_t)pos); 11 | } 12 | 13 | lwmqtt_err_t lwmqtt_read_data(uint8_t **buf, const uint8_t *buf_end, uint8_t **data, size_t len) { 14 | // check zero length 15 | if (len == 0) { 16 | *data = NULL; 17 | return LWMQTT_SUCCESS; 18 | } 19 | 20 | // check buffer size 21 | if ((size_t)(buf_end - (*buf)) < len) { 22 | return LWMQTT_BUFFER_TOO_SHORT; 23 | } 24 | 25 | // read data 26 | *data = *buf; 27 | 28 | // advance pointer 29 | *buf += len; 30 | 31 | return LWMQTT_SUCCESS; 32 | } 33 | 34 | lwmqtt_err_t lwmqtt_write_data(uint8_t **buf, const uint8_t *buf_end, uint8_t *data, size_t len) { 35 | // check zero length 36 | if (len == 0) { 37 | return LWMQTT_SUCCESS; 38 | } 39 | 40 | // check buffer size 41 | if ((size_t)(buf_end - (*buf)) < len) { 42 | return LWMQTT_BUFFER_TOO_SHORT; 43 | } 44 | 45 | // write data 46 | memcpy(*buf, data, len); 47 | 48 | // advance pointer 49 | *buf += len; 50 | 51 | return LWMQTT_SUCCESS; 52 | } 53 | 54 | lwmqtt_err_t lwmqtt_read_num(uint8_t **buf, const uint8_t *buf_end, uint16_t *num) { 55 | // check buffer size 56 | if ((size_t)(buf_end - (*buf)) < 2) { 57 | *num = 0; 58 | return LWMQTT_BUFFER_TOO_SHORT; 59 | } 60 | 61 | // read two byte integer 62 | *num = (uint16_t)256 * (*buf)[0] + (*buf)[1]; 63 | 64 | // adjust pointer 65 | *buf += 2; 66 | 67 | return LWMQTT_SUCCESS; 68 | } 69 | 70 | lwmqtt_err_t lwmqtt_write_num(uint8_t **buf, const uint8_t *buf_end, uint16_t num) { 71 | // check buffer size 72 | if ((size_t)(buf_end - (*buf)) < 2) { 73 | return LWMQTT_BUFFER_TOO_SHORT; 74 | } 75 | 76 | // write bytes 77 | (*buf)[0] = (uint8_t)(num / 256); 78 | (*buf)[1] = (uint8_t)(num % 256); 79 | 80 | // adjust pointer 81 | *buf += 2; 82 | 83 | return LWMQTT_SUCCESS; 84 | } 85 | 86 | lwmqtt_err_t lwmqtt_read_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t *str) { 87 | // read length 88 | uint16_t len; 89 | lwmqtt_err_t err = lwmqtt_read_num(buf, buf_end, &len); 90 | if (err != LWMQTT_SUCCESS) { 91 | return err; 92 | } 93 | 94 | // read data 95 | err = lwmqtt_read_data(buf, buf_end, (uint8_t **)&str->data, len); 96 | if (err != LWMQTT_SUCCESS) { 97 | return err; 98 | } 99 | 100 | // set length 101 | str->len = len; 102 | 103 | return LWMQTT_SUCCESS; 104 | } 105 | 106 | lwmqtt_err_t lwmqtt_write_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t str) { 107 | // write string length 108 | lwmqtt_err_t err = lwmqtt_write_num(buf, buf_end, str.len); 109 | if (err != LWMQTT_SUCCESS) { 110 | return err; 111 | } 112 | 113 | // write data 114 | err = lwmqtt_write_data(buf, buf_end, (uint8_t *)str.data, str.len); 115 | if (err != LWMQTT_SUCCESS) { 116 | return err; 117 | } 118 | 119 | return LWMQTT_SUCCESS; 120 | } 121 | 122 | lwmqtt_err_t lwmqtt_read_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t *byte) { 123 | // check buffer size 124 | if ((size_t)(buf_end - (*buf)) < 1) { 125 | *byte = 0; 126 | return LWMQTT_BUFFER_TOO_SHORT; 127 | } 128 | 129 | // read byte 130 | *byte = (*buf)[0]; 131 | 132 | // adjust pointer 133 | *buf += 1; 134 | 135 | return LWMQTT_SUCCESS; 136 | } 137 | 138 | lwmqtt_err_t lwmqtt_write_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t byte) { 139 | // check buffer size 140 | if ((size_t)(buf_end - (*buf)) < 1) { 141 | return LWMQTT_BUFFER_TOO_SHORT; 142 | } 143 | 144 | // write byte 145 | (*buf)[0] = byte; 146 | 147 | // adjust pointer 148 | *buf += 1; 149 | 150 | return LWMQTT_SUCCESS; 151 | } 152 | 153 | lwmqtt_err_t lwmqtt_varnum_length(uint32_t varnum, int *len) { 154 | if (varnum < 128) { 155 | *len = 1; 156 | return LWMQTT_SUCCESS; 157 | } else if (varnum < 16384) { 158 | *len = 2; 159 | return LWMQTT_SUCCESS; 160 | } else if (varnum < 2097151) { 161 | *len = 3; 162 | return LWMQTT_SUCCESS; 163 | } else if (varnum < 268435455) { 164 | *len = 4; 165 | return LWMQTT_SUCCESS; 166 | } else { 167 | *len = 0; 168 | return LWMQTT_VARNUM_OVERFLOW; 169 | } 170 | } 171 | 172 | lwmqtt_err_t lwmqtt_read_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t *varnum) { 173 | // prepare last byte 174 | uint8_t byte; 175 | 176 | // prepare multiplier 177 | uint32_t multiplier = 1; 178 | 179 | // prepare length 180 | size_t len = 0; 181 | 182 | // initialize number 183 | *varnum = 0; 184 | 185 | // decode variadic number 186 | do { 187 | // increment length 188 | len++; 189 | 190 | // return error if buffer is too small 191 | if ((size_t)(buf_end - (*buf)) < len) { 192 | return LWMQTT_BUFFER_TOO_SHORT; 193 | } 194 | 195 | // return error if the length has overflowed 196 | if (len > 4) { 197 | return LWMQTT_VARNUM_OVERFLOW; 198 | } 199 | 200 | // read byte 201 | byte = (*buf)[len - 1]; 202 | 203 | // add byte to number 204 | *varnum += (byte & 127u) * multiplier; 205 | 206 | // increase multiplier 207 | multiplier *= 128; 208 | } while ((byte & 128u) != 0); 209 | 210 | // adjust pointer 211 | *buf += len; 212 | 213 | return LWMQTT_SUCCESS; 214 | } 215 | 216 | lwmqtt_err_t lwmqtt_write_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t varnum) { 217 | // init len counter 218 | size_t len = 0; 219 | 220 | // encode variadic number 221 | do { 222 | // check overflow 223 | if (len == 4) { 224 | return LWMQTT_VARNUM_OVERFLOW; 225 | } 226 | 227 | // return error if buffer is too small 228 | if ((size_t)(buf_end - (*buf)) < len + 1) { 229 | return LWMQTT_BUFFER_TOO_SHORT; 230 | } 231 | 232 | // calculate current byte 233 | uint8_t byte = (uint8_t)(varnum % 128); 234 | 235 | // change remaining length 236 | varnum /= 128; 237 | 238 | // set the top bit of this byte if there are more to encode 239 | if (varnum > 0) { 240 | byte |= 0x80u; 241 | } 242 | 243 | // write byte 244 | (*buf)[len++] = byte; 245 | } while (varnum > 0); 246 | 247 | // adjust pointer 248 | *buf += len; 249 | 250 | return LWMQTT_SUCCESS; 251 | } 252 | -------------------------------------------------------------------------------- /src/lwmqtt/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef LWMQTT_HELPERS_H 2 | #define LWMQTT_HELPERS_H 3 | 4 | #include "lwmqtt.h" 5 | 6 | /** 7 | * Reads bits from a byte. 8 | * 9 | * @param byte The byte to read from. 10 | * @param pos The position of the first bit. 11 | * @param num The number of bits to read. 12 | * @return The read bits as a byte. 13 | */ 14 | uint8_t lwmqtt_read_bits(uint8_t byte, int pos, int num); 15 | 16 | /** 17 | * Write bits to a byte. 18 | * 19 | * @param byte The byte to write bits to. 20 | * @param value The bits to write as a byte. 21 | * @param pos The position of the first bit. 22 | * @param num The number of bits to write. 23 | */ 24 | void lwmqtt_write_bits(uint8_t *byte, uint8_t value, int pos, int num); 25 | 26 | /** 27 | * Reads arbitrary data from the specified buffer. The pointer is incremented by bytes read. 28 | * 29 | * @param buf Pointer to the buffer. 30 | * @param buf_end Pointer to the end of the buffer. 31 | * @param data Pointer to beginning of data. 32 | * @param len The amount of data to read. 33 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 34 | */ 35 | lwmqtt_err_t lwmqtt_read_data(uint8_t **buf, const uint8_t *buf_end, uint8_t **data, size_t len); 36 | 37 | /** 38 | * Writes arbitrary data to the specified buffer. The pointer is incremented by the bytes written. 39 | * 40 | * @param buf Pointer to the buffer. 41 | * @param buf_end Pointer to the end of the buffer. 42 | * @param data Pointer to the to be written data. 43 | * @param len The amount of data to write. 44 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 45 | */ 46 | lwmqtt_err_t lwmqtt_write_data(uint8_t **buf, const uint8_t *buf_end, uint8_t *data, size_t len); 47 | 48 | /** 49 | * Reads a two byte number from the specified buffer. The pointer is incremented by two. 50 | * 51 | * @param buf Pointer to the buffer. 52 | * @param buf_end Pointer to the end of the buffer. 53 | * @param num The read number. 54 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 55 | */ 56 | lwmqtt_err_t lwmqtt_read_num(uint8_t **buf, const uint8_t *buf_end, uint16_t *num); 57 | 58 | /** 59 | * Writes a two byte number to the specified buffer. The pointer is incremented by two. 60 | * 61 | * @param buf Pointer to the buffer. 62 | * @param buf_end Pointer to the end of the buffer. 63 | * @param num The number to write. 64 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 65 | */ 66 | lwmqtt_err_t lwmqtt_write_num(uint8_t **buf, const uint8_t *buf_end, uint16_t num); 67 | 68 | /** 69 | * Reads a string from the specified buffer into the passed object. The pointer is incremented by the bytes read. 70 | * 71 | * @param buf Pointer to the buffer. 72 | * @param buf_end Pointer to the end of the buffer. 73 | * @param str The object into which the data is to be read. 74 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 75 | */ 76 | lwmqtt_err_t lwmqtt_read_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t *str); 77 | 78 | /** 79 | * Writes a string to the specified buffer. The pointer is incremented by the bytes written. 80 | * 81 | * @param buf Pointer to the buffer. 82 | * @param buf_end Pointer to the end of the buffer. 83 | * @param str The string to write. 84 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 85 | */ 86 | lwmqtt_err_t lwmqtt_write_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t str); 87 | 88 | /** 89 | * Reads one byte from the buffer. The pointer is incremented by one. 90 | * 91 | * @param buf Pointer to the buffer. 92 | * @param buf_end Pointer to the end of the buffer. 93 | * @param byte The read byte. 94 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 95 | */ 96 | lwmqtt_err_t lwmqtt_read_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t *byte); 97 | 98 | /** 99 | * Writes one byte to the specified buffer. The pointer is incremented by one. 100 | * 101 | * @param buf Pointer to the buffer. 102 | * @param buf_end Pointer to the end of the buffer. 103 | * @param byte The byte to write. 104 | * @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT. 105 | */ 106 | lwmqtt_err_t lwmqtt_write_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t byte); 107 | 108 | /** 109 | * Returns the amount of bytes required by the variable number. 110 | * 111 | * @param varnum The number to check. 112 | * @param len The required length; 113 | * @return LWMQTT_SUCCESS or LWMQTT_VARNUM_OVERFLOW. 114 | */ 115 | lwmqtt_err_t lwmqtt_varnum_length(uint32_t varnum, int *len); 116 | 117 | /** 118 | * Reads a variable number from the specified buffer. The pointer is incremented by the bytes read. 119 | * 120 | * @param buf Pointer to the buffer. 121 | * @param buf_end Pointer to the end of the buffer. 122 | * @param varnum The read varnum. 123 | * @return LWMQTT_SUCCESS, LWMQTT_BUFFER_TOO_SHORT or LWMQTT_VARNUM_OVERFLOW. 124 | */ 125 | lwmqtt_err_t lwmqtt_read_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t *varnum); 126 | 127 | /** 128 | * Writes a variable number to the specified buffer. The pointer is incremented by the bytes written. 129 | * 130 | * @param buf Pointer to the buffer. 131 | * @param buf_end Pointer to the end of the buffer. 132 | * @param varnum The number to write. 133 | * @return LWMQTT_SUCCESS, LWMQTT_BUFFER_TOO_SHORT or LWMQTT_VARNUM_OVERFLOW. 134 | */ 135 | lwmqtt_err_t lwmqtt_write_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t varnum); 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /src/lwmqtt/lwmqtt.h: -------------------------------------------------------------------------------- 1 | #ifndef LWMQTT_H 2 | #define LWMQTT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * The error type used by all exposed APIs. 10 | * 11 | * If a function returns an error that operates on a connected client (e.g publish, keep_alive, etc.) the caller should 12 | * switch into a disconnected state, close and cleanup the current connection and start over by creating a new 13 | * connection. 14 | */ 15 | typedef enum { 16 | LWMQTT_SUCCESS = 0, 17 | LWMQTT_BUFFER_TOO_SHORT = -1, 18 | LWMQTT_VARNUM_OVERFLOW = -2, 19 | LWMQTT_NETWORK_FAILED_CONNECT = -3, 20 | LWMQTT_NETWORK_TIMEOUT = -4, 21 | LWMQTT_NETWORK_FAILED_READ = -5, 22 | LWMQTT_NETWORK_FAILED_WRITE = -6, 23 | LWMQTT_REMAINING_LENGTH_OVERFLOW = -7, 24 | LWMQTT_REMAINING_LENGTH_MISMATCH = -8, 25 | LWMQTT_MISSING_OR_WRONG_PACKET = -9, 26 | LWMQTT_CONNECTION_DENIED = -10, 27 | LWMQTT_FAILED_SUBSCRIPTION = -11, 28 | LWMQTT_SUBACK_ARRAY_OVERFLOW = -12, 29 | LWMQTT_PONG_TIMEOUT = -13, 30 | } lwmqtt_err_t; 31 | 32 | /** 33 | * The common string object. 34 | */ 35 | typedef struct { 36 | uint16_t len; 37 | char *data; 38 | } lwmqtt_string_t; 39 | 40 | /** 41 | * The default initializer for string objects. 42 | */ 43 | #define lwmqtt_default_string \ 44 | { 0, NULL } 45 | 46 | /** 47 | * Returns a string object for the passed C string. 48 | * 49 | * @param str The C string. 50 | * @return A string object. 51 | */ 52 | lwmqtt_string_t lwmqtt_string(const char *str); 53 | 54 | /** 55 | * Compares a string object to a C string. 56 | * 57 | * @param a The string object to compare. 58 | * @param b The C string to compare. 59 | * @return Similarity e.g. strcmp(). 60 | */ 61 | int lwmqtt_strcmp(lwmqtt_string_t a, const char *b); 62 | 63 | /** 64 | * The available QOS levels. 65 | */ 66 | typedef enum { 67 | LWMQTT_QOS0 = 0, 68 | LWMQTT_QOS1 = 1, 69 | LWMQTT_QOS2 = 2, 70 | LWMQTT_QOS_FAILURE = 128, 71 | } lwmqtt_qos_t; 72 | 73 | /** 74 | * The common message object. 75 | */ 76 | typedef struct { 77 | lwmqtt_qos_t qos; 78 | bool retained; 79 | uint8_t *payload; 80 | size_t payload_len; 81 | } lwmqtt_message_t; 82 | 83 | /** 84 | * The default initializer for message objects. 85 | */ 86 | #define lwmqtt_default_message \ 87 | { LWMQTT_QOS0, false, NULL, 0 } 88 | 89 | /** 90 | * The object defining the last will of a client. 91 | */ 92 | typedef struct { 93 | lwmqtt_string_t topic; 94 | lwmqtt_qos_t qos; 95 | bool retained; 96 | lwmqtt_string_t payload; 97 | } lwmqtt_will_t; 98 | 99 | /** 100 | * The default initializer for will objects. 101 | */ 102 | #define lwmqtt_default_will \ 103 | { lwmqtt_default_string, LWMQTT_QOS0, false, lwmqtt_default_string } 104 | 105 | /** 106 | * The available return codes transported by the connack packet. 107 | */ 108 | typedef enum { 109 | LWMQTT_CONNECTION_ACCEPTED = 0, 110 | LWMQTT_UNACCEPTABLE_PROTOCOL = 1, 111 | LWMQTT_IDENTIFIER_REJECTED = 2, 112 | LWMQTT_SERVER_UNAVAILABLE = 3, 113 | LWMQTT_BAD_USERNAME_OR_PASSWORD = 4, 114 | LWMQTT_NOT_AUTHORIZED = 5, 115 | LWMQTT_UNKNOWN_RETURN_CODE = 6 116 | } lwmqtt_return_code_t; 117 | 118 | /** 119 | * The object containing the connection options. 120 | */ 121 | typedef struct { 122 | lwmqtt_string_t client_id; 123 | uint16_t keep_alive; 124 | bool clean_session; 125 | lwmqtt_string_t username; 126 | lwmqtt_string_t password; 127 | lwmqtt_return_code_t return_code; 128 | bool session_present; 129 | } lwmqtt_connect_options_t; 130 | 131 | /** 132 | * The default initializer for the connect options objects. 133 | */ 134 | #define lwmqtt_default_connect_options \ 135 | { lwmqtt_default_string, 60, true, lwmqtt_default_string, lwmqtt_default_string, LWMQTT_UNKNOWN_RETURN_CODE, false } 136 | 137 | /** 138 | * The object containing the publish options. 139 | */ 140 | typedef struct { 141 | uint16_t *dup_id; 142 | bool skip_ack; 143 | } lwmqtt_publish_options_t; 144 | 145 | /** 146 | * The default initializer for publish options object. 147 | */ 148 | #define lwmqtt_default_publish_options \ 149 | { NULL, false } 150 | 151 | /** 152 | * Forward declaration of the client object. 153 | */ 154 | typedef struct lwmqtt_client_t lwmqtt_client_t; 155 | 156 | /** 157 | * The callback used to read from a network object. 158 | * 159 | * The callbacks is expected to read up to the amount of bytes in to the passed buffer. It should block the specified 160 | * timeout and wait for more incoming data. 161 | * 162 | * @param ref A custom reference. 163 | * @param buf The buffer. 164 | * @param len The length of the buffer. 165 | * @param read Variable that must be set with the amount of read bytes. 166 | * @param timeout The timeout in milliseconds for the operation. 167 | */ 168 | typedef lwmqtt_err_t (*lwmqtt_network_read_t)(void *ref, uint8_t *buf, size_t len, size_t *read, uint32_t timeout); 169 | 170 | /** 171 | * The callback used to write to a network object. 172 | * 173 | * The callback is expected to write up to the amount of bytes from the passed buffer. It should wait up to the 174 | * specified timeout to write the specified data to the network. 175 | * 176 | * @param ref A custom reference. 177 | * @param buf The buffer. 178 | * @param len The length of the buffer. 179 | * @param sent Variable that must be set with the amount of written bytes. 180 | * @param timeout The timeout in milliseconds for the operation. 181 | */ 182 | typedef lwmqtt_err_t (*lwmqtt_network_write_t)(void *ref, uint8_t *buf, size_t len, size_t *sent, uint32_t timeout); 183 | 184 | /** 185 | * The callback used to set a timer. 186 | * 187 | * @param ref A custom reference. 188 | * @param timeout The amount of milliseconds until the deadline. 189 | */ 190 | typedef void (*lwmqtt_timer_set_t)(void *ref, uint32_t timeout); 191 | 192 | /** 193 | * The callback used to get a timers value. 194 | * 195 | * @param ref A custom reference. 196 | * @return The amount of milliseconds until the deadline. May return negative numbers if the deadline has been reached. 197 | */ 198 | typedef int32_t (*lwmqtt_timer_get_t)(void *ref); 199 | 200 | /** 201 | * The callback used to forward incoming messages. 202 | * 203 | * Note: The callback is mostly executed because of a call to lwmqtt_yield() that processes incoming messages. However, 204 | * it is possible that the callback is also executed during a call to lwmqtt_subscribe(), lwmqtt_publish() or 205 | * lwmqtt_unsubscribe() if incoming messages are received between the required acknowledgements. It is therefore not 206 | * recommended to call any further lwmqtt methods in the callback as this might result in deadlocks. Instead, the 207 | * callback should place the received messages in a queue and dispatch them after the caller has returned. 208 | * 209 | * @param client The client object. 210 | * @param ref A custom reference. 211 | * @param str The topic string. 212 | * @param msg The received message. 213 | */ 214 | typedef void (*lwmqtt_callback_t)(lwmqtt_client_t *client, void *ref, lwmqtt_string_t str, lwmqtt_message_t msg); 215 | 216 | /** 217 | * The client object. 218 | */ 219 | struct lwmqtt_client_t { 220 | uint16_t last_packet_id; 221 | uint32_t keep_alive_interval; 222 | bool pong_pending; 223 | 224 | size_t write_buf_size, read_buf_size; 225 | uint8_t *write_buf, *read_buf; 226 | 227 | lwmqtt_callback_t callback; 228 | void *callback_ref; 229 | 230 | void *network; 231 | lwmqtt_network_read_t network_read; 232 | lwmqtt_network_write_t network_write; 233 | 234 | void *keep_alive_timer; 235 | void *command_timer; 236 | lwmqtt_timer_set_t timer_set; 237 | lwmqtt_timer_get_t timer_get; 238 | 239 | bool drop_overflow; 240 | uint32_t *overflow_counter; 241 | }; 242 | 243 | /** 244 | * Will initialize the specified client object. 245 | * 246 | * @param client The client object. 247 | * @param write_buf The write buffer. 248 | * @param write_buf_size The write buffer size. 249 | * @param read_buf The read buffer. 250 | * @param read_buf_size The read buffer size. 251 | */ 252 | void lwmqtt_init(lwmqtt_client_t *client, uint8_t *write_buf, size_t write_buf_size, uint8_t *read_buf, 253 | size_t read_buf_size); 254 | 255 | /** 256 | * Will set the network reference and callbacks for this client object. 257 | * 258 | * @param client The client object. 259 | * @param ref The reference to the network object. 260 | * @param read The read callback. 261 | * @param write The write callback. 262 | */ 263 | void lwmqtt_set_network(lwmqtt_client_t *client, void *ref, lwmqtt_network_read_t read, lwmqtt_network_write_t write); 264 | 265 | /** 266 | * Will set the timer references and callbacks for this client object. 267 | * 268 | * @param client The client object. 269 | * @param keep_alive_timer The reference to the keep alive timer. 270 | * @param command_timer The reference to the command timer. 271 | * @param set The set callback. 272 | * @param get The get callback. 273 | */ 274 | void lwmqtt_set_timers(lwmqtt_client_t *client, void *keep_alive_timer, void *command_timer, lwmqtt_timer_set_t set, 275 | lwmqtt_timer_get_t get); 276 | 277 | /** 278 | * Will set the callback used to receive incoming messages. 279 | * 280 | * @param client The client object. 281 | * @param ref A custom reference that will passed to the callback. 282 | * @param cb The callback to be called. 283 | */ 284 | void lwmqtt_set_callback(lwmqtt_client_t *client, void *ref, lwmqtt_callback_t cb); 285 | 286 | /** 287 | * Will configure the client to drop packets that overflow the read buffer. If a counter is provided it will be 288 | * incremented with each dropped packet. 289 | * 290 | * @param client The client. 291 | * @param enabled Whether dropping is enabled. 292 | * @param counter The dropped packet counter. 293 | */ 294 | void lwmqtt_drop_overflow(lwmqtt_client_t *client, bool enabled, uint32_t *counter); 295 | 296 | /** 297 | * Will send a connect packet and wait for a connack response. If options are provided they are used for the 298 | * connection attempt and the return code and whether a session was present is stored in it. 299 | * 300 | * The network object must already be connected to the server. An error is returned if the broker rejects the 301 | * connection. 302 | * 303 | * @param client The client object. 304 | * @param options The optional connect options. 305 | * @param will The will object. 306 | * @param timeout The command timeout. 307 | * @return An error value. 308 | */ 309 | lwmqtt_err_t lwmqtt_connect(lwmqtt_client_t *client, lwmqtt_connect_options_t *options, lwmqtt_will_t *will, 310 | uint32_t timeout); 311 | 312 | /** 313 | * Will send a publish packet and wait for all acks to complete. If the encoded packet (without payload) is bigger than 314 | * the write buffer the function will return LWMQTT_BUFFER_TOO_SHORT without attempting to send the packet. 315 | * 316 | * If options.dup_id is present and zero, the client will store the used packet id at the specified location (QoS >= 1). 317 | * If options.dup_id is present and non-zero, the client will use the specified number as the packet id and flag the 318 | * message as a duplicate (QoS >= 1). 319 | * 320 | * Note: The message callback might be called with incoming messages as part of this call. 321 | * 322 | * @param client The client object. 323 | * @param options The optional publish options. 324 | * @param topic The topic. 325 | * @param msg The message. 326 | * @param timeout The command timeout. 327 | * @return An error value. 328 | */ 329 | lwmqtt_err_t lwmqtt_publish(lwmqtt_client_t *client, lwmqtt_publish_options_t *options, lwmqtt_string_t topic, 330 | lwmqtt_message_t msg, uint32_t timeout); 331 | 332 | /** 333 | * Will send a subscribe packet with multiple topic filters plus QOS levels and wait for the suback to complete. 334 | * 335 | * Note: The message callback might be called with incoming messages as part of this call. 336 | * 337 | * @param client The client object. 338 | * @param count The number of topic filters and QOS levels. 339 | * @param topic_filter The list of topic filters. 340 | * @param qos The list of QOS levels. 341 | * @param timeout The command timeout. 342 | * @return An error value. 343 | */ 344 | lwmqtt_err_t lwmqtt_subscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, lwmqtt_qos_t *qos, 345 | uint32_t timeout); 346 | 347 | /** 348 | * Will send a subscribe packet with a single topic filter plus QOS level and wait for the suback to complete. 349 | * 350 | * Note: The message callback might be called with incoming messages as part of this call. 351 | * 352 | * @param client The client object. 353 | * @param topic_filter The topic filter. 354 | * @param qos The QOS level. 355 | * @param timeout The command timeout. 356 | * @return An error value. 357 | */ 358 | lwmqtt_err_t lwmqtt_subscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, lwmqtt_qos_t qos, 359 | uint32_t timeout); 360 | 361 | /** 362 | * Will send an unsubscribe packet with multiple topic filters and wait for the unsuback to complete. 363 | * 364 | * Note: The message callback might be called with incoming messages as part of this call. 365 | * 366 | * @param client The client object. 367 | * @param count The number of topic filters. 368 | * @param topic_filter The topic filter. 369 | * @param timeout The command timeout. 370 | * @return An error value. 371 | */ 372 | lwmqtt_err_t lwmqtt_unsubscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, uint32_t timeout); 373 | 374 | /** 375 | * Will send an unsubscribe packet with a single topic filter and wait for the unsuback to complete. 376 | * 377 | * Note: The message callback might be called with incoming messages as part of this call. 378 | * 379 | * @param client The client object. 380 | * @param topic_filter The topic filter. 381 | * @param timeout The command timeout. 382 | * @return An error value. 383 | */ 384 | lwmqtt_err_t lwmqtt_unsubscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, uint32_t timeout); 385 | 386 | /** 387 | * Will send a disconnect packet and finish the client. 388 | * 389 | * @param client The client object. 390 | * @param timeout The command timeout. 391 | * @return An error value. 392 | */ 393 | lwmqtt_err_t lwmqtt_disconnect(lwmqtt_client_t *client, uint32_t timeout); 394 | 395 | /** 396 | * Will yield control to the client and receive incoming packets from the network. 397 | * 398 | * Single-threaded applications may peek on the network and assess if data is available to read before calling yield and 399 | * potentially block until the timeout is reached. Multi-threaded applications may select on the socket and block until 400 | * data is available and then yield to the client if data is available. All applications may specify the amount of bytes 401 | * available to read in order to constrain the yield to only receive packets that are already in-flight. 402 | * 403 | * If no availability info is given the yield will return after one packet has been successfully read or the deadline 404 | * has been reached but no single byte has been received. 405 | * 406 | * Note: The message callback might be called with incoming messages as part of this call. 407 | * 408 | * @param client The client object. 409 | * @param available The available bytes to read. 410 | * @param timeout The command timeout. 411 | * @return An error value. 412 | */ 413 | lwmqtt_err_t lwmqtt_yield(lwmqtt_client_t *client, size_t available, uint32_t timeout); 414 | 415 | /** 416 | * Will yield control to the client to keep the connection alive. 417 | * 418 | * This functions must be called at a rate slightly lower than 25% of the configured keep alive. If keep alive is zero, 419 | * the function may not be called at all. 420 | * 421 | * @param client The client object. 422 | * @param timeout The command timeout. 423 | * @return An error value. 424 | */ 425 | lwmqtt_err_t lwmqtt_keep_alive(lwmqtt_client_t *client, uint32_t timeout); 426 | 427 | #endif // LWMQTT_H 428 | -------------------------------------------------------------------------------- /src/lwmqtt/packet.c: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | 3 | lwmqtt_err_t lwmqtt_detect_packet_type(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t *packet_type) { 4 | // set default packet type 5 | *packet_type = LWMQTT_NO_PACKET; 6 | 7 | // prepare pointer 8 | uint8_t *buf_ptr = buf; 9 | uint8_t *buf_end = buf + buf_len; 10 | 11 | // prepare header 12 | uint8_t header; 13 | 14 | // read header 15 | lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header); 16 | if (err != LWMQTT_SUCCESS) { 17 | return err; 18 | } 19 | 20 | // get packet type 21 | *packet_type = (lwmqtt_packet_type_t)lwmqtt_read_bits(header, 4, 4); 22 | 23 | // check if packet type is correct and can be received 24 | switch (*packet_type) { 25 | case LWMQTT_CONNACK_PACKET: 26 | case LWMQTT_PUBLISH_PACKET: 27 | case LWMQTT_PUBACK_PACKET: 28 | case LWMQTT_PUBREC_PACKET: 29 | case LWMQTT_PUBREL_PACKET: 30 | case LWMQTT_PUBCOMP_PACKET: 31 | case LWMQTT_SUBACK_PACKET: 32 | case LWMQTT_UNSUBACK_PACKET: 33 | case LWMQTT_PINGRESP_PACKET: 34 | return LWMQTT_SUCCESS; 35 | default: 36 | *packet_type = LWMQTT_NO_PACKET; 37 | return LWMQTT_MISSING_OR_WRONG_PACKET; 38 | } 39 | } 40 | 41 | lwmqtt_err_t lwmqtt_detect_remaining_length(uint8_t *buf, size_t buf_len, uint32_t *rem_len) { 42 | // prepare pointer 43 | uint8_t *ptr = buf; 44 | 45 | // attempt to decode remaining length 46 | lwmqtt_err_t err = lwmqtt_read_varnum(&ptr, buf + buf_len, rem_len); 47 | if (err == LWMQTT_VARNUM_OVERFLOW) { 48 | *rem_len = 0; 49 | return LWMQTT_REMAINING_LENGTH_OVERFLOW; 50 | } else if (err != LWMQTT_SUCCESS) { 51 | *rem_len = 0; 52 | return err; 53 | } 54 | 55 | return LWMQTT_SUCCESS; 56 | } 57 | 58 | lwmqtt_err_t lwmqtt_encode_connect(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_connect_options_t *options, 59 | lwmqtt_will_t *will) { 60 | // prepare pointers 61 | uint8_t *buf_ptr = buf; 62 | uint8_t *buf_end = buf + buf_len; 63 | 64 | // fixed header is 10 65 | uint32_t rem_len = 10; 66 | 67 | // add client id to remaining length 68 | rem_len += options->client_id.len + 2; 69 | 70 | // add will if present to remaining length 71 | if (will != NULL) { 72 | rem_len += will->topic.len + 2 + will->payload.len + 2; 73 | } 74 | 75 | // add username if username or password is present to remaining length 76 | if (options->username.len > 0 || options->password.len > 0) { 77 | rem_len += options->username.len + 2; 78 | 79 | // add password if present to remaining length 80 | if (options->password.len > 0) { 81 | rem_len += options->password.len + 2; 82 | } 83 | } 84 | 85 | // check remaining length length 86 | int rem_len_len; 87 | lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len); 88 | if (err == LWMQTT_VARNUM_OVERFLOW) { 89 | return LWMQTT_REMAINING_LENGTH_OVERFLOW; 90 | } 91 | 92 | // prepare header 93 | uint8_t header = 0; 94 | lwmqtt_write_bits(&header, LWMQTT_CONNECT_PACKET, 4, 4); 95 | 96 | // write header 97 | err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 98 | if (err != LWMQTT_SUCCESS) { 99 | return err; 100 | } 101 | 102 | // write remaining length 103 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len); 104 | if (err != LWMQTT_SUCCESS) { 105 | return err; 106 | } 107 | 108 | // write version string 109 | err = lwmqtt_write_string(&buf_ptr, buf_end, lwmqtt_string("MQTT")); 110 | if (err != LWMQTT_SUCCESS) { 111 | return err; 112 | } 113 | 114 | // write version number 115 | err = lwmqtt_write_byte(&buf_ptr, buf_end, 4); 116 | if (err != LWMQTT_SUCCESS) { 117 | return err; 118 | } 119 | 120 | // prepare flags 121 | uint8_t flags = 0; 122 | 123 | // set clean session 124 | lwmqtt_write_bits(&flags, (uint8_t)(options->clean_session), 1, 1); 125 | 126 | // set will flags if present 127 | if (will != NULL) { 128 | lwmqtt_write_bits(&flags, 1, 2, 1); 129 | lwmqtt_write_bits(&flags, will->qos, 3, 2); 130 | lwmqtt_write_bits(&flags, (uint8_t)(will->retained), 5, 1); 131 | } 132 | 133 | // set username flag if username or password is present 134 | if (options->username.len > 0 || options->password.len > 0) { 135 | lwmqtt_write_bits(&flags, 1, 7, 1); 136 | 137 | // set password flag if present 138 | if (options->password.len > 0) { 139 | lwmqtt_write_bits(&flags, 1, 6, 1); 140 | } 141 | } 142 | 143 | // write flags 144 | err = lwmqtt_write_byte(&buf_ptr, buf_end, flags); 145 | if (err != LWMQTT_SUCCESS) { 146 | return err; 147 | } 148 | 149 | // write keep alive 150 | err = lwmqtt_write_num(&buf_ptr, buf_end, options->keep_alive); 151 | if (err != LWMQTT_SUCCESS) { 152 | return err; 153 | } 154 | 155 | // write client id 156 | err = lwmqtt_write_string(&buf_ptr, buf_end, options->client_id); 157 | if (err != LWMQTT_SUCCESS) { 158 | return err; 159 | } 160 | 161 | // write will if present 162 | if (will != NULL) { 163 | // write topic 164 | err = lwmqtt_write_string(&buf_ptr, buf_end, will->topic); 165 | if (err != LWMQTT_SUCCESS) { 166 | return err; 167 | } 168 | 169 | // write payload length 170 | err = lwmqtt_write_num(&buf_ptr, buf_end, (uint16_t)will->payload.len); 171 | if (err != LWMQTT_SUCCESS) { 172 | return err; 173 | } 174 | 175 | // write payload 176 | err = lwmqtt_write_data(&buf_ptr, buf_end, (uint8_t *)will->payload.data, will->payload.len); 177 | if (err != LWMQTT_SUCCESS) { 178 | return err; 179 | } 180 | } 181 | 182 | // write username if username of password is present 183 | if (options->username.len > 0 || options->password.len > 0) { 184 | err = lwmqtt_write_string(&buf_ptr, buf_end, options->username); 185 | if (err != LWMQTT_SUCCESS) { 186 | return err; 187 | } 188 | } 189 | 190 | // write password if present 191 | if (options->username.len > 0 && options->password.len > 0) { 192 | err = lwmqtt_write_string(&buf_ptr, buf_end, options->password); 193 | if (err != LWMQTT_SUCCESS) { 194 | return err; 195 | } 196 | } 197 | 198 | // set written length 199 | *len = buf_ptr - buf; 200 | 201 | return LWMQTT_SUCCESS; 202 | } 203 | 204 | lwmqtt_err_t lwmqtt_decode_connack(uint8_t *buf, size_t buf_len, bool *session_present, 205 | lwmqtt_return_code_t *return_code) { 206 | // prepare pointers 207 | uint8_t *buf_ptr = buf; 208 | uint8_t *buf_end = buf + buf_len; 209 | 210 | // read header 211 | uint8_t header; 212 | lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header); 213 | if (err != LWMQTT_SUCCESS) { 214 | return err; 215 | } 216 | 217 | // check packet type 218 | if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_CONNACK_PACKET) { 219 | return LWMQTT_MISSING_OR_WRONG_PACKET; 220 | } 221 | 222 | // read remaining length 223 | uint32_t rem_len; 224 | err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len); 225 | if (err != LWMQTT_SUCCESS) { 226 | return err; 227 | } 228 | 229 | // check remaining length 230 | if (rem_len != 2) { 231 | return LWMQTT_REMAINING_LENGTH_MISMATCH; 232 | } 233 | 234 | // read flags 235 | uint8_t flags; 236 | err = lwmqtt_read_byte(&buf_ptr, buf_end, &flags); 237 | if (err != LWMQTT_SUCCESS) { 238 | return err; 239 | } 240 | 241 | // read return code 242 | uint8_t raw_return_code; 243 | err = lwmqtt_read_byte(&buf_ptr, buf_end, &raw_return_code); 244 | if (err != LWMQTT_SUCCESS) { 245 | return err; 246 | } 247 | 248 | // get session present 249 | *session_present = lwmqtt_read_bits(flags, 0, 1) == 1; 250 | 251 | // get return code 252 | switch (raw_return_code) { 253 | case 0: 254 | *return_code = LWMQTT_CONNECTION_ACCEPTED; 255 | break; 256 | case 1: 257 | *return_code = LWMQTT_UNACCEPTABLE_PROTOCOL; 258 | break; 259 | case 2: 260 | *return_code = LWMQTT_IDENTIFIER_REJECTED; 261 | break; 262 | case 3: 263 | *return_code = LWMQTT_SERVER_UNAVAILABLE; 264 | break; 265 | case 4: 266 | *return_code = LWMQTT_BAD_USERNAME_OR_PASSWORD; 267 | break; 268 | case 5: 269 | *return_code = LWMQTT_NOT_AUTHORIZED; 270 | break; 271 | default: 272 | *return_code = LWMQTT_UNKNOWN_RETURN_CODE; 273 | } 274 | 275 | return LWMQTT_SUCCESS; 276 | } 277 | 278 | lwmqtt_err_t lwmqtt_encode_zero(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type) { 279 | // prepare pointer 280 | uint8_t *buf_ptr = buf; 281 | uint8_t *buf_end = buf + buf_len; 282 | 283 | // write header 284 | uint8_t header = 0; 285 | lwmqtt_write_bits(&header, packet_type, 4, 4); 286 | lwmqtt_err_t err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 287 | if (err != LWMQTT_SUCCESS) { 288 | return err; 289 | } 290 | 291 | // write remaining length 292 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, 0); 293 | if (err != LWMQTT_SUCCESS) { 294 | return err; 295 | } 296 | 297 | // set length 298 | *len = buf_ptr - buf; 299 | 300 | return LWMQTT_SUCCESS; 301 | } 302 | 303 | lwmqtt_err_t lwmqtt_decode_ack(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t packet_type, uint16_t *packet_id) { 304 | // prepare pointer 305 | uint8_t *buf_ptr = buf; 306 | uint8_t *buf_end = buf + buf_len; 307 | 308 | // read header 309 | uint8_t header = 0; 310 | lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header); 311 | if (err != LWMQTT_SUCCESS) { 312 | return err; 313 | } 314 | 315 | // check packet type 316 | if (lwmqtt_read_bits(header, 4, 4) != packet_type) { 317 | return LWMQTT_MISSING_OR_WRONG_PACKET; 318 | } 319 | 320 | // read remaining length 321 | uint32_t rem_len; 322 | err = lwmqtt_read_varnum(&buf_ptr, buf + buf_len, &rem_len); 323 | if (err != LWMQTT_SUCCESS) { 324 | return err; 325 | } 326 | 327 | // check remaining length 328 | if (rem_len != 2) { 329 | return LWMQTT_REMAINING_LENGTH_MISMATCH; 330 | } 331 | 332 | // read packet id 333 | err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id); 334 | if (err != LWMQTT_SUCCESS) { 335 | return err; 336 | } 337 | 338 | return LWMQTT_SUCCESS; 339 | } 340 | 341 | lwmqtt_err_t lwmqtt_encode_ack(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type, 342 | uint16_t packet_id) { 343 | // prepare pointer 344 | uint8_t *buf_ptr = buf; 345 | uint8_t *buf_end = buf + buf_len; 346 | 347 | // prepare header 348 | uint8_t header = 0; 349 | 350 | // set packet type 351 | lwmqtt_write_bits(&header, packet_type, 4, 4); 352 | 353 | // set qos 354 | lwmqtt_write_bits(&header, (uint8_t)(packet_type == LWMQTT_PUBREL_PACKET ? LWMQTT_QOS1 : LWMQTT_QOS0), 1, 2); 355 | 356 | // write header 357 | lwmqtt_err_t err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 358 | if (err != LWMQTT_SUCCESS) { 359 | return err; 360 | } 361 | 362 | // write remaining length 363 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, 2); 364 | if (err != LWMQTT_SUCCESS) { 365 | return err; 366 | } 367 | 368 | // write packet id 369 | err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id); 370 | if (err != LWMQTT_SUCCESS) { 371 | return err; 372 | } 373 | 374 | // set written length 375 | *len = buf_ptr - buf; 376 | 377 | return LWMQTT_SUCCESS; 378 | } 379 | 380 | lwmqtt_err_t lwmqtt_decode_publish(uint8_t *buf, size_t buf_len, bool *dup, uint16_t *packet_id, lwmqtt_string_t *topic, 381 | lwmqtt_message_t *msg) { 382 | // prepare pointer 383 | uint8_t *buf_ptr = buf; 384 | uint8_t *buf_end = buf + buf_len; 385 | 386 | // read header 387 | uint8_t header; 388 | lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header); 389 | if (err != LWMQTT_SUCCESS) { 390 | return err; 391 | } 392 | 393 | // check packet type 394 | if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_PUBLISH_PACKET) { 395 | return LWMQTT_MISSING_OR_WRONG_PACKET; 396 | } 397 | 398 | // get dup 399 | *dup = lwmqtt_read_bits(header, 3, 1) == 1; 400 | 401 | // get retained 402 | msg->retained = lwmqtt_read_bits(header, 0, 1) == 1; 403 | 404 | // get qos 405 | switch (lwmqtt_read_bits(header, 1, 2)) { 406 | case 0: 407 | msg->qos = LWMQTT_QOS0; 408 | break; 409 | case 1: 410 | msg->qos = LWMQTT_QOS1; 411 | break; 412 | case 2: 413 | msg->qos = LWMQTT_QOS2; 414 | break; 415 | default: 416 | msg->qos = LWMQTT_QOS0; 417 | break; 418 | } 419 | 420 | // read remaining length 421 | uint32_t rem_len; 422 | err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len); 423 | if (err != LWMQTT_SUCCESS) { 424 | return err; 425 | } 426 | 427 | // check remaining length (topic length) 428 | if (rem_len < 2) { 429 | return LWMQTT_REMAINING_LENGTH_MISMATCH; 430 | } 431 | 432 | // check buffer capacity 433 | if ((uint32_t)(buf_end - buf_ptr) < rem_len) { 434 | return LWMQTT_BUFFER_TOO_SHORT; 435 | } 436 | 437 | // reset buf end 438 | buf_end = buf_ptr + rem_len; 439 | 440 | // read topic 441 | err = lwmqtt_read_string(&buf_ptr, buf_end, topic); 442 | if (err != LWMQTT_SUCCESS) { 443 | return err; 444 | } 445 | 446 | // read packet id if qos is at least 1 447 | if (msg->qos > 0) { 448 | err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id); 449 | if (err != LWMQTT_SUCCESS) { 450 | return err; 451 | } 452 | } else { 453 | *packet_id = 0; 454 | } 455 | 456 | // set payload length 457 | msg->payload_len = buf_end - buf_ptr; 458 | 459 | // read payload 460 | err = lwmqtt_read_data(&buf_ptr, buf_end, &msg->payload, buf_end - buf_ptr); 461 | if (err != LWMQTT_SUCCESS) { 462 | return err; 463 | } 464 | 465 | return LWMQTT_SUCCESS; 466 | } 467 | 468 | lwmqtt_err_t lwmqtt_encode_publish(uint8_t *buf, size_t buf_len, size_t *len, bool dup, uint16_t packet_id, 469 | lwmqtt_string_t topic, lwmqtt_message_t msg) { 470 | // prepare pointer 471 | uint8_t *buf_ptr = buf; 472 | uint8_t *buf_end = buf + buf_len; 473 | 474 | // calculate remaining length 475 | uint32_t rem_len = 2 + topic.len + (uint32_t)msg.payload_len; 476 | if (msg.qos > 0) { 477 | rem_len += 2; 478 | } 479 | 480 | // check remaining length length 481 | int rem_len_len; 482 | lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len); 483 | if (err == LWMQTT_VARNUM_OVERFLOW) { 484 | return LWMQTT_REMAINING_LENGTH_OVERFLOW; 485 | } 486 | 487 | // prepare header 488 | uint8_t header = 0; 489 | 490 | // set packet type 491 | lwmqtt_write_bits(&header, LWMQTT_PUBLISH_PACKET, 4, 4); 492 | 493 | // set dup 494 | lwmqtt_write_bits(&header, (uint8_t)(dup), 3, 1); 495 | 496 | // set qos 497 | lwmqtt_write_bits(&header, msg.qos, 1, 2); 498 | 499 | // set retained 500 | lwmqtt_write_bits(&header, (uint8_t)(msg.retained), 0, 1); 501 | 502 | // write header 503 | err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 504 | if (err != LWMQTT_SUCCESS) { 505 | return err; 506 | } 507 | 508 | // write remaining length 509 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len); 510 | if (err != LWMQTT_SUCCESS) { 511 | return err; 512 | } 513 | 514 | // write topic 515 | err = lwmqtt_write_string(&buf_ptr, buf_end, topic); 516 | if (err != LWMQTT_SUCCESS) { 517 | return err; 518 | } 519 | 520 | // write packet id if qos is at least 1 521 | if (msg.qos > 0) { 522 | err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id); 523 | if (err != LWMQTT_SUCCESS) { 524 | return err; 525 | } 526 | } 527 | 528 | // set length 529 | *len = buf_ptr - buf; 530 | 531 | return LWMQTT_SUCCESS; 532 | } 533 | 534 | lwmqtt_err_t lwmqtt_encode_subscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count, 535 | lwmqtt_string_t *topic_filters, lwmqtt_qos_t *qos_levels) { 536 | // prepare pointer 537 | uint8_t *buf_ptr = buf; 538 | uint8_t *buf_end = buf + buf_len; 539 | 540 | // calculate remaining length 541 | uint32_t rem_len = 2; 542 | for (int i = 0; i < count; i++) { 543 | rem_len += 2 + topic_filters[i].len + 1; 544 | } 545 | 546 | // check remaining length length 547 | int rem_len_len; 548 | lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len); 549 | if (err == LWMQTT_VARNUM_OVERFLOW) { 550 | return LWMQTT_REMAINING_LENGTH_OVERFLOW; 551 | } 552 | 553 | // prepare header 554 | uint8_t header = 0; 555 | 556 | // set packet type 557 | lwmqtt_write_bits(&header, LWMQTT_SUBSCRIBE_PACKET, 4, 4); 558 | 559 | // set qos 560 | lwmqtt_write_bits(&header, LWMQTT_QOS1, 1, 2); 561 | 562 | // write header 563 | err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 564 | if (err != LWMQTT_SUCCESS) { 565 | return err; 566 | } 567 | 568 | // write remaining length 569 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len); 570 | if (err != LWMQTT_SUCCESS) { 571 | return err; 572 | } 573 | 574 | // write packet id 575 | err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id); 576 | if (err != LWMQTT_SUCCESS) { 577 | return err; 578 | } 579 | 580 | // write all subscriptions 581 | for (int i = 0; i < count; i++) { 582 | // write topic 583 | err = lwmqtt_write_string(&buf_ptr, buf_end, topic_filters[i]); 584 | if (err != LWMQTT_SUCCESS) { 585 | return err; 586 | } 587 | 588 | // write qos level 589 | err = lwmqtt_write_byte(&buf_ptr, buf_end, (uint8_t)qos_levels[i]); 590 | if (err != LWMQTT_SUCCESS) { 591 | return err; 592 | } 593 | } 594 | 595 | // set length 596 | *len = buf_ptr - buf; 597 | 598 | return LWMQTT_SUCCESS; 599 | } 600 | 601 | lwmqtt_err_t lwmqtt_decode_suback(uint8_t *buf, size_t buf_len, uint16_t *packet_id, int max_count, int *count, 602 | lwmqtt_qos_t *granted_qos_levels) { 603 | // prepare pointer 604 | uint8_t *buf_ptr = buf; 605 | uint8_t *buf_end = buf + buf_len; 606 | 607 | // read header 608 | uint8_t header; 609 | lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header); 610 | if (err != LWMQTT_SUCCESS) { 611 | return err; 612 | } 613 | 614 | // check packet type 615 | if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_SUBACK_PACKET) { 616 | return LWMQTT_MISSING_OR_WRONG_PACKET; 617 | } 618 | 619 | // read remaining length 620 | uint32_t rem_len; 621 | err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len); 622 | if (err != LWMQTT_SUCCESS) { 623 | return err; 624 | } 625 | 626 | // check remaining length (packet id + min. one suback code) 627 | if (rem_len < 3) { 628 | return LWMQTT_REMAINING_LENGTH_MISMATCH; 629 | } 630 | 631 | // read packet id 632 | err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id); 633 | if (err != LWMQTT_SUCCESS) { 634 | return err; 635 | } 636 | 637 | // read all suback codes 638 | for (*count = 0; *count < (int)rem_len - 2; (*count)++) { 639 | // check max count 640 | if (*count > max_count) { 641 | return LWMQTT_SUBACK_ARRAY_OVERFLOW; 642 | } 643 | 644 | // read qos level 645 | uint8_t raw_qos_level; 646 | err = lwmqtt_read_byte(&buf_ptr, buf_end, &raw_qos_level); 647 | if (err != LWMQTT_SUCCESS) { 648 | return err; 649 | } 650 | 651 | // set qos level 652 | switch (raw_qos_level) { 653 | case 0: 654 | granted_qos_levels[*count] = LWMQTT_QOS0; 655 | break; 656 | case 1: 657 | granted_qos_levels[*count] = LWMQTT_QOS1; 658 | break; 659 | case 2: 660 | granted_qos_levels[*count] = LWMQTT_QOS2; 661 | break; 662 | default: 663 | granted_qos_levels[*count] = LWMQTT_QOS_FAILURE; 664 | break; 665 | } 666 | } 667 | 668 | return LWMQTT_SUCCESS; 669 | } 670 | 671 | lwmqtt_err_t lwmqtt_encode_unsubscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count, 672 | lwmqtt_string_t *topic_filters) { 673 | // prepare pointer 674 | uint8_t *buf_ptr = buf; 675 | uint8_t *buf_end = buf + buf_len; 676 | 677 | // calculate remaining length 678 | uint32_t rem_len = 2; 679 | for (int i = 0; i < count; i++) { 680 | rem_len += 2 + topic_filters[i].len; 681 | } 682 | 683 | // check remaining length length 684 | int rem_len_len; 685 | lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len); 686 | if (err == LWMQTT_VARNUM_OVERFLOW) { 687 | return LWMQTT_REMAINING_LENGTH_OVERFLOW; 688 | } 689 | 690 | // prepare header 691 | uint8_t header = 0; 692 | 693 | // set packet type 694 | lwmqtt_write_bits(&header, LWMQTT_UNSUBSCRIBE_PACKET, 4, 4); 695 | 696 | // set qos 697 | lwmqtt_write_bits(&header, LWMQTT_QOS1, 1, 2); 698 | 699 | // write header 700 | err = lwmqtt_write_byte(&buf_ptr, buf_end, header); 701 | if (err != LWMQTT_SUCCESS) { 702 | return err; 703 | } 704 | 705 | // write remaining length 706 | err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len); 707 | if (err != LWMQTT_SUCCESS) { 708 | return err; 709 | } 710 | 711 | // write packet id 712 | err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id); 713 | if (err != LWMQTT_SUCCESS) { 714 | return err; 715 | } 716 | 717 | // write topics 718 | for (int i = 0; i < count; i++) { 719 | err = lwmqtt_write_string(&buf_ptr, buf_end, topic_filters[i]); 720 | if (err != LWMQTT_SUCCESS) { 721 | return err; 722 | } 723 | } 724 | 725 | // set length 726 | *len = buf_ptr - buf; 727 | 728 | return LWMQTT_SUCCESS; 729 | } 730 | -------------------------------------------------------------------------------- /src/lwmqtt/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef LWMQTT_PACKET_H 2 | #define LWMQTT_PACKET_H 3 | 4 | #include "helpers.h" 5 | 6 | /** 7 | * The available packet types. 8 | */ 9 | typedef enum { 10 | LWMQTT_NO_PACKET = 0, 11 | LWMQTT_CONNECT_PACKET = 1, 12 | LWMQTT_CONNACK_PACKET, 13 | LWMQTT_PUBLISH_PACKET, 14 | LWMQTT_PUBACK_PACKET, 15 | LWMQTT_PUBREC_PACKET, 16 | LWMQTT_PUBREL_PACKET, 17 | LWMQTT_PUBCOMP_PACKET, 18 | LWMQTT_SUBSCRIBE_PACKET, 19 | LWMQTT_SUBACK_PACKET, 20 | LWMQTT_UNSUBSCRIBE_PACKET, 21 | LWMQTT_UNSUBACK_PACKET, 22 | LWMQTT_PINGREQ_PACKET, 23 | LWMQTT_PINGRESP_PACKET, 24 | LWMQTT_DISCONNECT_PACKET 25 | } lwmqtt_packet_type_t; 26 | 27 | /** 28 | * Will detect the packet type from the at least one byte long buffer. 29 | * 30 | * @param buf The buffer from which the packet type will be detected. 31 | * @param buf_len The length of the specified buffer. 32 | * @param packet_type The packet type. 33 | * @return An error value. 34 | */ 35 | lwmqtt_err_t lwmqtt_detect_packet_type(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t *packet_type); 36 | 37 | /** 38 | * Will detect the remaining length form the at least on byte long buffer. 39 | * 40 | * It will return LWMQTT_BUFFER_TOO_SHORT if the buffer is to short and an additional byte should be read from the 41 | * network. In case the remaining length is overflowed it will return LWMQTT_REMAINING_LENGTH_OVERFLOW. 42 | * 43 | * @param buf The buffer from which the remaining length will be detected. 44 | * @param buf_len The length of the specified buffer. 45 | * @param rem_len The detected remaining length. 46 | * @return An error value. 47 | */ 48 | lwmqtt_err_t lwmqtt_detect_remaining_length(uint8_t *buf, size_t buf_len, uint32_t *rem_len); 49 | 50 | /** 51 | * Encodes a connect packet into the supplied buffer. 52 | * 53 | * @param buf The buffer into which the packet will be encoded. 54 | * @param buf_len The length of the specified buffer. 55 | * @param len The encoded length of the packet. 56 | * @param options The options to be used to build the connect packet. 57 | * @param will The last will and testament. 58 | * @return An error value. 59 | */ 60 | lwmqtt_err_t lwmqtt_encode_connect(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_connect_options_t *options, 61 | lwmqtt_will_t *will); 62 | 63 | /** 64 | * Decodes a connack packet from the supplied buffer. 65 | * 66 | * @param buf The raw buffer data. 67 | * @param buf_len The length of the specified buffer. 68 | * @param session_present The session present flag. 69 | * @param return_code The return code. 70 | * @return An error value. 71 | */ 72 | lwmqtt_err_t lwmqtt_decode_connack(uint8_t *buf, size_t buf_len, bool *session_present, 73 | lwmqtt_return_code_t *return_code); 74 | 75 | /** 76 | * Encodes a zero (disconnect, pingreq) packet into the supplied buffer. 77 | * 78 | * @param buf The buffer into which the packet will be encoded. 79 | * @param buf_len The length of the specified buffer. 80 | * @param len The encoded length of the packet. 81 | * @param packet_type The packet type. 82 | * @return An error value. 83 | */ 84 | lwmqtt_err_t lwmqtt_encode_zero(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type); 85 | 86 | /** 87 | * Decodes an ack (puback, pubrec, pubrel, pubcomp, unsuback) packet from the supplied buffer. 88 | * 89 | * @param buf The raw buffer data. 90 | * @param buf_len The length of the specified buffer. 91 | * @param packet_type The packet type. 92 | * @param packet_id The packet id. 93 | * @return An error value. 94 | */ 95 | lwmqtt_err_t lwmqtt_decode_ack(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t packet_type, uint16_t *packet_id); 96 | 97 | /** 98 | * Encodes an ack (puback, pubrec, pubrel, pubcomp) packet into the supplied buffer. 99 | * 100 | * @param buf The buffer into which the packet will be encoded. 101 | * @param buf_len The length of the specified buffer. 102 | * @param len The encoded length of the packet. 103 | * @param packet_type The packet type. 104 | * @param packet_id The packet id. 105 | * @return An error value. 106 | */ 107 | lwmqtt_err_t lwmqtt_encode_ack(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type, 108 | uint16_t packet_id); 109 | 110 | /** 111 | * Decodes a publish packet from the supplied buffer. 112 | * 113 | * @param buf The raw buffer data. 114 | * @param buf_len The length of the specified buffer. 115 | * @param dup The dup flag. 116 | * @param packet_id The packet id. 117 | * @param topic The topic. 118 | * @parma msg The message. 119 | * @return An error value. 120 | */ 121 | lwmqtt_err_t lwmqtt_decode_publish(uint8_t *buf, size_t buf_len, bool *dup, uint16_t *packet_id, lwmqtt_string_t *topic, 122 | lwmqtt_message_t *msg); 123 | 124 | /** 125 | * Encodes a publish packet into the supplied buffer. 126 | * 127 | * Note: The payload is not written to the buffer and the reported encoded 128 | * length does not include the payload size. 129 | * 130 | * @param buf The buffer into which the packet will be encoded. 131 | * @param buf_len The length of the specified buffer. 132 | * @param len The encoded length of the packet. 133 | * @param dup The dup flag. 134 | * @param packet_id The packet id. 135 | * @param topic The topic. 136 | * @param msg The message. 137 | * @return An error value. 138 | */ 139 | lwmqtt_err_t lwmqtt_encode_publish(uint8_t *buf, size_t buf_len, size_t *len, bool dup, uint16_t packet_id, 140 | lwmqtt_string_t topic, lwmqtt_message_t msg); 141 | 142 | /** 143 | * Encodes a subscribe packet into the supplied buffer. 144 | * 145 | * @param buf The buffer into which the packet will be encoded. 146 | * @param buf_len The length of the specified buffer. 147 | * @param len The encoded length of the packet. 148 | * @param packet_id The packet id. 149 | * @param count The number of members in the topic_filters and qos_levels array. 150 | * @param topic_filters The array of topic filter. 151 | * @param qos_levels The array of requested QoS levels. 152 | * @return An error value. 153 | */ 154 | lwmqtt_err_t lwmqtt_encode_subscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count, 155 | lwmqtt_string_t *topic_filters, lwmqtt_qos_t *qos_levels); 156 | 157 | /** 158 | * Decodes a suback packet from the supplied buffer. 159 | * 160 | * @param buf The raw buffer data. 161 | * @param buf_len The length of the specified buffer. 162 | * @param packet_id The packet id. 163 | * @param max_count The maximum number of members allowed in the granted_qos_levels array. 164 | * @param count The number of members in the granted_qos_levels array. 165 | * @param granted_qos_levels The granted QoS levels. 166 | * @return An error value. 167 | */ 168 | lwmqtt_err_t lwmqtt_decode_suback(uint8_t *buf, size_t buf_len, uint16_t *packet_id, int max_count, int *count, 169 | lwmqtt_qos_t *granted_qos_levels); 170 | 171 | /** 172 | * Encodes the supplied unsubscribe data into the supplied buffer, ready for sending 173 | * 174 | * @param buf The buffer into which the packet will be encoded. 175 | * @param buf_len The length of the specified buffer. 176 | * @param len The encoded length of the packet. 177 | * @param packet_id The packet id. 178 | * @param count The number of members in the topic_filters array. 179 | * @param topic_filters The array of topic filters. 180 | * @return An error value. 181 | */ 182 | lwmqtt_err_t lwmqtt_encode_unsubscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count, 183 | lwmqtt_string_t *topic_filters); 184 | 185 | #endif // LWMQTT_PACKET_H 186 | -------------------------------------------------------------------------------- /src/lwmqtt/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "lwmqtt.h" 4 | 5 | lwmqtt_string_t lwmqtt_string(const char *str) { 6 | // check for null 7 | if (str == NULL) { 8 | return (lwmqtt_string_t){0, NULL}; 9 | } 10 | 11 | // get length 12 | uint16_t len = (uint16_t)strlen(str); 13 | 14 | // check zero length 15 | if (len == 0) { 16 | return (lwmqtt_string_t){0, NULL}; 17 | } 18 | 19 | return (lwmqtt_string_t){len, (char *)str}; 20 | } 21 | 22 | int lwmqtt_strcmp(lwmqtt_string_t a, const char *b) { 23 | // get string of b 24 | lwmqtt_string_t b_str = lwmqtt_string(b); 25 | 26 | // return if both are zero length 27 | if (a.len == 0 && b_str.len == 0) { 28 | return 0; 29 | } 30 | 31 | // return if lengths are different 32 | if (a.len != b_str.len) { 33 | return -1; 34 | } 35 | 36 | // compare memory of same length 37 | return strncmp(a.data, b_str.data, a.len); 38 | } 39 | --------------------------------------------------------------------------------