NAV Navbar
cURL Arduino Python Circuitpython Ruby

Adafruit IO MQTT API

MQTT, or message queue telemetry transport, is a protocol for device communication that Adafruit IO supports. Using a MQTT library or client you can publish and subscribe to a feed to send and receive feed data.

If you aren't familiar with MQTT check out this introduction from the HiveMQ blog. All of the subsequent posts in the MQTT essentials series are great and worth reading too.

Client Libraries

To use the MQTT API that Adafruit IO exposes you'll need a MQTT client library. For Python, Ruby, and Arduino you can use Adafruit's IO libraries as they include support for MQTT. For other languages or platforms look for a MQTT library that ideally supports the MQTT 3.1.1 protocol.

MQTT Connection Details

We strongly recommend connecting using SSL (Port 8883) if your client allows it.

Host io.adafruit.com
Secure (SSL) Port 8883
Insecure Port 1883
Username Your Adafruit IO Username
Password Your Adafruit IO Key

Need to manually set a Client ID for your MQTT client? Use a unique value such as a random GUID. Whenever possible, leave the client ID field blank.

We do not currently limit the number of clients that can connect to Adafruit IO, but we do limit the rate at which connections may be attempted to 20 per minute. Exceeding the connection attempt rate limit will result in a temporary ban.

MQTT QoS

One feature of MQTT is the ability to specify a QoS, or quality of service, level when publishing feed data. This allows an application to confirm that its data has been sucessfully published.

If you aren't familiar with MQTT QoS levels be sure to read this great blog post explaining their meaning.

For publishing feed values the Adafruit IO MQTT API supports QoS level 0 (at most once) and 1 (at least once) only.

QoS level 2 (exactly once) is not currently supported.

MQTT API Rate Limiting

Adafruit IO's MQTT server imposes a rate limit to prevent excessive load on the service. If a user performs too many publish actions in a short period of time then some of the publish requests might be rejected. The current rate limit is at most 1 request per second (or 60 requests within 60 seconds), without an Adafruit IO+ Boost applied to your account.

If you exceed this limit, a notice will be sent to the (username)/throttle topic. You can subscribe to the topic if you wish to know when the Adafruit IO rate limit has been exceeded for your user account.

This limit applies to all connections so if you have multiple devices or clients publishing data be sure to delay their updates enough that the total rate is below 2 requests/second.

MQTT Data Format

There are a few ways to send data to our MQTT API if you're writing your own client library.

The simplest way to send values to an IO Feed topic is to just send the value. For example, a temperature sensor is going to produce numeric values like 22.587. If you're sending to mosfet/feeds/photocell-one you can use the raw number or a string. That means either 22.587 or 22.587 will be accepted as a numeric value. Adafruit IO does its best to treat data as numeric values so that we can show you your data as a chart on an Adafruit IO dashboard and through our Charting API.

Sending data with location

To tag your data with a location value, you'll either need to wrap it in a JSON object first or send it to the special /csv formatted MQTT topic.

Sending JSON

JSON can be sent to either the base topic or the /json topic - for example mosfet/feeds/photocell-one or mosfet/feeds/photocell-one/json. The proper format for location tagged JSON data is:

Example JSON topic object:

{
  "value": 22.587,
  "lat": 38.1123,
  "lon": -91.2325,
  "ele": 112
}

Specifically, JSON objects must include a value key, and may include lat, lon, and ele keys.

Sending CSV

Alternatively, you can send location tagged data to /csv topics. In this example, that would be the topic mosfet/feeds/photocell-one/csv instead of mosfet/feeds/photocell-one. Both store data in the same feed. The format IO expects for location tagged CSV data is VALUE, LATITUDE, LONGITUDE, ELEVATION.

With the example data shown before, that means you could publish the string "22.587,38.1123,-91.2325,112" to mosfet/feeds/photocell-one/csv. to store the value "22.587" in the location latitude: 38.1123, longitude: -91.2325, elevation: 112.

An example is displayed below, which uses a simple Ruby MQTT library and the data shown, all these examples publish the same data to the same feed.

