├── 0.01 ├── NetduinoMQTT.cs └── Program.cs ├── 0.02 ├── NetduinoMQTT.cs └── Program.cs ├── CHANGELOG ├── LICENSE └── README /0.01/NetduinoMQTT.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2011-2012 Dan Anderson. All rights reserved. 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY DAN ANDERSON ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 17 | SHALL Dan Anderson OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 | THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | */ 26 | 27 | using System; 28 | using System.Net; 29 | using System.Net.Sockets; 30 | using System.Text; 31 | using Microsoft.SPOT; 32 | using Socket = System.Net.Sockets.Socket; 33 | 34 | namespace Netduino_MQTT_Client_Library 35 | { 36 | static class Constants 37 | { 38 | 39 | // TODO: Add other constants so we don't have "magic" numbers 40 | public const int MQTTPROTOCOLVERSION = 3; 41 | public const int MAXLENGTH = 268435455; // 256MB 42 | public const int MAX_CLIENTID = 23; 43 | public const int MIN_CLIENTID = 1; 44 | public const int MAX_KEEPALIVE = 65535; 45 | public const int MIN_KEEPALIVE = 0; 46 | public const int MAX_USERNAME = 65535; 47 | public const int MAX_PASSWORD = 65535; 48 | public const int MAX_TOPIC_LENGTH = 32767; 49 | public const int MIN_TOPIC_LENGTH = 1; 50 | 51 | // Error Codes 52 | public const int CLIENTID_LENGTH_ERROR = 1; 53 | public const int KEEPALIVE_LENGTH_ERROR = 1; 54 | public const int MESSAGE_LENGTH_ERROR = 1; 55 | public const int TOPIC_LENGTH_ERROR = 1; 56 | public const int TOPIC_WILDCARD_ERROR = 1; 57 | public const int USERNAME_LENGTH_ERROR = 1; 58 | public const int PASSWORD_LENGTH_ERROR = 1; 59 | public const int CONNECTION_ERROR = 1; 60 | 61 | public const int CONNECTION_OK = 0; 62 | 63 | public const int CONNACK_LENGTH = 4; 64 | public const int PINGRESP_LENGTH = 2; 65 | 66 | public const byte MQTT_CONN_OK = 0x00; // Connection Accepted 67 | public const byte MQTT_CONN_BAD_PROTOCOL_VERSION = 0x01; // Connection Refused: unacceptable protocol version 68 | public const byte MQTT_CONN_BAD_IDENTIFIER = 0x02; // Connection Refused: identifier rejected 69 | public const byte MQTT_CONN_SERVER_UNAVAILABLE = 0x03; // Connection Refused: server unavailable 70 | public const byte MQTT_CONN_BAD_AUTH = 0x04; // Connection Refused: bad user name or password 71 | public const byte MQTT_CONN_NOT_AUTH = 0x05; // Connection Refused: not authorized 72 | 73 | // Message types 74 | public const int MQTT_CONNECT_TYPE = 0x10; 75 | public const int MQTT_CONNACK_TYPE = 0x20; 76 | public const int MQTT_PUBLISH_TYPE = 0x30; 77 | public const int MQTT_PING_REQ_TYPE = 0xc0; 78 | public const int MQTT_PING_RESP_TYPE = 0xd0; 79 | public const int MQTT_DISCONNECT_TYPE = 0xe0; 80 | 81 | // Flags 82 | public const int CLEAN_SESSION_FLAG = 0x02; 83 | public const int USING_USERNAME_FLAG = 0x80; 84 | public const int USING_PASSWORD_FLAG = 0x40; 85 | public const int CONTINUATION_BIT = 0x80; 86 | } 87 | 88 | public static class NetduinoMQTT 89 | { 90 | 91 | public static int ConnectMQTT(Socket mySocket, String clientID, int keepAlive = 20, bool cleanSession = true, String username = "", String password = "") 92 | { 93 | 94 | int index = 0; 95 | int tmp = 0; 96 | int digit = 0; 97 | int remainingLength = 0; 98 | int fixedHeader = 0; 99 | int varHeader = 0; 100 | int payload = 0; 101 | int returnCode = 0; 102 | bool usingUsername = false; 103 | bool usingPassword = false; 104 | byte connectFlags = 0x00; 105 | byte[] buffer = null; 106 | 107 | UTF8Encoding encoder = new UTF8Encoding(); 108 | 109 | byte[] utf8ClientID = Encoding.UTF8.GetBytes(clientID); 110 | byte[] utf8Username = Encoding.UTF8.GetBytes(username); 111 | byte[] utf8Password = Encoding.UTF8.GetBytes(password); 112 | 113 | // Some Error Checking 114 | // ClientID improperly sized 115 | if ((utf8ClientID.Length > Constants.MAX_CLIENTID) || (utf8ClientID.Length < Constants.MIN_CLIENTID)) 116 | return Constants.CLIENTID_LENGTH_ERROR; 117 | // KeepAlive out of bounds 118 | if ((keepAlive > Constants.MAX_KEEPALIVE) || (keepAlive < Constants.MIN_KEEPALIVE)) 119 | return Constants.KEEPALIVE_LENGTH_ERROR; 120 | // Username too long 121 | if (utf8Username.Length > Constants.MAX_USERNAME) 122 | return Constants.USERNAME_LENGTH_ERROR; 123 | // Password too long 124 | if (utf8Password.Length > Constants.MAX_PASSWORD) 125 | return Constants.PASSWORD_LENGTH_ERROR; 126 | 127 | // Check features being used 128 | if (!username.Equals("")) 129 | usingUsername = true; 130 | if (!password.Equals("")) 131 | usingPassword = true; 132 | 133 | // Calculate the size of the var header 134 | varHeader += 2; // Protocol Name Length 135 | varHeader += 6; // Protocol Name 136 | varHeader++; // Protocol version 137 | varHeader++; // Connect Flags 138 | varHeader += 2; // Keep Alive 139 | 140 | // Calculate the size of the fixed header 141 | fixedHeader++; // byte 1 142 | 143 | // Calculate the payload 144 | payload = utf8ClientID.Length + 2; 145 | if (usingUsername) 146 | { 147 | payload += utf8Username.Length + 2; 148 | } 149 | if (usingPassword) 150 | { 151 | payload += utf8Password.Length + 2; 152 | } 153 | 154 | // Calculate the remaining size 155 | remainingLength = varHeader + payload; 156 | 157 | // Check that remaining length will fit into 4 encoded bytes 158 | if (remainingLength > Constants.MAXLENGTH) 159 | return Constants.MESSAGE_LENGTH_ERROR; 160 | 161 | tmp = remainingLength; 162 | 163 | // Add space for each byte we need in the fixed header to store the length 164 | while (tmp > 0) 165 | { 166 | fixedHeader++; 167 | tmp = tmp / 128; 168 | }; 169 | // End of Fixed Header 170 | 171 | // Build buffer for message 172 | buffer = new byte[fixedHeader + varHeader + payload]; 173 | 174 | // Fixed Header (2.1) 175 | buffer[index++] = Constants.MQTT_CONNECT_TYPE; 176 | 177 | // Encode the fixed header remaining length 178 | tmp = remainingLength; 179 | do 180 | { 181 | digit = tmp % 128; 182 | tmp = tmp / 128; 183 | if (tmp > 0) 184 | { 185 | digit = digit | Constants.CONTINUATION_BIT; 186 | } 187 | buffer[index++] = (byte)digit; 188 | } while (tmp > 0); 189 | // End Fixed Header 190 | 191 | // Connect (3.1) 192 | // Protocol Name 193 | buffer[index++] = 0; // String (MQIsdp) Length MSB - always 6 so, zeroed 194 | buffer[index++] = 6; // Length LSB 195 | buffer[index++] = (byte)'M'; // M 196 | buffer[index++] = (byte)'Q'; // Q 197 | buffer[index++] = (byte)'I'; // I 198 | buffer[index++] = (byte)'s'; // s 199 | buffer[index++] = (byte)'d'; // d 200 | buffer[index++] = (byte)'p'; // p 201 | 202 | // Protocol Version 203 | buffer[index++] = Constants.MQTTPROTOCOLVERSION; 204 | 205 | // Connect Flags 206 | // TODO: re-read the spec to figure out what the "will" stuff is about 207 | if (cleanSession) 208 | connectFlags |= (byte)Constants.CLEAN_SESSION_FLAG; 209 | if (usingUsername) 210 | connectFlags |= (byte)Constants.USING_USERNAME_FLAG; 211 | if (usingPassword) 212 | connectFlags |= (byte)Constants.USING_PASSWORD_FLAG; 213 | 214 | // Set the connect flags 215 | buffer[index++] = connectFlags; 216 | 217 | // Keep alive (defaulted to 20 seconds above) 218 | buffer[index++] = (byte)(keepAlive / 256); // Keep Alive MSB 219 | buffer[index++] = (byte)(keepAlive % 256); // Keep Alive LSB 220 | 221 | // ClientID 222 | buffer[index++] = (byte)(utf8ClientID.Length / 256); // Length MSB 223 | buffer[index++] = (byte)(utf8ClientID.Length % 256); // Length LSB 224 | for (var i = 0; i < utf8ClientID.Length; i++) 225 | { 226 | buffer[index++] = utf8ClientID[i]; 227 | } 228 | 229 | // Username 230 | if (usingUsername) 231 | { 232 | buffer[index++] = (byte)(utf8Username.Length / 256); // Length MSB 233 | buffer[index++] = (byte)(utf8Username.Length % 256); // Length LSB 234 | 235 | for (var i = 0; i < utf8Username.Length; i++) 236 | { 237 | buffer[index++] = utf8Username[i]; 238 | } 239 | } 240 | 241 | // Password 242 | if (usingPassword) 243 | { 244 | buffer[index++] = (byte)(utf8Password.Length / 256); // Length MSB 245 | buffer[index++] = (byte)(utf8Password.Length % 256); // Length LSB 246 | 247 | for (var i = 0; i < utf8Password.Length; i++) 248 | { 249 | buffer[index++] = utf8Password[i]; 250 | } 251 | } 252 | 253 | // Send the message 254 | returnCode = mySocket.Send(buffer, index, 0); 255 | 256 | // The return code should equal our buffer length 257 | if (returnCode != buffer.Length) 258 | { 259 | return Constants.CONNECTION_ERROR; 260 | } 261 | 262 | // Get the acknowledgement message 263 | returnCode = mySocket.Receive(buffer, 0); 264 | 265 | // The length should equal 4 266 | if (returnCode != Constants.CONNACK_LENGTH) 267 | { 268 | return Constants.CONNECTION_ERROR; 269 | } 270 | 271 | // This should be a message type 2 (CONNACK) 272 | if (buffer[0] == Constants.MQTT_CONNACK_TYPE) 273 | { 274 | // This is our return code from the server 275 | return buffer[3]; 276 | } 277 | 278 | // If not zero = return the return code 279 | return Constants.CONNECTION_ERROR; 280 | } 281 | 282 | // Publish a message to a broker (3.3) 283 | public static int PublishMQTT(Socket mySocket, String topic, String message) 284 | { 285 | 286 | int index = 0; 287 | int digit = 0; 288 | int tmp = 0; 289 | int fixedHeader = 0; 290 | int varHeader = 0; 291 | int payload = 0; 292 | int remainingLength = 0; 293 | byte[] buffer = null; 294 | 295 | UTF8Encoding encoder = new UTF8Encoding(); 296 | 297 | byte[] utf8Topic = Encoding.UTF8.GetBytes(topic); 298 | 299 | // Some error checking 300 | 301 | // Topic contains wildcards 302 | if ((topic.IndexOf('#') != -1) || (topic.IndexOf('+') != -1)) 303 | return Constants.TOPIC_WILDCARD_ERROR; 304 | 305 | // Topic is too long or short 306 | if ((utf8Topic.Length > Constants.MAX_TOPIC_LENGTH) || (utf8Topic.Length < Constants.MIN_TOPIC_LENGTH)) 307 | return Constants.TOPIC_LENGTH_ERROR; 308 | 309 | // Calculate the size of the var header 310 | varHeader += 2; // Topic Name Length 311 | varHeader += utf8Topic.Length; // Topic Name 312 | 313 | // Calculate the size of the fixed header 314 | fixedHeader++; // byte 1 315 | 316 | // Calculate the payload 317 | payload = message.Length; 318 | 319 | // Calculate the remaining size 320 | remainingLength = varHeader + payload; 321 | 322 | // Check that remaining length will fit into 4 encoded bytes 323 | if (remainingLength > Constants.MAXLENGTH) 324 | return Constants.MESSAGE_LENGTH_ERROR; 325 | 326 | // Add space for each byte we need in the fixed header to store the length 327 | tmp = remainingLength; 328 | while (tmp > 0) 329 | { 330 | fixedHeader++; 331 | tmp = tmp / 128; 332 | }; 333 | // End of Fixed Header 334 | 335 | // Build buffer for message 336 | buffer = new byte[fixedHeader + varHeader + payload]; 337 | 338 | // Start of Fixed header 339 | // Publish (3.3) 340 | buffer[index++] = Constants.MQTT_PUBLISH_TYPE; 341 | 342 | // Encode the fixed header remaining length 343 | tmp = remainingLength; 344 | do 345 | { 346 | digit = tmp % 128; 347 | tmp = tmp / 128; 348 | if (tmp > 0) 349 | { 350 | digit = digit | 0x80; 351 | } 352 | buffer[index++] = (byte)digit; 353 | } while (tmp > 0); 354 | // End of fixed header 355 | 356 | // Start of Variable header 357 | // Length of topic name 358 | buffer[index++] = (byte)(utf8Topic.Length / 256); // Length MSB 359 | buffer[index++] = (byte)(utf8Topic.Length % 256); // Length LSB 360 | // Topic 361 | for (var i = 0; i < utf8Topic.Length; i++) 362 | { 363 | buffer[index++] = utf8Topic[i]; 364 | } 365 | // End of variable header 366 | 367 | // Start of Payload 368 | // Message (Length is accounted for in the fixed header) 369 | for (var i = 0; i < message.Length; i++) 370 | { 371 | buffer[index++] = (byte)message[i]; 372 | } 373 | // End of Payload 374 | 375 | return mySocket.Send(buffer, buffer.Length, 0); 376 | } 377 | 378 | // Disconnect from broker (3.14) 379 | public static int DisconnectMQTT(Socket mySocket) 380 | { 381 | byte[] buffer = null; 382 | buffer = new byte[2]; 383 | buffer[0] = Constants.MQTT_DISCONNECT_TYPE; 384 | buffer[1] = 0x00; 385 | return mySocket.Send(buffer, buffer.Length, 0); 386 | } 387 | 388 | // Ping the MQTT broker - used to extend keep alive 389 | public static int PingMQTT(Socket mySocket) 390 | { 391 | 392 | int index = 0; 393 | int returnCode = 0; 394 | byte[] buffer = null; 395 | 396 | buffer = new byte[2]; 397 | buffer[index++] = Constants.MQTT_PING_REQ_TYPE; 398 | buffer[index++] = 0x00; 399 | 400 | // Send the ping 401 | returnCode = mySocket.Send(buffer, index, 0); 402 | 403 | // The return code should equal our buffer length 404 | if (returnCode != buffer.Length) 405 | { 406 | return Constants.CONNECTION_ERROR; 407 | } 408 | 409 | // Get the acknowledgement message 410 | returnCode = mySocket.Receive(buffer, 0); 411 | 412 | // The length should equal 2 413 | if (returnCode != Constants.PINGRESP_LENGTH) 414 | { 415 | return Constants.CONNECTION_ERROR; 416 | } 417 | 418 | // This should be a message type 2 (CONNACK) 419 | if (buffer[0] == Constants.MQTT_PING_RESP_TYPE) 420 | { 421 | return 0; 422 | } 423 | return Constants.CONNECTION_ERROR; 424 | } 425 | } 426 | } 427 | 428 | 429 | -------------------------------------------------------------------------------- /0.01/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2011-2012 Dan Anderson. All rights reserved. 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY DAN ANDERSON ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 17 | SHALL Dan Anderson OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 | THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | */ 26 | 27 | using System; 28 | using System.Net; 29 | using System.Net.Sockets; 30 | using System.Threading; 31 | using Microsoft.SPOT; 32 | using Microsoft.SPOT.Hardware; 33 | using SecretLabs.NETMF.Hardware; 34 | using SecretLabs.NETMF.Hardware.NetduinoPlus; 35 | using Netduino_MQTT_Client_Library; 36 | 37 | namespace myNetduinoMQTT 38 | { 39 | public class Program 40 | { 41 | 42 | public static void Main() 43 | { 44 | 45 | // setup our interrupt port (on-board button) 46 | InterruptPort button = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow); 47 | 48 | // assign our interrupt handler 49 | button.OnInterrupt += new NativeEventHandler(button_OnInterrupt); 50 | 51 | // go to sleep until the interrupt wakes us (saves power) 52 | Thread.Sleep(Timeout.Infinite); 53 | } 54 | 55 | // the interrupt handler 56 | static void button_OnInterrupt(uint data1, uint data2, DateTime time) 57 | { 58 | int returnCode = 0; 59 | int connectionError = 0; 60 | // Get broker's IP address. 61 | IPHostEntry hostEntry = Dns.GetHostEntry("192.168.1.106"); 62 | // Create socket and connect to the broker's IP address and port 63 | Socket mySocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); 64 | try 65 | { 66 | mySocket.Connect(new IPEndPoint(hostEntry.AddressList[0], 1883)); 67 | } 68 | catch (SocketException SE) 69 | { 70 | Debug.Print("Connection Error"); 71 | connectionError = 1; 72 | } 73 | if (connectionError != 1) 74 | { 75 | // Send the connect message 76 | returnCode = NetduinoMQTT.ConnectMQTT(mySocket, "tester\u00A5", 2, true, "roger\u00A5", "password\u00A5"); 77 | if (returnCode != 0) 78 | { 79 | Debug.Print("Connection Error:"); 80 | Debug.Print(returnCode.ToString()); 81 | } 82 | else 83 | { 84 | // Send our message 85 | NetduinoMQTT.PublishMQTT(mySocket, "test", "Ow! Quit it!"); 86 | 87 | for (int i = 0; i < 11; i++) 88 | { 89 | returnCode = NetduinoMQTT.PingMQTT(mySocket); 90 | if (returnCode == 0) 91 | { 92 | Debug.Print("Ping Received"); 93 | Thread.Sleep(1000); 94 | } 95 | } 96 | Thread.Sleep(3000); 97 | // Send the disconnect message 98 | NetduinoMQTT.DisconnectMQTT(mySocket); 99 | } 100 | // Close the socket 101 | mySocket.Close(); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /0.02/NetduinoMQTT.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2011-2012 Dan Anderson. All rights reserved. 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY DAN ANDERSON ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 17 | SHALL DAN ANDERSON OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 | THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | */ 26 | 27 | using System; 28 | using System.Net; 29 | using System.Net.Sockets; 30 | using System.Text; 31 | using Microsoft.SPOT; 32 | using Microsoft.SPOT.Hardware; 33 | using Socket = System.Net.Sockets.Socket; 34 | 35 | namespace Netduino_MQTT_Client_Library 36 | { 37 | static class Constants 38 | { 39 | 40 | // These have been scaled down to the hardware 41 | // Maximum values are commented out - you can 42 | // adjust, but keep in mind the limits of the hardware 43 | 44 | public const int MQTTPROTOCOLVERSION = 3; 45 | //public const int MAXLENGTH = 268435455; // 256MB 46 | public const int MAXLENGTH = 10240; // 10K 47 | public const int MAX_CLIENTID = 23; 48 | public const int MIN_CLIENTID = 1; 49 | public const int MAX_KEEPALIVE = 65535; 50 | public const int MIN_KEEPALIVE = 0; 51 | //public const int MAX_USERNAME = 65535; 52 | //public const int MAX_PASSWORD = 65535; 53 | public const int MAX_USERNAME = 12; 54 | public const int MAX_PASSWORD = 12; 55 | //public const int MAX_TOPIC_LENGTH = 32767; 56 | public const int MAX_TOPIC_LENGTH = 256; 57 | public const int MIN_TOPIC_LENGTH = 1; 58 | public const int MAX_MESSAGEID = 65535; 59 | 60 | // Error Codes 61 | public const int CLIENTID_LENGTH_ERROR = 1; 62 | public const int KEEPALIVE_LENGTH_ERROR = 1; 63 | public const int MESSAGE_LENGTH_ERROR = 1; 64 | public const int TOPIC_LENGTH_ERROR = 1; 65 | public const int TOPIC_WILDCARD_ERROR = 1; 66 | public const int USERNAME_LENGTH_ERROR = 1; 67 | public const int PASSWORD_LENGTH_ERROR = 1; 68 | public const int CONNECTION_ERROR = 1; 69 | public const int ERROR = 1; 70 | public const int SUCCESS = 0; 71 | 72 | public const int CONNECTION_OK = 0; 73 | 74 | public const int CONNACK_LENGTH = 4; 75 | public const int PINGRESP_LENGTH = 2; 76 | 77 | public const byte MQTT_CONN_OK = 0x00; // Connection Accepted 78 | public const byte MQTT_CONN_BAD_PROTOCOL_VERSION = 0x01; // Connection Refused: unacceptable protocol version 79 | public const byte MQTT_CONN_BAD_IDENTIFIER = 0x02; // Connection Refused: identifier rejected 80 | public const byte MQTT_CONN_SERVER_UNAVAILABLE = 0x03; // Connection Refused: server unavailable 81 | public const byte MQTT_CONN_BAD_AUTH = 0x04; // Connection Refused: bad user name or password 82 | public const byte MQTT_CONN_NOT_AUTH = 0x05; // Connection Refused: not authorized 83 | 84 | // Message types 85 | public const byte MQTT_CONNECT_TYPE = 0x10; 86 | public const byte MQTT_CONNACK_TYPE = 0x20; 87 | public const byte MQTT_PUBLISH_TYPE = 0x30; 88 | public const byte MQTT_PING_REQ_TYPE = 0xc0; 89 | public const byte MQTT_PING_RESP_TYPE = 0xd0; 90 | public const byte MQTT_DISCONNECT_TYPE = 0xe0; 91 | public const byte MQTT_SUBSCRIBE_TYPE = 0x82; 92 | public const byte MQTT_UNSUBSCRIBE_TYPE = 0xa2; 93 | 94 | // Flags 95 | public const int CLEAN_SESSION_FLAG = 0x02; 96 | public const int USING_USERNAME_FLAG = 0x80; 97 | public const int USING_PASSWORD_FLAG = 0x40; 98 | public const int CONTINUATION_BIT = 0x80; 99 | } 100 | 101 | public static class NetduinoMQTT 102 | { 103 | // Setup our random number generator 104 | private static Random rand = new Random((int)(Utility.GetMachineTime().Ticks & 0xffffffff)); 105 | 106 | //*************************************************************** 107 | // Send Messages 108 | //*************************************************************** 109 | 110 | // Connect to the MQTT Server 111 | public static int ConnectMQTT(Socket mySocket, String clientID, int keepAlive = 20, bool cleanSession = true, String username = "", String password = "") 112 | { 113 | 114 | int index = 0; 115 | int tmp = 0; 116 | int remainingLength = 0; 117 | int fixedHeader = 0; 118 | int varHeader = 0; 119 | int payload = 0; 120 | int returnCode = 0; 121 | bool usingUsername = false; 122 | bool usingPassword = false; 123 | byte connectFlags = 0x00; 124 | byte[] buffer = null; 125 | byte[] inputBuffer = new byte[1]; 126 | byte firstByte = 0x00; 127 | 128 | UTF8Encoding encoder = new UTF8Encoding(); 129 | 130 | byte[] utf8ClientID = Encoding.UTF8.GetBytes(clientID); 131 | byte[] utf8Username = Encoding.UTF8.GetBytes(username); 132 | byte[] utf8Password = Encoding.UTF8.GetBytes(password); 133 | 134 | // Some Error Checking 135 | // ClientID improperly sized 136 | if ((utf8ClientID.Length > Constants.MAX_CLIENTID) || (utf8ClientID.Length < Constants.MIN_CLIENTID)) 137 | return Constants.CLIENTID_LENGTH_ERROR; 138 | // KeepAlive out of bounds 139 | if ((keepAlive > Constants.MAX_KEEPALIVE) || (keepAlive < Constants.MIN_KEEPALIVE)) 140 | return Constants.KEEPALIVE_LENGTH_ERROR; 141 | // Username too long 142 | if (utf8Username.Length > Constants.MAX_USERNAME) 143 | return Constants.USERNAME_LENGTH_ERROR; 144 | // Password too long 145 | if (utf8Password.Length > Constants.MAX_PASSWORD) 146 | return Constants.PASSWORD_LENGTH_ERROR; 147 | 148 | // Check features being used 149 | if (!username.Equals("")) 150 | usingUsername = true; 151 | if (!password.Equals("")) 152 | usingPassword = true; 153 | 154 | // Calculate the size of the var header 155 | varHeader += 2; // Protocol Name Length 156 | varHeader += 6; // Protocol Name 157 | varHeader++; // Protocol version 158 | varHeader++; // Connect Flags 159 | varHeader += 2; // Keep Alive 160 | 161 | // Calculate the size of the fixed header 162 | fixedHeader++; // byte 1 163 | 164 | // Calculate the payload 165 | payload = utf8ClientID.Length + 2; 166 | if (usingUsername) 167 | { 168 | payload += utf8Username.Length + 2; 169 | } 170 | if (usingPassword) 171 | { 172 | payload += utf8Password.Length + 2; 173 | } 174 | 175 | // Calculate the remaining size 176 | remainingLength = varHeader + payload; 177 | 178 | // Check that remaining length will fit into 4 encoded bytes 179 | if (remainingLength > Constants.MAXLENGTH) 180 | return Constants.MESSAGE_LENGTH_ERROR; 181 | 182 | tmp = remainingLength; 183 | 184 | // Add space for each byte we need in the fixed header to store the length 185 | while (tmp > 0) 186 | { 187 | fixedHeader++; 188 | tmp = tmp / 128; 189 | }; 190 | // End of Fixed Header 191 | 192 | // Build buffer for message 193 | buffer = new byte[fixedHeader + varHeader + payload]; 194 | 195 | // Fixed Header (2.1) 196 | buffer[index++] = Constants.MQTT_CONNECT_TYPE; 197 | 198 | // Encode the fixed header remaining length 199 | // Add remaining length 200 | index = doRemainingLength(remainingLength, index, buffer); 201 | // End Fixed Header 202 | 203 | // Connect (3.1) 204 | // Protocol Name 205 | buffer[index++] = 0; // String (MQIsdp) Length MSB - always 6 so, zeroed 206 | buffer[index++] = 6; // Length LSB 207 | buffer[index++] = (byte)'M'; // M 208 | buffer[index++] = (byte)'Q'; // Q 209 | buffer[index++] = (byte)'I'; // I 210 | buffer[index++] = (byte)'s'; // s 211 | buffer[index++] = (byte)'d'; // d 212 | buffer[index++] = (byte)'p'; // p 213 | 214 | // Protocol Version 215 | buffer[index++] = Constants.MQTTPROTOCOLVERSION; 216 | 217 | // Connect Flags 218 | if (cleanSession) 219 | connectFlags |= (byte)Constants.CLEAN_SESSION_FLAG; 220 | if (usingUsername) 221 | connectFlags |= (byte)Constants.USING_USERNAME_FLAG; 222 | if (usingPassword) 223 | connectFlags |= (byte)Constants.USING_PASSWORD_FLAG; 224 | 225 | // Set the connect flags 226 | buffer[index++] = connectFlags; 227 | 228 | // Keep alive (defaulted to 20 seconds above) 229 | buffer[index++] = (byte)(keepAlive / 256); // Keep Alive MSB 230 | buffer[index++] = (byte)(keepAlive % 256); // Keep Alive LSB 231 | 232 | // ClientID 233 | buffer[index++] = (byte)(utf8ClientID.Length / 256); // Length MSB 234 | buffer[index++] = (byte)(utf8ClientID.Length % 256); // Length LSB 235 | for (var i = 0; i < utf8ClientID.Length; i++) 236 | { 237 | buffer[index++] = utf8ClientID[i]; 238 | } 239 | 240 | // Username 241 | if (usingUsername) 242 | { 243 | buffer[index++] = (byte)(utf8Username.Length / 256); // Length MSB 244 | buffer[index++] = (byte)(utf8Username.Length % 256); // Length LSB 245 | 246 | for (var i = 0; i < utf8Username.Length; i++) 247 | { 248 | buffer[index++] = utf8Username[i]; 249 | } 250 | } 251 | 252 | // Password 253 | if (usingPassword) 254 | { 255 | buffer[index++] = (byte)(utf8Password.Length / 256); // Length MSB 256 | buffer[index++] = (byte)(utf8Password.Length % 256); // Length LSB 257 | 258 | for (var i = 0; i < utf8Password.Length; i++) 259 | { 260 | buffer[index++] = utf8Password[i]; 261 | } 262 | } 263 | 264 | // Send the message 265 | returnCode = mySocket.Send(buffer, index, 0); 266 | 267 | // The return code should equal our buffer length 268 | if (returnCode != buffer.Length) 269 | { 270 | return Constants.CONNECTION_ERROR; 271 | } 272 | 273 | // Get the acknowledgement message 274 | returnCode = mySocket.Receive(inputBuffer, 0); 275 | 276 | if (returnCode < 1) 277 | return Constants.CONNECTION_ERROR; 278 | 279 | firstByte = inputBuffer[0]; 280 | 281 | // If this is the CONNACK - pass it to the CONNACK handler 282 | if (((int)firstByte & Constants.MQTT_CONNACK_TYPE) > 0) 283 | { 284 | returnCode = handleCONNACK(mySocket, firstByte); 285 | if (returnCode > 0) 286 | { 287 | return Constants.ERROR; 288 | } 289 | } 290 | return Constants.SUCCESS; 291 | } 292 | 293 | // Publish a message to a broker (3.3) 294 | public static int PublishMQTT(Socket mySocket, String topic, String message) 295 | { 296 | int index = 0; 297 | int tmp = 0; 298 | int fixedHeader = 0; 299 | int varHeader = 0; 300 | int payload = 0; 301 | int remainingLength = 0; 302 | int returnCode = 0; 303 | byte[] buffer = null; 304 | 305 | // Setup a UTF8 encoder 306 | UTF8Encoding encoder = new UTF8Encoding(); 307 | 308 | // Encode the topic 309 | byte[] utf8Topic = Encoding.UTF8.GetBytes(topic); 310 | 311 | // Some error checking 312 | // Topic contains wildcards 313 | if ((topic.IndexOf('#') != -1) || (topic.IndexOf('+') != -1)) 314 | return Constants.TOPIC_WILDCARD_ERROR; 315 | 316 | // Topic is too long or short 317 | if ((utf8Topic.Length > Constants.MAX_TOPIC_LENGTH) || (utf8Topic.Length < Constants.MIN_TOPIC_LENGTH)) 318 | return Constants.TOPIC_LENGTH_ERROR; 319 | 320 | // Calculate the size of the var header 321 | varHeader += 2; // Topic Name Length (MSB, LSB) 322 | varHeader += utf8Topic.Length; // Length of the topic 323 | 324 | // Calculate the size of the fixed header 325 | fixedHeader++; // byte 1 326 | 327 | // Calculate the payload 328 | payload = message.Length; 329 | 330 | // Calculate the remaining size 331 | remainingLength = varHeader + payload; 332 | 333 | // Check that remaining length will fit into 4 encoded bytes 334 | if (remainingLength > Constants.MAXLENGTH) 335 | return Constants.MESSAGE_LENGTH_ERROR; 336 | 337 | // Add space for each byte we need in the fixed header to store the length 338 | tmp = remainingLength; 339 | while (tmp > 0) 340 | { 341 | fixedHeader++; 342 | tmp = tmp / 128; 343 | }; 344 | // End of Fixed Header 345 | 346 | // Build buffer for message 347 | buffer = new byte[fixedHeader + varHeader + payload]; 348 | 349 | // Start of Fixed header 350 | // Publish (3.3) 351 | buffer[index++] = Constants.MQTT_PUBLISH_TYPE; 352 | 353 | // Encode the fixed header remaining length 354 | // Add remaining length 355 | index = doRemainingLength(remainingLength, index, buffer); 356 | // End Fixed Header 357 | 358 | // Start of Variable header 359 | // Length of topic name 360 | buffer[index++] = (byte)(utf8Topic.Length / 256); // Length MSB 361 | buffer[index++] = (byte)(utf8Topic.Length % 256); // Length LSB 362 | // Topic 363 | for (var i = 0; i < utf8Topic.Length; i++) 364 | { 365 | buffer[index++] = utf8Topic[i]; 366 | } 367 | // End of variable header 368 | 369 | // Start of Payload 370 | // Message (Length is accounted for in the fixed header) 371 | for (var i = 0; i < message.Length; i++) 372 | { 373 | buffer[index++] = (byte)message[i]; 374 | } 375 | // End of Payload 376 | 377 | returnCode = mySocket.Send(buffer, buffer.Length, 0); 378 | 379 | if (returnCode < buffer.Length) 380 | return Constants.CONNECTION_ERROR; 381 | 382 | return Constants.SUCCESS; 383 | } 384 | 385 | // Disconnect from broker (3.14) 386 | public static int DisconnectMQTT(Socket mySocket) 387 | { 388 | byte[] buffer = null; 389 | int returnCode = 0; 390 | buffer = new byte[2]; 391 | buffer[0] = Constants.MQTT_DISCONNECT_TYPE; 392 | buffer[1] = 0x00; 393 | returnCode = mySocket.Send(buffer, buffer.Length, 0); 394 | 395 | if (returnCode < buffer.Length) 396 | return Constants.CONNECTION_ERROR; 397 | 398 | return Constants.SUCCESS; ; 399 | } 400 | 401 | // Subscribe to a topic 402 | public static int SubscribeMQTT(Socket mySocket, String[] topic, int[] QoS, int topics) 403 | { 404 | int index = 0; 405 | int index2 = 0; 406 | int messageIndex = 0; 407 | int messageID = 0; 408 | int tmp = 0; 409 | int fixedHeader = 0; 410 | int varHeader = 0; 411 | int payloadLength = 0; 412 | int remainingLength = 0; 413 | int returnCode = 0; 414 | byte[] buffer = null; 415 | byte[][] utf8Topics = null; 416 | 417 | UTF8Encoding encoder = new UTF8Encoding(); 418 | 419 | utf8Topics = new byte[topics][]; 420 | 421 | while (index < topics) 422 | { 423 | utf8Topics[index] = new byte[Encoding.UTF8.GetBytes(topic[index]).Length]; 424 | utf8Topics[index] = Encoding.UTF8.GetBytes(topic[index]); 425 | if ((utf8Topics[index].Length > Constants.MAX_TOPIC_LENGTH) || (utf8Topics[index].Length < Constants.MIN_TOPIC_LENGTH)) 426 | { 427 | return Constants.TOPIC_LENGTH_ERROR; 428 | } 429 | else 430 | { 431 | payloadLength += 2; // Size (LSB + MSB) 432 | payloadLength += utf8Topics[index].Length; // Length of topic 433 | payloadLength++; // QoS Requested 434 | index++; 435 | } 436 | } 437 | 438 | // Calculate the size of the fixed header 439 | fixedHeader++; // byte 1 440 | 441 | // Calculate the size of the var header 442 | varHeader += 2; // Message ID is 2 bytes 443 | 444 | // Calculate the remaining size 445 | remainingLength = varHeader + payloadLength; 446 | 447 | // Check that remaining encoded length will fit into 4 encoded bytes 448 | if (remainingLength > Constants.MAXLENGTH) 449 | return Constants.MESSAGE_LENGTH_ERROR; 450 | 451 | // Add space for each byte we need in the fixed header to store the length 452 | tmp = remainingLength; 453 | while (tmp > 0) 454 | { 455 | fixedHeader++; 456 | tmp = tmp / 128; 457 | }; 458 | 459 | // Build buffer for message 460 | buffer = new byte[fixedHeader + varHeader + payloadLength]; 461 | 462 | // Start of Fixed header 463 | // Publish (3.3) 464 | buffer[messageIndex++] = Constants.MQTT_SUBSCRIBE_TYPE; 465 | 466 | // Add remaining length 467 | messageIndex = doRemainingLength(remainingLength, messageIndex, buffer); 468 | // End Fixed Header 469 | 470 | // Start of Variable header 471 | // Message ID 472 | messageID = rand.Next(Constants.MAX_MESSAGEID); 473 | Debug.Print("SUBSCRIBE: Message ID: " + messageID); 474 | buffer[messageIndex++] = (byte)(messageID / 256); // Length MSB 475 | buffer[messageIndex++] = (byte)(messageID % 256); // Length LSB 476 | // End of variable header 477 | 478 | // Start of Payload 479 | index = 0; 480 | while (index < topics) 481 | { 482 | // Length of Topic 483 | buffer[messageIndex++] = (byte)(utf8Topics[index].Length / 256); // Length MSB 484 | buffer[messageIndex++] = (byte)(utf8Topics[index].Length % 256); // Length LSB 485 | 486 | index2 = 0; 487 | while (index2 < utf8Topics[index].Length) 488 | { 489 | buffer[messageIndex++] = utf8Topics[index][index2]; 490 | index2++; 491 | } 492 | buffer[messageIndex++] = (byte)(QoS[index]); 493 | index++; 494 | } 495 | // End of Payload 496 | 497 | returnCode = mySocket.Send(buffer, buffer.Length, 0); 498 | 499 | if (returnCode < buffer.Length) 500 | return Constants.CONNECTION_ERROR; 501 | 502 | return Constants.SUCCESS; 503 | } 504 | 505 | // Unsubscribe to a topic 506 | public static int UnsubscribeMQTT(Socket mySocket, String[] topic, int[] QoS, int topics) 507 | { 508 | int index = 0; 509 | int index2 = 0; 510 | int messageIndex = 0; 511 | int messageID = 0; 512 | int tmp = 0; 513 | int fixedHeader = 0; 514 | int varHeader = 0; 515 | int payloadLength = 0; 516 | int remainingLength = 0; 517 | int returnCode = 0; 518 | byte[] buffer = null; 519 | byte[][] utf8Topics = null; 520 | 521 | UTF8Encoding encoder = new UTF8Encoding(); 522 | 523 | utf8Topics = new byte[topics][]; 524 | 525 | while (index < topics) 526 | { 527 | utf8Topics[index] = new byte[Encoding.UTF8.GetBytes(topic[index]).Length]; 528 | utf8Topics[index] = Encoding.UTF8.GetBytes(topic[index]); 529 | if ((utf8Topics[index].Length > Constants.MAX_TOPIC_LENGTH) || (utf8Topics[index].Length < Constants.MIN_TOPIC_LENGTH)) 530 | { 531 | return Constants.TOPIC_LENGTH_ERROR; 532 | } 533 | else 534 | { 535 | payloadLength += 2; // Size (LSB + MSB) 536 | payloadLength += utf8Topics[index].Length; // Length of topic 537 | index++; 538 | } 539 | } 540 | 541 | // Calculate the size of the fixed header 542 | fixedHeader++; // byte 1 543 | 544 | // Calculate the size of the var header 545 | varHeader += 2; // Message ID is 2 bytes 546 | 547 | // Calculate the remaining size 548 | remainingLength = varHeader + payloadLength; 549 | 550 | // Check that remaining encoded length will fit into 4 encoded bytes 551 | if (remainingLength > Constants.MAXLENGTH) 552 | return Constants.MESSAGE_LENGTH_ERROR; 553 | 554 | // Add space for each byte we need in the fixed header to store the length 555 | tmp = remainingLength; 556 | while (tmp > 0) 557 | { 558 | fixedHeader++; 559 | tmp = tmp / 128; 560 | }; 561 | 562 | // Build buffer for message 563 | buffer = new byte[fixedHeader + varHeader + payloadLength]; 564 | 565 | // Start of Fixed header 566 | // Publish (3.3) 567 | buffer[messageIndex++] = Constants.MQTT_UNSUBSCRIBE_TYPE; 568 | 569 | // Add remaining length - writes to buffer, so need to get the new index back 570 | messageIndex = doRemainingLength(remainingLength, messageIndex, buffer); 571 | // End Fixed Header 572 | 573 | // Start of Variable header 574 | // Message ID 575 | messageID = rand.Next(Constants.MAX_MESSAGEID); 576 | Debug.Print("UNSUBSCRIBE: Message ID: " + messageID); 577 | buffer[messageIndex++] = (byte)(messageID / 256); // Length MSB 578 | buffer[messageIndex++] = (byte)(messageID % 256); // Length LSB 579 | // End of variable header 580 | 581 | // Start of Payload 582 | index = 0; 583 | while (index < topics) 584 | { 585 | // Length of Topic 586 | buffer[messageIndex++] = (byte)(utf8Topics[index].Length / 256); // Length MSB 587 | buffer[messageIndex++] = (byte)(utf8Topics[index].Length % 256); // Length LSB 588 | 589 | index2 = 0; 590 | while (index2 < utf8Topics[index].Length) 591 | { 592 | buffer[messageIndex++] = utf8Topics[index][index2]; 593 | index2++; 594 | } 595 | index++; 596 | } 597 | // End of Payload 598 | 599 | returnCode = mySocket.Send(buffer, buffer.Length, 0); 600 | 601 | if (returnCode < buffer.Length) 602 | return Constants.CONNECTION_ERROR; 603 | 604 | return Constants.SUCCESS; 605 | } 606 | 607 | // Respond to a PINGRESP 608 | public static int sendPINGRESP(Socket mySocket) 609 | { 610 | int index = 0; 611 | int returnCode = 0; 612 | byte[] buffer = new byte[2]; 613 | 614 | buffer[index++] = Constants.MQTT_PING_RESP_TYPE; 615 | buffer[index++] = 0x00; 616 | 617 | // Send the ping 618 | returnCode = mySocket.Send(buffer, index, 0); 619 | // The return code should equal our buffer length 620 | if (returnCode != buffer.Length) 621 | { 622 | return Constants.CONNECTION_ERROR; 623 | } 624 | return Constants.SUCCESS; 625 | } 626 | 627 | // Ping the MQTT broker - used to extend keep alive 628 | public static int PingMQTT(Socket mySocket) 629 | { 630 | 631 | int index = 0; 632 | int returnCode = 0; 633 | byte[] buffer = new byte[2]; 634 | 635 | buffer[index++] = Constants.MQTT_PING_REQ_TYPE; 636 | buffer[index++] = 0x00; 637 | 638 | // Send the ping 639 | returnCode = mySocket.Send(buffer, index, 0); 640 | // The return code should equal our buffer length 641 | if (returnCode != buffer.Length) 642 | { 643 | return Constants.CONNECTION_ERROR; 644 | } 645 | return Constants.SUCCESS; 646 | } 647 | 648 | //*************************************************************** 649 | // Utility Functions 650 | //*************************************************************** 651 | 652 | // Append the remaining length field for the fixed header 653 | public static int doRemainingLength(int remainingLength, int index, byte[] buffer) 654 | { 655 | int digit = 0; 656 | do 657 | { 658 | digit = remainingLength % 128; 659 | remainingLength /= 128; 660 | if (remainingLength > 0) 661 | { 662 | digit = digit | Constants.CONTINUATION_BIT; 663 | } 664 | buffer[index++] = (byte)digit; 665 | } while (remainingLength > 0); 666 | return index; 667 | } 668 | 669 | 670 | // Extract the remaining length field from the fixed header 671 | public static int undoRemainingLength(Socket mySocket) 672 | { 673 | int multiplier = 1; 674 | int count = 0; 675 | int digit = 0; 676 | int remainingLength = 0; 677 | byte[] nextByte = new byte[1]; 678 | do 679 | { 680 | if (mySocket.Receive(nextByte, 0) == 1) 681 | { 682 | digit = (byte)nextByte[0]; 683 | remainingLength += ((digit & 0x7F) * multiplier); 684 | multiplier *= 128; 685 | } 686 | count++; 687 | } while (((digit & 0x80) != 0) && count <4); 688 | return remainingLength; 689 | } 690 | 691 | //*************************************************************** 692 | // Main Listener loop 693 | //*************************************************************** 694 | 695 | // Listen for data on the socket - call appropriate handlers based on first byte 696 | public static int listen(Socket mySocket) 697 | { 698 | int returnCode = 0; 699 | byte first = 0x00; 700 | byte[] buffer = new byte[1]; 701 | while (true) 702 | { 703 | returnCode = mySocket.Receive(buffer, 0); 704 | if (returnCode > 0) 705 | { 706 | Debug.Print("Data Received"); 707 | first = buffer[0]; 708 | switch (first >> 4) 709 | { 710 | case 0: // Reserved 711 | Debug.Print("First Reserved Message received"); 712 | returnCode = Constants.ERROR; 713 | break; 714 | case 1: // Connect (Broker Only) 715 | Debug.Print("CONNECT Message received"); 716 | returnCode = Constants.ERROR; 717 | break; 718 | case 2: // CONNACK 719 | Debug.Print("CONNACK Message received"); 720 | returnCode = handleCONNACK(mySocket, first); 721 | break; 722 | case 3: // PUBLISH 723 | Debug.Print("PUBLISH Message received"); 724 | returnCode = handlePUBLISH(mySocket, first); 725 | break; 726 | case 4: // PUBACK (QoS > 0 - did it anyway) 727 | Debug.Print("PUBACK Message received"); 728 | returnCode = handlePUBACK(mySocket, first); 729 | break; 730 | case 5: // PUBREC (QoS 2) 731 | Debug.Print("PUBREC Message received"); 732 | returnCode = Constants.ERROR; 733 | break; 734 | case 6: // PUBREL (QoS 2) 735 | Debug.Print("PUBREL Message received"); 736 | returnCode = Constants.ERROR; 737 | break; 738 | case 7: // PUBCOMP (QoS 2) 739 | Debug.Print("PUBCOMP Message received"); 740 | returnCode = Constants.ERROR; 741 | break; 742 | case 8: // SUBSCRIBE (Broker only) 743 | Debug.Print("SUBSCRIBE Message received"); 744 | returnCode = Constants.ERROR; 745 | break; 746 | case 9: // SUBACK 747 | Debug.Print("SUBACK Message received"); 748 | returnCode = handleSUBACK(mySocket, first); 749 | break; 750 | case 10: // UNSUBSCRIBE (Broker Only) 751 | Debug.Print("UNSUBSCRIBE Message received"); 752 | returnCode = Constants.ERROR; 753 | break; 754 | case 11: // UNSUBACK 755 | Debug.Print("UNSUBACK Message received"); 756 | returnCode = handleUNSUBACK(mySocket, first); 757 | break; 758 | case 12: // PINGREQ (Technically a Broker Deal - but we're doing it anyway) 759 | Debug.Print("PINGREQ Message received"); 760 | returnCode = handlePINGREQ(mySocket, first); 761 | break; 762 | case 13: // PINGRESP 763 | Debug.Print("PINGRESP Message received"); 764 | returnCode = handlePINGRESP(mySocket, first); 765 | break; 766 | case 14: // DISCONNECT (Broker Only) 767 | Debug.Print("DISCONNECT Message received"); 768 | returnCode = Constants.ERROR; 769 | break; 770 | case 15: // Reserved 771 | Debug.Print("Last Reserved Message received"); 772 | returnCode = Constants.ERROR; 773 | break; 774 | default: // Default action 775 | Debug.Print("Unknown Message received"); // Should never get here 776 | returnCode = Constants.ERROR; 777 | break; 778 | } 779 | if (returnCode != Constants.SUCCESS) 780 | { 781 | Debug.Print("An error occurred in message processing"); 782 | } 783 | } 784 | } 785 | } 786 | 787 | //*************************************************************** 788 | // Message handlers (received) 789 | //*************************************************************** 790 | 791 | public static int handleSUBACK(Socket mySocket, byte firstByte) 792 | { 793 | int remainingLength = 0; 794 | int messageID = 0; 795 | int index = 0; 796 | int QoSIndex = 0; 797 | int[] QoS = null; 798 | byte[] buffer = null; 799 | remainingLength = undoRemainingLength(mySocket); 800 | buffer = new byte[remainingLength]; 801 | if ((mySocket.Receive(buffer, 0) != remainingLength) || remainingLength < 3) 802 | return Constants.ERROR; 803 | messageID += buffer[index++] * 256; 804 | messageID += buffer[index++]; 805 | Debug.Print("SUBACK: Message ID: " + messageID); 806 | do 807 | { 808 | QoS = new int[remainingLength - 2]; 809 | QoS[QoSIndex++] = buffer[index++]; 810 | Debug.Print("SUBACK: QoS Granted: " + QoS[QoSIndex-1]); 811 | } while(index < remainingLength); 812 | return Constants.SUCCESS; 813 | } 814 | 815 | // Messages from the broker come back to us as publish messages 816 | public static int handlePUBLISH(Socket mySocket, byte firstByte) 817 | { 818 | int remainingLength = 0; 819 | int messageID = 0; 820 | int topicLength = 0; 821 | int topicIndex = 0; 822 | int payloadIndex = 0; 823 | int index = 0; 824 | byte[] buffer = null; 825 | byte[] topic = null; 826 | byte[] payload = null; 827 | int QoS = 0x00; 828 | String topicString = null; 829 | String payloadString = null; 830 | 831 | remainingLength = undoRemainingLength(mySocket); 832 | buffer = new byte[remainingLength]; 833 | if ((mySocket.Receive(buffer, 0) != remainingLength) || remainingLength < 5) 834 | return Constants.ERROR; 835 | topicLength += buffer[index++] * 256; 836 | topicLength += buffer[index++]; 837 | topic = new byte[topicLength]; 838 | while (topicIndex < topicLength) 839 | { 840 | topic[topicIndex++] = buffer[index++]; 841 | } 842 | QoS = firstByte & 0x06; 843 | if (QoS > 0) 844 | { 845 | messageID += buffer[index++] * 256; 846 | messageID += buffer[index++]; 847 | Debug.Print("PUBLISH: Message ID: " + messageID); 848 | } 849 | topicString = new String(Encoding.UTF8.GetChars(topic)); 850 | Debug.Print("PUBLISH: Topic: " + topicString); 851 | payload = new byte[remainingLength - index]; 852 | while (index < remainingLength) 853 | { 854 | payload[payloadIndex++] = buffer[index++]; 855 | } 856 | 857 | Debug.Print("PUBLISH: Payload Length: " + payload.Length); 858 | 859 | // This doesn't work if the payload isn't UTF8 860 | payloadString = new String(Encoding.UTF8.GetChars(payload)); 861 | Debug.Print("PUBLISH: Payload: " + payloadString); 862 | 863 | return Constants.SUCCESS; 864 | } 865 | 866 | public static int handleUNSUBACK(Socket mySocket, byte firstByte) 867 | { 868 | int returnCode = 0; 869 | int messageID = 0; 870 | byte[] buffer = new byte[3]; 871 | returnCode = mySocket.Receive(buffer, 0); 872 | if ((buffer[0] != 2) || (returnCode != 3)) 873 | return Constants.ERROR; 874 | messageID += buffer[1] * 256; 875 | messageID += buffer[2]; 876 | Debug.Print("UNSUBACK: Message ID: " + messageID); 877 | return Constants.SUCCESS; 878 | } 879 | 880 | // Ping response - this should be a total of 2 bytes - that's pretty much 881 | // all I'm looking for. 882 | public static int handlePINGRESP(Socket mySocket, byte firstByte) 883 | { 884 | int returnCode = 0; 885 | Debug.Print("Ping Response Received"); 886 | byte[] buffer = new byte[1]; 887 | returnCode = mySocket.Receive(buffer, 0); 888 | if ((buffer[0] != 0) || (returnCode != 1)) 889 | { 890 | return Constants.ERROR; 891 | } 892 | return Constants.SUCCESS; 893 | } 894 | 895 | // Ping Request 896 | public static int handlePINGREQ(Socket mySocket, byte firstByte) 897 | { 898 | int returnCode = 0; 899 | byte[] buffer = new byte[1]; 900 | returnCode = mySocket.Receive(buffer, 0); 901 | if((returnCode != 1) || (buffer[0] != 0)) 902 | return Constants.ERROR; 903 | returnCode = sendPINGRESP(mySocket); 904 | if (returnCode != 0) 905 | return Constants.ERROR; 906 | return Constants.SUCCESS; 907 | } 908 | 909 | // Connect acknowledgement - returns 3 more bytes, byte 3 910 | // should be 0 for success 911 | public static int handleCONNACK(Socket mySocket, byte firstByte) 912 | { 913 | int returnCode = 0; 914 | byte[] buffer = new byte[3]; 915 | returnCode = mySocket.Receive(buffer, 0); 916 | if ((buffer[0] != 2) || (buffer[2] > 0) || (returnCode != 3)) 917 | return Constants.ERROR; 918 | return Constants.SUCCESS; 919 | } 920 | 921 | // We're not doing QoS 1 yet, so this is just here for flushing 922 | // and to notice if we are getting this message for some reason 923 | public static int handlePUBACK(Socket mySocket, byte firstByte) 924 | { 925 | int returnCode = 0; 926 | int messageID = 0; 927 | byte[] buffer = new byte[3]; 928 | returnCode = mySocket.Receive(buffer, 0); 929 | if ((buffer[0] != 2) || (returnCode != 3)) 930 | return Constants.ERROR; 931 | messageID += buffer[1] * 256; 932 | messageID += buffer[2]; 933 | Debug.Print("PUBACK: Message ID: " + messageID); 934 | return Constants.SUCCESS; 935 | } 936 | } 937 | } 938 | 939 | 940 | -------------------------------------------------------------------------------- /0.02/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2011-2012 Dan Anderson. All rights reserved. 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY DAN ANDERSON ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 17 | SHALL DAN ANDERSON OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 | THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | */ 26 | 27 | using System; 28 | using System.Net; 29 | using System.Net.Sockets; 30 | using System.Threading; 31 | using Microsoft.SPOT; 32 | using Microsoft.SPOT.Hardware; 33 | using SecretLabs.NETMF.Hardware; 34 | using SecretLabs.NETMF.Hardware.NetduinoPlus; 35 | using Netduino_MQTT_Client_Library; 36 | 37 | namespace myNetduinoMQTT 38 | { 39 | public class Program 40 | { 41 | static Thread listenerThread; 42 | static Socket mySocket = null; 43 | 44 | public static void Main() 45 | { 46 | 47 | int returnCode = 0; 48 | 49 | // You can subscribe to multiple topics in one go 50 | // (If your broker supports this RSMB does, mosquitto does not) 51 | // Our examples use one topic per request. 52 | // 53 | //int[] topicQoS = { 0, 0 }; 54 | //String[] subTopics = { "test", "test2" }; 55 | //int numTopics = 2; 56 | 57 | int[] topicQoS = { 0 }; 58 | String[] subTopics = { "test" }; 59 | int numTopics = 1; 60 | 61 | // Get broker's IP address. 62 | //IPHostEntry hostEntry = Dns.GetHostEntry("test.mosquitto.org"); 63 | IPHostEntry hostEntry = Dns.GetHostEntry("192.168.1.106"); 64 | 65 | // Create socket and connect to the broker's IP address and port 66 | mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 67 | try 68 | { 69 | mySocket.Connect(new IPEndPoint(hostEntry.AddressList[0], 1883)); 70 | } 71 | catch (SocketException SE) 72 | { 73 | Debug.Print("Connection Error: " + SE.ErrorCode); 74 | return; 75 | } 76 | 77 | // Send the connect message 78 | // You can use UTF8 in the clientid, username and password - be careful, this can be a pain 79 | //returnCode = NetduinoMQTT.ConnectMQTT(mySocket, "tester\u00A5", 2000, true, "roger\u00A5", "password\u00A5"); 80 | returnCode = NetduinoMQTT.ConnectMQTT(mySocket, "tester402", 20, true, "roger", "password"); 81 | if (returnCode != 0) 82 | { 83 | Debug.Print("Connection Error: " + returnCode.ToString()); 84 | return; 85 | } 86 | 87 | // Set up so that we ping the server after 1 second, then every 10 seconds 88 | // First time is initial delay, Second is subsequent delays 89 | Timer pingTimer = new Timer(new TimerCallback(pingIt), null, 1000, 10000); 90 | 91 | // Setup and start a new thread for the listener 92 | listenerThread = new Thread(mylistenerThread); 93 | listenerThread.Start(); 94 | 95 | // setup our interrupt port (on-board button) 96 | InterruptPort button = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow); 97 | 98 | // assign our interrupt handler 99 | button.OnInterrupt += new NativeEventHandler(button_OnInterrupt); 100 | 101 | // Subscribe to our topic(s) 102 | returnCode = NetduinoMQTT.SubscribeMQTT(mySocket, subTopics, topicQoS, numTopics); 103 | 104 | //*********************************************** 105 | // This is just some example stuff: 106 | //*********************************************** 107 | 108 | // Publish a message 109 | NetduinoMQTT.PublishMQTT(mySocket, "test", "Testing from NetduinoMQTT"); 110 | 111 | // Subscribe to "test/two" 112 | subTopics[0] = "test/two"; 113 | returnCode = NetduinoMQTT.SubscribeMQTT(mySocket, subTopics, topicQoS, numTopics); 114 | 115 | // Send a message to "test/two" 116 | NetduinoMQTT.PublishMQTT(mySocket, "test/two", "Testing from NetduinoMQTT to test/two"); 117 | 118 | // Unsubscribe from "test/two" 119 | returnCode = NetduinoMQTT.UnsubscribeMQTT(mySocket, subTopics, topicQoS, numTopics); 120 | 121 | // go to sleep until the interrupt or the timer wakes us 122 | // (mylistenerThread is in a seperate thread that continues) 123 | Thread.Sleep(Timeout.Infinite); 124 | } 125 | 126 | // the interrupt handler for the button 127 | static void button_OnInterrupt(uint data1, uint data2, DateTime time) 128 | { 129 | // Send our message 130 | NetduinoMQTT.PublishMQTT(mySocket, "test", "Ow! Quit it!"); 131 | return; 132 | } 133 | 134 | // The thread that listens for inbound messages 135 | private static void mylistenerThread() 136 | { 137 | NetduinoMQTT.listen(mySocket); 138 | } 139 | 140 | // The function that the timer calls to ping the server 141 | // Our keep alive is 15 seconds - we ping again every 10. 142 | // So we should live forever. 143 | static void pingIt(object o) 144 | { 145 | Debug.Print("pingIT"); 146 | NetduinoMQTT.PingMQTT(mySocket); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | NetduinoMQTT ChangeLog: 2 | 3 | Version 0.02: 4 | 0.02 - Jan 29 - Added subscription support 5 | 6 | Version 0.01: 7 | 0.01a - Jan 11 - BugFix 256 versus 255 in all byte splitting operations. 8 | 0.01 - Dec 25 - Jan 11 - Basic MQTT Publishing - numerous enhancements over several weeks. First release. 9 | 10 | Version 0.02: 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | NetduinoMQTT 2 | 3 | Copyright 2011-2012 Dan Anderson. All rights reserved. 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY DAN ANDERSON ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 17 | SHALL Dan Anderson OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 | THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NetduinoMQTT 2 | 3 | Introduction: 4 | 5 | This is a simple, QoS 0, MQTT (MQTT.org) client. 6 | 7 | It can be used by the Netduino to send MQTT messages to a MQTT broker and subscribe to topics. 8 | 9 | This program is intended to be VERY readable - even at the expense of size/performance. 10 | 11 | The program.cs is just an example of using the library functions to interact with a broker. 12 | 13 | Version 0.02 supports: 14 | 1. Everything from 0.01(a) 15 | 2. Subscribing and unsubscribing 16 | 3. The client will respond to broker pings 17 | 4. The sizes of the various fields have been arbitrarily shortened 18 | to keep this from eating all of the memory. 19 | 20 | Version 0.01(a) supports: 21 | 1. Connecting to an MQTT broker (RSMB and mosquitto have been tested) (Section 3.1) 22 | 2. Sending MQTT messages to a broker (Section 3.3) 23 | 3. Disconnecting from a broker (Section 3.14) 24 | 4. Pings (used for keep alive - example in program.cs) 25 | 26 | Version 0.01 will not be further enhanced (beyond bug fixes and maintaining protocol support at this basic level of functionality). I think it is important to keep a publish only version without the QoS or Subscribe stuff. I think this is important both for understanding the basics and for a very small client. 27 | 28 | I'm not sure where version 0.03 is going. Maybe QoS 1 and basic broker functionality. Maybe just QoS 1. Maybe add in MQTT-S forwarder or gateway functionality. 29 | 30 | None of this has been comprehensively tested. If you do any testing - please let me know your results. I did some very basic functional testing that seemed good and I'm planning on testing further when time allows. But, if you use this - I'd love the feedback/assistance. 31 | 32 | Thanks 33 | Dan 34 | --------------------------------------------------------------------------------