Now that you have a working demo, it's time to look 'under the hood' as it were, and see how the Adafruit_MQTT library really works!
We'll go section by section at the mqtt example. In this case we'll use the ESP8266 version (mqtt_esp8266) but other than the connection function, the base code is indentical
#includes
The top of the sketch has the includes. We'll need whatever supporting header files and libraries but also Adafruit_MQTT.h and another header that tells the MQTT library which transport we are using. For example, the ESP8266 demo has
#include <ESP8266WiFi.h> #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h"
That's because the core Adafruit_MQTT.cpp and header file do not actually contain the low level code for sending and receiving packets. Instead, those are kept in the special header file. We use a header file instead of another .cpp so that we don't have the annoyance of having to include every possible supported transport header. It's a bit of a clever hack but it works very well! (Hat tip to tonyd)
WiFi and Authentication
After some pin definitions and other objects required for the transport layer such as WiFi credentials, Cellular APN details, etc. you'll have the adafruit.io credentials
#define AIO_SERVER "io.adafruit.com" #define AIO_SERVERPORT 1883 #define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." #define AIO_KEY "...your AIO key..."
We covered these already, but you can change brokers and port if you'd like. Username and key are required right now so don't forget them!
Later on, you'll see these #define's used to assign const char's - these use Flash memory not RAM so it saves a bit of memory on those constrained devices
// Store the MQTT server, username, and password in flash memory. // This is required for using the Adafruit MQTT library. const char MQTT_SERVER[] PROGMEM = AIO_SERVER; const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME; const char MQTT_PASSWORD[] PROGMEM = AIO_KEY;
Publish & Subscribe
You can do two things (for the most part) with MQTT. You can publish data to the broker, and you can subscribe data from the broker.
In this diagram you can see there's two of each
- The car publishes its location
- The toaster subscribes to the car location
- The toaster publishes toasting status
- The cell phone subscribes to the toaster status
"Car Location" and "Toasting Status" are topics.
You can have multiple subscribers to a 'topic' (e.g. the car location) and in theory you can have mulitple publishes too, although you cant tell who published it so it requires care.
Publishing
Lets look at how we publish to a topic. Start by creating the name
const char PHOTOCELL_FEED[] PROGMEM = AIO_USERNAME "/feeds/photocell";
The name of the photo cell topic is AIO_USERNAME/feeds/photocell - that means if your username is ladyada, the feed is called "ladyada/feeds/photocell". That way it doesnt get confused with anybody else's photocell feed. Only you have access to publish to the feeds under your username.
We store the name of the feed in PHOTOCELL_FEED which is stored in flash for safekeeping.
Then we can create the Adafruit_MQTT_Publish object, which we also call photocell
and create that with Adafruit_MQTT_Publish(&mqtt, NameOfTheFeed)
Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, PHOTOCELL_FEED);
That's pretty much it! Now you can interact and publish just using the photocell
object (which we will see later)
const char ONOFF_FEED[] PROGMEM = AIO_USERNAME "/feeds/onoff";
Likewise this feed is called ladyada/feeds/onoff
And you create an MQTT subscription object with:
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, ONOFF_FEED);
void setup() { Serial.begin(115200); delay(10); Serial.println(F("Adafruit MQTT demo")); // Connect to WiFi access point. Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(WLAN_SSID); WiFi.begin(WLAN_SSID, WLAN_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP());
This section will be slightly different depending on how you're connecting to the Internet, be it WiFi or Cellular or Ethernet... Basically, just get connected!
Next, we want to subscribe to our topics:
// Setup MQTT subscription for onoff feed. mqtt.subscribe(&onoffbutton); }
We only have one subscription in this sketch, but you can subscribe to as many topics as you like (within your memory constraints). Just add a new mqtt.subscribe(&feedobject) for each feed
Since we have to create memory objects to store the subscriptions, by default the # of subs allowed is 5. You can increase that by going into Adafruit_MQTT.h and editing this line:
// how many subscriptions we want to be able to track #define MAXSUBSCRIPTIONS 5
Then saving & recompiling.
Main Loop
This is the main program loop, all we're really doing is waiting for subscription notifications and then publishing a number once in a while.
Check Connection
First up, always make sure you're connected to the MQTT server, we have a helper program called MQTT_connect()
void loop() { // Ensure the connection to the MQTT server is alive (this will make the first // connection and automatically reconnect when disconnected). See the MQTT_connect // function definition further below. MQTT_connect();
This function is defined below. It checks to make sure that MQTT is connected, and if not, it reconnects.
// Function to connect and reconnect as necessary to the MQTT server. // Should be called in the loop function and it will take care if connecting. void MQTT_connect() { int8_t ret; // Stop if already connected. if (mqtt.connected()) { return; } Serial.print("Connecting to MQTT... "); while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected Serial.println(mqtt.connectErrorString(ret)); Serial.println("Retrying MQTT connection in 5 seconds..."); mqtt.disconnect(); delay(5000); // wait 5 seconds } Serial.println("MQTT Connected!"); }
Wait for subscription messages
OK so after the connection check, we mostly sit around and wait for subscriptions to come in.
// this is our 'wait for incoming subscription packets' busy subloop // try to spend your time here Adafruit_MQTT_Subscribe *subscription; while ((subscription = mqtt.readSubscription(5000))) { if (subscription == &onoffbutton) { Serial.print(F("Got: ")); Serial.println((char *)onoffbutton.lastread); } }
We start by creating a pointer to a Adafruit_MQTT_Subscribe object.
Adafruit_MQTT_Subscribe *subscription;
We'll use this to determine which subscription was received. In this case we only have one subscription, so we don't really need it. But if you have more than one its required so we keep it in here so you dont forget
Next, we wait for a subscription message:
while ((subscription = mqtt.readSubscription(5000)))
mqtt.readSubscription(timeInMilliseconds) will sit and listen for up to 'time' for a message. It will either get a message before the timeout, and reply with a pointer to the subscription or it will timeout and return 0. In this case, it will wait up to 5 seconds for a subscription message.
If the reader times out, the while loop will fail. However, say we do get a valid non-zero return. Then we will compare what subscription we got to our known subs:
if (subscription == &onoffbutton) {
For example, here we're comparing to the onoffbutton feed sub. If they match, we can read the last message. The message is in feedobject.lastread. You may have to cast this since MQTT is completely agnostic about what the message data is.
Serial.print(F("Got: "));
Serial.println((char *)onoffbutton.lastread);
The subscription only store one message (the last read one). Also, there's a limit to the size of the message. Since some people are using this library with small microcontrollers, we set the default to 20 bytes. If you want to, say, pass around twitter messages or chucks of binary data you'll want to expand this.
You can adjust it in Adafruit_MQTT.h
// how much data we save in a subscription object // eg max-subscription-payload-size #define SUBSCRIPTIONDATALEN 20
Publish data
If you've got any subscriptions, you should listen for them in the large bulk of the time you have 'available' in your loop.
Once you're done listening, you can send some data. Publication is much easier than subscribing, you just call the publish function of the feed object. You can send ints, floats, strings, etc!
// Now we can publish stuff! Serial.print(F("\nSending photocell val ")); Serial.print(x); Serial.print("..."); if (! photocell.publish(x++)) { Serial.println(F("Failed")); } else { Serial.println(F("OK!")); }
You can check for success or failure of publication. The MQTT library does not retransmit if there's a failure so if you want to send a message again - do it by hand!
You can set it in Adafruit_MQTT.h - the default is 300 seconds (5 minutes)
// Adjust as necessary, in seconds. Default to 5 minutes. #define MQTT_CONN_KEEPALIVE 300
If you are publishing once every 5 minutes, or more, then you're good to go.
If you are not publishing data, only subscribing, you must send a ping to let the broker know you're around!
Pinging is easy, just call ping()
// ping the server to keep the mqtt connection alive // NOT required if you are publishing once every KEEPALIVE seconds /* if(! mqtt.ping()) { mqtt.disconnect(); }
There's one downside to pinging...that's that if a subscription packet happens to come in during the ping, it will get thrown out. So ping rarely! Note that you can also lose packets if they arrive during publication.
It's rare, and as long as you ping only 2 or so times per the keepalive, you ought not have it occur too often.
If the ping fails, it will disconnect from the MQTT socket, forcing a reconnect
Text editor powered by tinymce.