# first you'll need https://github.com/njh/ruby-mqtt
require 'mqtt'

username = 'test_username'
key      = 'not-a-real-key'
url      = "mqtts://#{ username }:#{ key }@io.adafruit.com"

mqtt_client = MQTT::Client.connect(url, 8883)

# simplest thing that could possibly work
mqtt_client.publish('test_username/feeds/example', 22.587)

# sending numbers as strings is fine, IO stores all data internally
# as strings anyways
mqtt_client.publish('test_username/feeds/example', '22.587')

# CSV formatted, no location
mqtt_client.publish('test_username/feeds/example/csv', '22.587')

# CSV formatted, with location
mqtt_client.publish('test_username/feeds/example/csv',
                  '22.587,38.1123,-91.2325,112')

# JSON formatted, no location
mqtt_client.publish('test_username/feeds/example', '{"value":22.587}')
mqtt_client.publish('test_username/feeds/example/json', '{"value":22.587}')

# JSON formatted, with location
mqtt_client.publish('test_username/feeds/example',
'{"value":22.587,"lat":38.1123,"lon":-91.2325,"ele":112}')
mqtt_client.publish('test_username/feeds/example/json',
'{"value":22.587,"lat":38.1123,"lon":-91.2325,"ele":112}')

Sending JSON Data through Adafruit IO

Because Adafruit IO supports additional features beyond a basic MQTT brokering service, such as location tagging for data points, the service supports data in the JSON format described above. Namely, the example JSON response on the sidebar.

JSON Response Format Example

  {
    "value": 22.587,
    "lat": 38.1123,
    "lon": -91.2325,
    "ele": 112
  }

This lets us store the individual value, 22.587, and data about the value: its latitude, longitude, and elevation. Metadata!

But what happens when the value you want to send is itself JSON? Good news! There are a few solutions available to you in that situation.

IO-Formatted JSON

The simplest way to send JSON data to Adafruit IO is to wrap it in the format described above. For example, if instead of 22.587, I wanted to send something like, {"sensor-1":22.587,"sensor-2":13.182}, the "wrapped" version would look like this:

Example of IO-Formatted JSON

{
  "value": {"sensor-1":22.587,"sensor-2":13.182},
  "lat": 38.1123,
  "lon": -91.2325,
  "ele": 112
}

It's worth noting that because Adafruit IO parses the entire JSON object that you send it, any valid JSON will be parsed and when it is stored in our system and forwarded to any subscribers, it will be regenerated. The significance of that is that if you publish JSON data with whitespace, it will be stored and republished without whitespace, because our generator produces the most compact JSON format possible.

Double-Encoded JSON Strings

The second way you can send JSON data as a value is to "double encode" it before sending, in which case IO will treat it as a raw string. If you're using something like javascript's JSON.stringify function or Ruby's JSON.generate, double encoding means passing the result of JSON.stringify through JSON.stringify a second time. In this node.js console example, you can see the difference:

Here's an example of sending double-encoded strings through Adafruit IO:

> JSON.stringify({"sensor-1":22.587,"sensor-2":13.182})
'{"sensor-1":22.587,"sensor-2":13.182}'
> JSON.stringify(JSON.stringify({"sensor-1":22.587,"sensor-2":13.182}))
'"{\"sensor-1\":22.587,\"sensor-2\":13.182}"'

The double encoded JSON string can be sent directly through Adafruit IO without interference from our processing system, because the processing system will not interpret it as JSON. In your receiving code, because the value passed through includes surrounding double quotes, you have to call your parse function twice to restore the JSON object.

Here's an example of interpreting double-encoded JSON Strings sent through Adafruit IO:

> var input = '"{\\\"sensor-1\\\":22.587,\\\"sensor-2\\\":13.182}"'
> JSON.parse(JSON.parse(input))
{ 'sensor-1': 22.587, 'sensor-2': 13.182 }

Non-IO Formatted JSON

The third way you can send raw JSON data is to just send it. If Adafruit IO doesn't find a "value" key in the JSON object you send, it will treat the whole blob as plain text and store and forward the data. That means with our example JSON object, sending the string {"sensor-1":22.587,"sensor-2":13.182} will result in {"sensor-1":22.587,"sensor-2":13.182} being stored in IO and sent to MQTT subscribers.

Retained values

MQTT is a tremendously useful protocol for building small connected devices and is relatively simple to understand and implement (if implementing networking protocols is your thing). Unfortunately, a few features of the Adafruit IO platform make it difficult for us to support the entire MQTT 3.1+ protocol specification in our broker. Specifically, one particular feature, the publish retain flag.

MQTT Retain

In the MQTT protocol, setting the retain flag on a published message asks the MQTT broker (server) to store that message. Then any new clients which connect and subscribe to that topic will immediately receive the retained message. Retain makes writing basic MQTT-only Internet of Things clients easier, without it, a client that connects and subscribes to a feed topic has to wait until a new value is published on the feed to know what state it should be in. In the case of slowly updated feeds, there could be hours between updates which means a device that disconnects and reconnects (for example, due to power cycling or sleeping) might lose the current value for a long time between updates.

Adafruit IO's Limitations

Among other factors, our scale, Adafruit IO's mix of MQTT & HTTP APIs, the speed at which we’re taking in new data, and the fact that we’re already storing almost every message that is sent mean that a “simple” feature like retain becomes difficult to support without making MQTT service performance worse for everyone.

Since we don’t actually store data in the broker but at a lower level and can’t support PUBLISH retain directly, we’re proposing a different solution for the retaining problem: the /get topic modifier.

Using the */get topic

The way it works is that for any given Adafruit IO MQTT feed or group, subscribe to the appropriate topic, then add /get to the topic you subscribed to and publish anything to that new topic (our Arduino library uses the null character: \0) . IO will immediately publish, just for that client, the most recent value received on the feed.

For example, let's imagine we have a device that subscribes to a counter feed: uname/f/counter. If we want to get the latest value, the /get topic we should publish to is uname/f/counter/get. After connecting to IO, subscribing to uname/f/counter, and publishing to uname/f/counter/get, we will immediately receive a message on our uname/f/counter subscription with the last value that was published to counter.

If you’re using the Adafruit IO Arduino library, you can add /get support to your project in one line of code:

// ... from the adafruitio_01_subscribe example sketch
AdafruitIO_Feed *counter = io.feed("counter");

void setup() {
  // 1. start IO connection
  io.connect();

  // 2. prepare MQTT subscription with handler function
  counter->onMessage(handleMessage);

  // 3. wait for successful connection
  while(io.mqttStatus() < AIO_CONNECTED) {
    delay(500);
  }

  // 4. send /get message, requesting last value, triggering
  //    the handleMessage handler function
  counter->get(); // ask Adafruit IO to resend the last value
}
// ....

You can also perform a /get using the Adafruit IO CircuitPython library in one line of code:

python io.get('temperature')

MQTT Topics

Feed Topic Format

Adafruit IO's MQTT API exposes feed data using special topics. You can publish a new value for a feed to its topic, or you can subscribe to a feed's topic to be notified when the feed has a new value.

Your_Adafruit_IO_Username/feeds/Feed-Key

Group Topic Format

Your_Adafruit_IO_Username/groups/Feed-Key

Small Topic Format

If you're using an embedded system and need to conserve space, use the /f/ or /g/ topic formats:

Using a Wildcard

You can also subscribe to the parent 'feeds' path to be notified when any owned feed changes using MQTT's # wildcard character. For example, the user could subscribe to either:

Once subscribed to the path above any change to a feed owned by Your_Adafruit_IO_Username will be sent to the MQTT client. The topic will specify the feed that was updated, and the payload will have the new value.

Be aware the MQTT server sends feed updates on all possible paths for a specific feed. For example, subscribing to IO-Username/f/# and publishing to IO-Username/f/photocell-one would produce messages from: IO-Username/f/photocell-one, IO-Username/f/photocell-one/json, and IO-Username/f/photocell-one/csv; each referring to the same updated value. To reduce noise, make sure to grab the specific topic of the feed / format you're interested in and change your subscription to that.

If you'd like to avoid the formatted feeds (/json and /csv topics) but still see all the feeds you're publishing to, you can use MQTT's + wildcard in place of #. In this case, subscribing to IO-Username/f/+ would produce output on IO-Username/f/photocell-one, but not IO-Username/f/photocell-one/json.

Group Topics

You aren't limited to just Feed based MQTT topics, Adafruit IO supports grouped feeds as well. Similar to our HTTP group data creation API, you can publish to multiple feeds or subscribe to multiple feeds through a single MQTT topic.

The topics formats for publishing or subscribing are:

Publish to Feeds in a Group

If you use the /json or /csv endings on your group MQTT topic, your data should be formatted in JSON or CSV, respectively. By default, IO expects published values to be in JSON format.

The JSON and CSV format IO expects are displayed below:

JSON Expected Response from Adafruit IO

{
  "feeds": {
    "key-1": "value 1",
    "key-2": "value 2",
    "key-3": "value 3"
  },
  "location": {
    "lat": 0.0,
    "lon": 0.0,
    "ele": 0.0
  }
}

CSV Format Expected Response from Adafruit IO

key-1,value 1
key-2,value 2
key-3,value 3
location,0.0,0.0,0.0

Group Guidelines

Subscribing to groups

If you use the /json or /csv endings when subscribing to your group MQTT topic, your data will be formatted in JSON or CSV, respectively. By default, IO publishes values in JSON format.

The formats your subscription will receive are the same as the formats IO expects to receive. Expected responses for JSON and CSV are below.

Expected Group JSON Response

{
  "feeds": {
    "key-1": "value 1",
    "key-2": "value 2",
    "key-3": "value 3"
  },
  "location": {
    "lat": 1.0,
    "lon": 2.0,
    "ele": 3.0
  }
}

Expected Group CSV Response

key-1,value 1
key-2,value 2
key-3,value 3
location,1.0,2.0,3.0

It's important to note that you will only receive updated values for the feeds that received new values. That means if you're subscribed to {username}/groups/example and publish to {username}/feeds/key-1, the subscription will receive:

{
  "feeds": {
    "key-1": "value 1"
  }
}

It's also worth noting that JSON subscription formats will always receive string type values, regardless of whether a string or number was published.

Time Topics

Adafruit IO provides some built-in MQTT topics for getting the current server time. The current available topics are:

Most of the Adafruit IO client libraries provide helper functions for easy use of these topics:

time/seconds

This topic publishes the current time in Unix epoch seconds.

The current Unix epoch time at the time of writing is: 1562164754.

time/millis

This topic publishes the same thing as time/seconds every second, but with slightly higher precision and more digits.

The current Unix epoch time in milliseconds at the time of writing is: 1562165081839

time/ISO-8601

This topic publishes the current time in ISO 8601 UTC format. That's year-month-dayThour:minute:second.millisecondsZ where "-", ":", "T", and "Z" are character literals / always included in the time string.

The current time in ISO 8601 UTC format at the time of writing is: 2019-07-03T14:47:16.038Z

time/hours

This topic publishes the current hour of the day in 24 hour format (the values 0 - 23) in the UTC time zone. It only publishes once every hour.

Error Topics

Adafruit IO provides two error reporting MQTT topics you can subscribe to: {username}/errors and {username}/throttle.

{username}/errors

This topic publishes error messages related to publishing and subscribing to Adafruit IO feeds. Because MQTT is an asynchronous and compact messaging format, we cannot respond to invalid PUBLISH or SUBSCRIBE packets with a comprehensive error message, so we use this topic to update subscribers when an error occurs.

You may only subscribe to your personal errors feed.

{username}/throttle

This topic publishes throttle warning and error messages to all subscribed clients. We do our best to provide an accurate accounting in the error message of the minimum amount of time you should wait before attempting to publish again.

Be warned: exceeding the rate limit too often within a short time span will result in a temporary ban from the Adafruit IO MQTT service.

You may only subscribe to your personal throttle feed.

Adafruit IO Monitor

Both error feeds can be seen live on your Adafruit IO Monitor page.