So, you have a CircuitPython project and want to connect it to the internet? You may want to consider adding MQTT to your project. MQTT is an extremely popular and lightweight protocol which can connect your project to the internet and quickly process network events.

MQTT is a small, lightweight protocol which is incredibly easy to use in scenarios where bandwidth is at a premium, your project is sending a small amount of data every so often, or if you'd like to process network events incredibly quickly (clicking a button would cause your project to do something).

We've built a robust MQTT module for CircuitPython called CircuitPython MiniMQTT to quickly get you started connecting your projects to the internet and sending data around.

In this guide, you will set up your CircuitPython board with the necessary libraries, connect to the internet and connect your CircuitPython board to either a MQTT broker of your choice or the free Adafruit IO MQTT broker. We've included code-walkthrough and advanced usage sections to this guide so you can roll your own MiniMQTT project!

Parts

You'll need a CircuitPython board which either has network connectivity built-in or external network hardware .

All-in-One CircuitPython WiFi Boards

Don't want to add extra hardware to your project? Consider grabbing a board which has an ESP32 WiFi co-processor built-in!

Adafruit Metro M4 Airlift Lite dev board with SAMD51 an ESP32 Wifi Co-processor.
Give your next project a lift with AirLift - our witty name for the ESP32 co-processor that graces this Metro M4. You already know about the Adafruit Metro...
$34.95
In Stock
Front view of a Adafruit PyPortal - CircuitPython Powered Internet Display with a pyportal logo image on the display.
PyPortal, our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Make custom touch screen interface...
$54.95
In Stock

Externally Connected CircuitPython Network Hardware

If you already have a project which uses a CircuitPython board without internet connectivity, you can easily add WiFi connectivity with an externally connected AirLift module.

WiFi

If you're using an Adafruit Feather, adding WiFi connectivity to your project is as easily as snapping the the AirLift FeatherWing on top.

Angled shot of Adafruit AirLift FeatherWing.
Give your Feather project a lift with the Adafruit AirLift FeatherWing - a FeatherWing that lets you use the powerful ESP32 as a WiFi co-processor. You probably have your...
$12.95
In Stock

If you're using a CircuitPython board with an Arduino form-factor, you'll want to pick up an AirLift Shield.

Angled shot of Wi-Fi co-processor shield PCB.
Give your Arduino project a lift with the Adafruit AirLift Shield - a shield that lets you use the powerful ESP32 as a WiFi co-processor. You probably have your favorite...
$14.95
In Stock

If none of the form factors above work for your project - we also have a breakout board which can connect to any microcontroller over SPI.

Top view of Adafruit AirLift Breakout Board.
Give your plain ol' microcontroller project a lift with the Adafruit AirLift - a breakout board that lets you use the powerful ESP32 as a WiFi co-processor. You probably...
$9.95
In Stock

Ethernet

Wireless is wonderful, but sometimes you want the strong reliability of a wired connection. If your project is going to be part of a permanent installation, you'll want to add ethernet networking to your project. 

Angled shot of a Adafruit Ethernet FeatherWing with an Ethernet cable connected to it.
Wireless is wonderful, but sometimes you want the strong reliability of a wire. If your Feather board is going to be part of a permanent installation, this Ethernet...
$19.95
In Stock
Angled shot of a Ethernet Shield for Arduino.
The W5500 Ethernet Shield for Arduino from Seeed Studio is a great way to set up your projects with internet connectivity with just a single chip.  Similar to the
$39.95
In Stock

MQTT devices, like your CircuitPython board, connect to a broker with a client library.

We've written an awesome CircuitPython MQTT client library called Adafruit MiniMQTT.

This library is based off previous work by pfalcon on uMQTT (and the umqtt port to ESP32SPI by beachbc). MiniMQTT's primary difference from MicroPython's uMQTT library is its use of calling conventions and method names similar to The Eclipse Foundation's Paho.Mqtt.Python

Install CircuitPython

Some CircuitPython compatible boards come with CircuitPython installed. Others are CircuitPython-ready, but need to have it installed. As well, you may want to update the version of CircuitPython already installed on your board. The steps are the same for installing and updating. 

CircuitPython Library Installation

To interface your AirLift breakout/board with and the internet - you'll need to install a few CircuitPython libraries on your board.

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle matching your version of CircuitPython. 

CircuitPython hardware shows up on your computer operating system as a flash drive when connected via usb. The flash drive is called CIRCUITPY and contains a number of files. You will need to add additional files to enable the features of this project.

First, create a folder on the drive named lib if it is not already there.

Ensure your board's lib folder has the following files and folders copied over. The version of the files must be the same major version as your version of CircuitPython (i.e. 4.x for 4.x, 5.x for 5.x, etc.)

  • adafruit_minimqtt

If you do not want to host your own MQTT broker, using Adafruit IO's MQTT broker is a great way to get started connecting your CircuitPython project to the internet. Best of all - it's free to use!

You're going to build an Adafruit IO Dashboard which can visualize incoming data from your CircuitPython board, and send data to it.

Obtain Adafruit IO Username and Key

If you have not already, sign up for an Adafruit IO account by clicking this link

Next, you're going to need your Adafruit IO username and secret API key.

Navigate to your profile and click the View AIO Key button to retrieve them. Write them down in a safe place, you'll need them later.

Create Adafruit IO Feeds

Adafruit IO uses a special type of MQTT Topic named a Feed to store data along with metadata (information about the data). You'll be publishing data to one feed, and subscribing to another.

Create two new Adafruit IO Feeds named onoff and photocell.

  • photocell - This feed will store light data published from your device to Adafruit IO
  • onoff - This feed will act as an on/off switch, publishing data to your device from Adafruit IO

If you have not created an Adafruit IO Feed before, follow this page and come back once you've the created two feeds above.

Create an Adafruit IO Dashboard

Adafruit IO Dashboards are a way to interact with feeds. You can link blocks on dashboards to your feeds. The blocks can either display information about the feed (such as the current temperature) or allow you to interact with a feed by setting it to different values.

Start by creating a new dashboard. Name it whatever you'd like!

Create a Gauge Block

After creating a dashboard, create a Gauge Block to display the value of the Photocell feed.

  • Choose the photocell feed
  • Change the block title to Photocell
  • Set the Gauge Minimum Value to 0
  • Set the Gauge Maximum Value to 1024

If you do not know how to add blocks to a dashboard, head to over this page and come back when you've added a gauge block to your dashboard.

Create a Toggle Switch Block

To send values to the onoff feed you created - create a toggle switch block.

  • Choose the onoff feed
  • Set the block title to On/Off
  • Set the Button On Text to ON
  • Set the Button Off Text to OFF

Your dashboard should look like the following:

CircuitPython works with WiFi-capable boards to enable you to make projects that have network connectivity. This means working with various passwords and API keys. As of CircuitPython 8, there is support for a settings.toml file. This is a file that is stored on your CIRCUITPY drive, that contains all of your secret network information, such as your SSID, SSID password and any API keys for IoT services. It is designed to separate your sensitive information from your code.py file so you are able to share your code without sharing your credentials.

CircuitPython previously used a secrets.py file for this purpose. The settings.toml file is quite similar.

Your settings.toml file should be stored in the main directory of your CIRCUITPY drive. It should not be in a folder.

CircuitPython settings.toml File

This section will provide a couple of examples of what your settings.toml file should look like, specifically for CircuitPython WiFi projects in general.

The most minimal settings.toml file must contain your WiFi SSID and password, as that is the minimum required to connect to WiFi. Copy this example, paste it into your settings.toml, and update:

  • your_wifi_ssid
  • your_wifi_password
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"

Many CircuitPython network-connected projects on the Adafruit Learn System involve using Adafruit IO. For these projects, you must also include your Adafruit IO username and key. Copy the following example, paste it into your settings.toml file, and update:

  • your_wifi_ssid
  • your_wifi_password
  • your_aio_username
  • your_aio_key
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
ADAFRUIT_AIO_USERNAME = "your_aio_username"
ADAFRUIT_AIO_KEY = "your_aio_key"

Some projects use different variable names for the entries in the settings.toml file. For example, a project might use ADAFRUIT_AIO_ID in the place of ADAFRUIT_AIO_USERNAME. If you run into connectivity issues, one of the first things to check is that the names in the settings.toml file match the names in the code.

Not every project uses the same variable name for each entry in the settings.toml file! Always verify it matches the code.

settings.toml File Tips

Here is an example settings.toml file.

# Comments are supported
CIRCUITPY_WIFI_SSID = "guest wifi"
CIRCUITPY_WIFI_PASSWORD = "guessable"
CIRCUITPY_WEB_API_PORT = 80
CIRCUITPY_WEB_API_PASSWORD = "passw0rd"
test_variable = "this is a test"
thumbs_up = "\U0001f44d"

In a settings.toml file, it's important to keep these factors in mind:

  • Strings are wrapped in double quotes; ex: "your-string-here"
  • Integers are not quoted and may be written in decimal with optional sign (+1, -1, 1000) or hexadecimal (0xabcd).
    • Floats, octal (0o567) and binary (0b11011) are not supported.
  • Use \u escapes for weird characters, \x and \ooo escapes are not available in .toml files
    • Example: \U0001f44d for 👍 (thumbs up emoji) and \u20ac for € (EUR sign)
  • Unicode emoji, and non-ASCII characters, stand for themselves as long as you're careful to save in "UTF-8 without BOM" format

 

 

When your settings.toml file is ready, you can save it in your text editor with the .toml extension.

Accessing Your settings.toml Information in code.py

In your code.py file, you'll need to import the os library to access the settings.toml file. Your settings are accessed with the os.getenv() function. You'll pass your settings entry to the function to import it into the code.py file.

import os

print(os.getenv("test_variable"))

In the upcoming CircuitPython WiFi examples, you'll see how the settings.toml file is used for connecting to your SSID and accessing your API keys.

Internet Connect!

Once you have CircuitPython setup and libraries installed, you can get your project connected to the Internet over WiFi. 

To do this, you'll be editing CircuitPython code and will need an editor. We suggest using Mu, a lightweight text editor with support for CircuitPython built-in. 

Click the button below to get instructions on how to install the Mu Editor.

If you have not yet connected your CircuitPython WiFi board to the Internet, follow one of the guides below and come back when you've successfully connected to the internet:

Before continuing, make sure you've connected your CircuitPython board to the internet by following the steps above

settings.toml File Setup for Adafruit IO

While you created a settings.toml file and connected to the internet in the previous step, as described in the Create Your settings.toml File page earlier in the guide. You'll need to edit the settings.toml file to include your Adafruit IO Username and Active Key

Add the following lines to your settings.toml file, replacing _your_adafruit_io_username with your Adafruit IO username.

Then, replace _your_big_huge_super_long_aio_key_ with your Adafruit IO Active Key.

CIRCUITPY_WIFI_SSID = "your-ssid-here"
CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here"

aio_username = "_your_adafruit_io_username_"
aio_key = "_your_big_huge_super_long_aio_key_"

Make sure you save this file before proceeding as settings.toml in the root directory of your board CIRCUITPY drive.

Code Usage

ESP32-S2 Code

CircuitPython boards using the ESP32-S2 such as the MagTag or Metro ESP32-S2 have internet connectivity built into the CircuitPython firmware.

Copy the following code to your code.py file on your microcontroller:

You will need MiniMQTT version 5.0.0 or later AND CircuitPython 6.2.0-beta.1 to work with the ESP32-S2.
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import ssl
import socketpool
import wifi
import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your WiFi credentials. DO NOT share that file or commit it into Git or other
# source control.

# Set your Adafruit IO Username, Key and Port in settings.toml
# (visit io.adafruit.com if you need to create an account,
# or if you need your Adafruit IO key.)
aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}")
wifi.radio.connect(
    os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
)
print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!")
### Feeds ###

# Setup a feed named 'photocell' for publishing to a feed
photocell_feed = aio_username + "/feeds/photocell"

# Setup a feed named 'onoff' for subscribing to changes
onoff_feed = aio_username + "/feeds/onoff"

### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}")
    # Subscribe to all changes on the onoff_feed.
    client.subscribe(onoff_feed)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from Adafruit IO!")


def message(client, topic, message):
    # This method is called when a topic the client is subscribed to
    # has a new message.
    print(f"New message on topic {topic}: {message}")


# Create a socket pool
pool = socketpool.SocketPool(wifi.radio)
ssl_context = ssl.create_default_context()

# If you need to use certificate/key pair authentication (e.g. X.509), you can load them in the
# ssl context by uncommenting the lines below and adding the following keys to your settings.toml:
# "device_cert_path" - Path to the Device Certificate
# "device_key_path" - Path to the RSA Private Key
# ssl_context.load_cert_chain(
#     certfile=os.getenv("device_cert_path"), keyfile=os.getenv("device_key_path")
# )

# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    port=1883,
    username=aio_username,
    password=aio_key,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message

# Connect the client to the MQTT broker.
print("Connecting to Adafruit IO...")
mqtt_client.connect()

photocell_val = 0
while True:
    # Poll the message queue
    mqtt_client.loop(timeout=1)

    # Send a new message
    print(f"Sending photocell value: {photocell_val}...")
    mqtt_client.publish(photocell_feed, photocell_val)
    print("Sent!")
    photocell_val += 1
    time.sleep(5)

ESP32 AirLift Code

CircuitPython boards using an ESP32 "AirLift" Co-Processor such as the PyPortal require a helper library to connect to the internet.

Copy the following code to your code.py file on your microcontroller:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi

import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your WiFi credentials. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(
    board.NEOPIXEL, 1, brightness=0.2
)  # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
# Uncomment below for an externally defined RGB LED
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)

### Feeds ###

# Setup a feed named 'photocell' for publishing to a feed
photocell_feed = aio_username + "/feeds/photocell"

# Setup a feed named 'onoff' for subscribing to changes
onoff_feed = aio_username + "/feeds/onoff"

### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed)
    # Subscribe to all changes on the onoff_feed.
    client.subscribe(onoff_feed)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from Adafruit IO!")


def message(client, topic, message):
    # This method is called when a topic the client is subscribed to
    # has a new message.
    print("New message on topic {0}: {1}".format(topic, message))


# Connect to WiFi
print("Connecting to WiFi...")
esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print("Connected!")

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message

# Connect the client to the MQTT broker.
print("Connecting to Adafruit IO...")
mqtt_client.connect()

photocell_val = 0
while True:
    # Poll the message queue
    mqtt_client.loop()

    # Send a new message
    print("Sending photocell value: %d..." % photocell_val)
    mqtt_client.publish(photocell_feed, photocell_val)
    print("Sent!")
    photocell_val += 1
    time.sleep(5)

(Optional) Change ESP32 AirLift Pinouts

If you are using a board with a built-in ESP32 (like the PyPortal), you do not need to make changes to the code. It's already setup for usage with those boards. However, if you are using an externally connected ESP32 (like the AirLift Breakout), you'll need to define the ESP32's pinouts.

Make sure to change the ESP32 pin definitions in the code to match your wiring. You can do this by uncommenting and editing the following lines in your code to match your wiring.

esp32_cs = DigitalInOut(board.D9)
esp32_ready = DigitalInOut(board.D10)
esp32_reset = DigitalInOut(board.D5)

Feed Publishing Example

Directly after saving the code.py file, open a serial monitor/REPL to see the output. It should look something like the following:

code.py output:
Connecting to Adafruit IO...
Connected to Adafruit IO! Listening for topic changes on brubell/feeds/onoff
Sending photocell value: 0...
Sent!
Sending photocell value: 1...
Sent!
Sending photocell value: 2...
Sent!
Sending photocell value: 3...
Sent!

Navigate to the dashboard you created earlier. You should see the photocell gauge increasing its value as your CircuitPython device publishes the increasing photocell value to Adafruit IO.

If you navigate to the page for the photocell feed, you'll see the values increasing there along with metadata about when the data was received by the Adafruit IO broker.

Feed Subscription Example

While we're publishing the increasing photocell value to Adafruit IO - our code also subscribes to changes on the onoff feed.

The example code.py subscribes to the onoff feed when the client successfully connects to Adafruit IO. You don't need to make any changes to your code!

With the code still running on your CircuitPython device - click the toggle switch to send a value to the onoff feed. You should see the value appear on your serial monitor/REPL.

If you really want to see the speed of MQTT - remove the one second delay in the while True loop.

Change the following code (within while True) from:

print('Sent!')
photocell_val += 1
time.sleep(1)

to

print('Sent!')
photocell_val += 1
time.sleep(0.5)

Be warned - if you do this you will not be able to run the code for very long. This is because Adafruit IO's MQTT server imposes a rate limit to prevent excessive load on the service.

The current Adafruit IO Data Rate is at most 1 request per second (or 60 requests within 60 seconds), without an Adafruit IO+ Boost applied to your account.

Going Further - the Adafruit IO CircuitPython Module

While you can use this code to communicate with Adafruit IO, the recommend method of using CircuitPython with Adafruit IO is with the Adafruit IO CircuitPython module

This module has methods to simplify using the Adafruit IO MQTT API. It also includes helper features to make your experience using Adafruit IO better.

If you want to use Adafruit IO and CircuitPython, check out Adafruit IO CircuitPython and its code examples on GitHub!

Ethernet Setup

This page assumes you've set up and connected your CircuitPython board to an Ethernet connection.

Connect Ethernet Cable

Make sure you have your Ethernet FeatherWing or Ethernet Shield firmly plugged into your hardware, and an Ethernet cable connected to your router or switch.

Code Usage

Copy the following code to a file on your computer. Name the file code.py. With your operating system file explorer/finder, copy this file to the CIRCUITPY drive which appears when your microcontroller board is plugged in via USB.

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import board
import busio
from digitalio import DigitalInOut
import adafruit_connection_manager
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K

import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

cs = DigitalInOut(board.D10)
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialize ethernet interface with DHCP
eth = WIZNET5K(spi_bus, cs)

### Feeds ###

# Setup a feed named 'photocell' for publishing to a feed
photocell_feed = aio_username + "/feeds/photocell"

# Setup a feed named 'onoff' for subscribing to changes
onoff_feed = aio_username + "/feeds/onoff"

### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed)
    # Subscribe to all changes on the onoff_feed.
    client.subscribe(onoff_feed)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from Adafruit IO!")


def message(client, topic, message):
    # This method is called when a topic the client is subscribed to
    # has a new message.
    print("New message on topic {0}: {1}".format(topic, message))


pool = adafruit_connection_manager.get_radio_socketpool(eth)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(eth)

# Set up a MiniMQTT Client
# NOTE: We'll need to connect insecurely for ethernet configurations.
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    is_ssl=False,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message

# Connect the client to the MQTT broker.
print("Connecting to Adafruit IO...")
mqtt_client.connect()

photocell_val = 0
while True:
    # Poll the message queue
    mqtt_client.loop()

    # Send a new message
    print("Sending photocell value: %d..." % photocell_val)
    mqtt_client.publish(photocell_feed, photocell_val)
    print("Sent!")
    photocell_val += 1
    time.sleep(5)

Feed Publishing Example

Directly after saving the code.py file, open a serial monitor/REPL to see the output. It should look something like the following:

code.py output:
Connecting to Adafruit IO...
Connected to Adafruit IO! Listening for topic changes on brubell/feeds/onoff
Sending photocell value: 0...
Sent!
Sending photocell value: 1...
Sent!
Sending photocell value: 2...
Sent!
Sending photocell value: 3...
Sent!

Navigate to the dashboard you created earlier. You should see the photocell gauge increasing its value as your CircuitPython device publishes the increasing photocell value to Adafruit IO.

If you navigate to the page for the photocell feed, you'll see the values increasing there along with metadata about when the data was received by the Adafruit IO broker.

Feed Subscription Example

While we're publishing the increasing photocell value to Adafruit IO - our code also subscribes to changes on the onoff feed.

The example code.py subscribes to the onoff feed when the client successfully connects to Adafruit IO. You don't need to make any changes to your code!

With the code still running on your CircuitPython device - click the toggle switch to send a value to the onoff feed. You should see the value appear on your serial monitor/REPL.

If you really want to see the speed of MQTT - remove the one second delay in the while True loop.

Change the following code (within while True) from:

print('Sent!')
photocell_val += 1
time.sleep(1)

to

print('Sent!')
photocell_val += 1
time.sleep(0.5)

Be warned - if you do this you will not be able to run the code for very long. This is because Adafruit IO's MQTT server imposes a rate limit to prevent excessive load on the service.

The current Adafruit IO Data Rate is at most 1 request per second (or 60 requests within 60 seconds), without an Adafruit IO+ Boost applied to your account.

Going Further - the Adafruit IO CircuitPython Module

While you can use this code to communicate with Adafruit IO, the recommended method of using CircuitPython with Adafruit IO is with the Adafruit IO CircuitPython module

This module has methods to simplify using the Adafruit IO MQTT API. It also includes helper features to make your experience using Adafruit IO better.

If you want to use Adafruit IO and CircuitPython, check out Adafruit IO CircuitPython and the code example for Ethernet on GitHub!

Cellular Data Setup

This page assumes you've set up and connected your CircuitPython board to a cellular data connection.

Connect FONA

Make sure you have attached a LiPoly battery, GSM antenna, GPS antenna to your FONA. 

You must also have a SIM card inserted in the FONA to use the module with cellular data. 

Code Usage

Copy the following code to a file on your computer. Name the file code.py. With your operating system file explorer/finder, copy this file to the CIRCUITPY drive which appears when your microcontroller board is plugged in via USB.

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import board
import busio
import digitalio
import adafruit_connection_manager
from adafruit_fona.adafruit_fona import FONA
import adafruit_fona.adafruit_fona_network as network
import adafruit_fona.adafruit_fona_socket as pool

import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your GPRS credentials. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

### Cellular ###

# Create a serial connection for the FONA connection
uart = busio.UART(board.TX, board.RX)
rst = digitalio.DigitalInOut(board.D4)
# Initialize FONA
fona = FONA(uart, rst)

### Feeds ###

# Setup a feed named 'photocell' for publishing to a feed
photocell_feed = aio_username + "/feeds/photocell"

# Setup a feed named 'onoff' for subscribing to changes
onoff_feed = aio_username + "/feeds/onoff"

### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to Adafruit IO! Listening for topic changes on %s" % onoff_feed)
    # Subscribe to all changes on the onoff_feed.
    client.subscribe(onoff_feed)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from Adafruit IO!")


def message(client, topic, message):
    # This method is called when a topic the client is subscribed to
    # has a new message.
    print("New message on topic {0}: {1}".format(topic, message))


# Initialize cellular data network
network = network.CELLULAR(
    fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password"))
)

while not network.is_attached:
    print("Attaching to network...")
    time.sleep(0.5)
print("Attached!")

while not network.is_connected:
    print("Connecting to network...")
    network.connect()
    time.sleep(0.5)
print("Network Connected!")

ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, fona)

# Set up a MiniMQTT Client
# NOTE: We'll need to connect insecurely for ethernet configurations.
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    is_ssl=False,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message


# Connect the client to the MQTT broker.
print("Connecting to Adafruit IO...")
mqtt_client.connect()

photocell_val = 0
while True:
    # Poll the message queue
    mqtt_client.loop()

    # Send a new message
    print("Sending photocell value: %d..." % photocell_val)
    mqtt_client.publish(photocell_feed, photocell_val)
    print("Sent!")
    photocell_val += 1
    time.sleep(5)

Feed Publishing Example

Directly after saving the code.py file, open a serial monitor/REPL to see the output. It should look something like the following:

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
Attaching to network...
Attached to network!
Connecting to network...
Connected to network!
Connecting to Adafruit IO...
Connected to Adafruit IO! Listening for topic changes on brubell/feeds/onoff
Sending photocell value: 0...
Sent!
Sending photocell value: 1...
Sent!
Sending photocell value: 2...
Sent!
Sending photocell value: 3...
Sent!

Navigate to the dashboard you created earlier. You should see the photocell gauge increasing its value as your CircuitPython device publishes the increasing photocell value to Adafruit IO.

If you navigate to the page for the photocell feed, you'll see the values increasing there along with metadata about when the data was received by the Adafruit IO broker.

Feed Subscription Example

While we're publishing the increasing photocell value to Adafruit IO - our code also subscribes to changes on the onoff feed.

The example code.py subscribes to the onoff feed when the client successfully connects to Adafruit IO. You don't need to make any changes to your code!

With the code still running on your CircuitPython device - click the toggle switch to send a value to the onoff feed. You should see the value appear on your serial monitor/REPL.

If you really want to see the speed of MQTT - remove the one second delay in the while True loop.

Change the following code (within while True) from:

print('Sent!')
photocell_val += 1
time.sleep(1)

to

print('Sent!')
photocell_val += 1
time.sleep(0.5)

Be warned - if you do this you will not be able to run the code for very long. This is because Adafruit IO's MQTT server imposes a rate limit to prevent excessive load on the service.

The current Adafruit IO Data Rate is at most 1 request per second (or 60 requests within 60 seconds), without an Adafruit IO+ Boost applied to your account.

Going Further - the Adafruit IO CircuitPython Module

While you can use this code to communicate with Adafruit IO, the recommended method of using CircuitPython with Adafruit IO is with the Adafruit IO CircuitPython module

This module has methods to simplify using the Adafruit IO MQTT API. It also includes helper features to make your experience using Adafruit IO better.

If you want to use Adafruit IO and CircuitPython, check out Adafruit IO CircuitPython and the code example for Ethernet on GitHub!

You can connect your CircuitPython device to any MQTT broker of your choice. MQTT brokers generally fall in three categories: Commercial hosted (Paid), Free hosted (Adafruit IO's MQTT broker lives on servers owned by Adafruit - we have a free tier), or Free and self-hosted (bring your own server!).

Commercial Brokers

These large-scale commercial (paid) brokers come with a higher data-rate speed and usually include device management features. 

These brokers can also connect to CircuitPython devices running MiniMQTT. The primary advantage of using one of these services over your own broker or Adafruit IO is integration with a larger suite of cloud-hosted tools. For example, if you are sending data to an Azure IoT Hub via MQTT, you could visualize sensor data using PowerBI or run machine learning models on it from Azure. 

Self-Hosted Brokers

There are plenty of tools out there to host your own MQTT broker. We like Eclipse's Mosquitto - it's an open-source broker which implements the MQTT protocol. There are images for Mac, Linux, Windows and Raspberry Pi.

settings.toml File Setup for Adafruit IO

While you created a settings.toml file and connected to the internet in the previous step, as described in the Create Your settings.toml File page earlier in the guide. You'll need to edit the settings.toml file to include your Adafruit IO Username and Active Key

Add the following lines to your settings.toml file, replacing _your_adafruit_io_username with your Adafruit IO username.

Then, replace _your_big_huge_super_long_aio_key_ with your Adafruit IO Active Key.

CIRCUITPY_WIFI_SSID = "your-ssid-here"
CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here"

aio_username = "_your_adafruit_io_username_"
aio_key = "_your_big_huge_super_long_aio_key_"

Make sure you save this file before proceeding as settings.toml in the root directory of your board CIRCUITPY drive.

Code

Copy the following code to your code.py file on your microcontroller:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import board
import busio
from digitalio import DigitalInOut
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your WiFi credentials. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(
            os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
        )
    except RuntimeError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)

### Topic Setup ###

# MQTT Topic
# Use this topic if you'd like to connect to a standard MQTT broker
# mqtt_topic = "test/topic"

# Adafruit IO-style Topic
# Use this topic if you'd like to connect to io.adafruit.com
mqtt_topic = aio_username + "/feeds/temperature"


### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connect(mqtt_client, userdata, flags, rc):
    # This function will be called when the mqtt_client is connected
    # successfully to the broker.
    print("Connected to MQTT Broker!")
    print("Flags: {0}\n RC: {1}".format(flags, rc))


def disconnect(mqtt_client, userdata, rc):
    # This method is called when the mqtt_client disconnects
    # from the broker.
    print("Disconnected from MQTT Broker!")


def subscribe(mqtt_client, userdata, topic, granted_qos):
    # This method is called when the mqtt_client subscribes to a new feed.
    print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))


def unsubscribe(mqtt_client, userdata, topic, pid):
    # This method is called when the mqtt_client unsubscribes from a feed.
    print("Unsubscribed from {0} with PID {1}".format(topic, pid))


def publish(mqtt_client, userdata, topic, pid):
    # This method is called when the mqtt_client publishes data to a feed.
    print("Published to {0} with PID {1}".format(topic, pid))


def message(client, topic, message):
    print("New message on topic {0}: {1}".format(topic, message))


pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Connect callback handlers to mqtt_client
mqtt_client.on_connect = connect
mqtt_client.on_disconnect = disconnect
mqtt_client.on_subscribe = subscribe
mqtt_client.on_unsubscribe = unsubscribe
mqtt_client.on_publish = publish
mqtt_client.on_message = message

print("Attempting to connect to %s" % mqtt_client.broker)
mqtt_client.connect()

print("Subscribing to %s" % mqtt_topic)
mqtt_client.subscribe(mqtt_topic)

print("Publishing to %s" % mqtt_topic)
mqtt_client.publish(mqtt_topic, "Hello Broker!")

print("Unsubscribing from %s" % mqtt_topic)
mqtt_client.unsubscribe(mqtt_topic)

print("Disconnecting from %s" % mqtt_client.broker)
mqtt_client.disconnect()

Before running your code, you'll need to make a few small changes for your configuration.

First, replace mqtt_topic with the mqtt topic you'd like to subscribe to. For example, if you are building a weather station and want to subscribe to its temperature.

Change the mqtt_topic from

mqtt_topic = 'test/topic'

to

mqtt_topic= 'weatherstation/temperature'

MiniMQTT Port Configuration

By default, MiniMQTT connects to port 8883 (Secure/SSL). If you'd like to connect to a different port on the broker, use the following code to initialize the client.

# Set up a MiniMQTT Client
client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
  	port=1883
)

Code Usage

After setting up your topic and broker configuration, it's time to connect. Save the code.py file and open the serial monitor.

The client attempts to connect to the MQTT broker you specified.

Attempting to connect to io.adafruit.com
Connected to MQTT Broker!
Flags: 0
RC: 0

The client subscribes to the mqtt_topic you specified.

Subscribed to user/topic with QOS level 1

And notifies you that it subscribed to mqtt_topic with a quality of service level of 1. You can set the quality of service level by modifying the call to subscribe (read the docs about this method here).

The client publishes the string Hello Broker to the mqtt_topic.

Published to user/topic with PID 11

The client unsubscribes from the mqtt_topic.

Unsubscribed from user/topic with PID 22

Finally, the client disconnects from the MQTT broker

Disconnected from MQTT Broker!

The code tests all the primary methods of the MiniMQTT client. We'll walk through the code to understand how this example works.

The code first connects the ESP32 to the wireless network you specified in the settings.toml file on your CIRCUITPY drive.

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(
            os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
        )
    except RuntimeError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)

Then, it initializes a MQTT object using the network interface (esp, ethernet) you are using:

# Initialize MQTT interface with the esp interface
MQTT.set_socket(socket, esp)

Once a WiFi network connection has been established, the code sets up a new MiniMQTT client instance

client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
)

Then, the code attaches callback handler methods to the client. 

# Connect callback handlers to client
client.on_connect = connect
client.on_disconnect = disconnect
client.on_subscribe = subscribe
client.on_unsubscribe = unsubscribe
client.on_publish = publish

MiniMQTT Callback Methods

We're going to stop here to explain the utility and operation of the callback methods which are an important part of building programs with MiniMQTT.

Further up in the code, there are methods named connect(), publish(), subscribe(), unsubscribe() and disconnect()

def connect(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print('Connected to MQTT Broker!')
    print('Flags: {0}\n RC: {1}'.format(flags, rc))

def disconnect(client, userdata, rc):
    # This method is called when the client disconnects
    # from the broker.
    print('Disconnected from MQTT Broker!')

def subscribe(client, userdata, topic, granted_qos):
    # This method is called when the client subscribes to a new feed.
    print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))

def unsubscribe(client, userdata, topic, pid):
    # This method is called when the client unsubscribes from a feed.
    print('Unsubscribed from {0} with PID {1}'.format(topic, pid))

def publish(client, userdata, topic, pid):
    # This method is called when the client publishes data to a feed.
    print('Published to {0} with PID {1}'.format(topic, pid))

Each of these methods is executed when a callback is successfully read back from a MQTT control packet sent by the client.

This means that whenever MiniMQTT sends a CONNECT command to the MQTT broker, the broker should respond that it received it and send another packet back to MiniMQTT (called a CONNACK packet).

If MiniMQTT receives this CONNACK packet, it will execute the client's on_connect method. It'll raise an error if it did not connect successfully. 

As an example, we'll set up a connect callback method named connect. This method will print the string, Connected to MQTT Broker, if it successfully connects to your MQTT broker.

def connect(client, userdata, flags, rc):
    print('Connected to MQTT Broker!')

Then, we'll set the client's on_connect property to the connect method.

client.on_connect = connect

To connect the MiniMQTT client to the broker, we'll run the following line:

client.connect()

When the client's connect() method runs, it sends a CONNECT command to the MQTT broker with information including the broker's address and waits for the CONNACK.

Once the CONNACK is received, MiniMQTT calls on_connect from "behind the scenes". Since connect is attached to on_connect, connect will execute and print out the following message:

Connected to MQTT Broker!

Ok - we now understand how the connect method callback works. The rest of the example calls more functions which also execute similar callback methods.

First, the code attempts to connect to the MQTT broker you specified.

print('Attempting to connect to %s'%client.broker)
client.connect()

If the CircuitPython board connects successfully, it'll run the connect method.

The code will next attempt to subscribe to a mqtt_topic you defined earlier in the code.

print('Subscribing to %s'%mqtt_topic)
client.subscribe(mqtt_topic)

If the client successfully subscribes to the mqtt_topic, the subscribe method will execute. 

Once the client successfully subscribes - it'll publish to the mqtt_topic.

print('Publishing to %s'%mqtt_topic)
client.publish(mqtt_topic, 'Hello Broker!')

After publishing, your broker should display that it received a value from the client. In addition, the publish method should run.

Next, the client unsubscribes from the mqtt_topic and disconnects from the broker.

print('Unsubscribing from %s'%mqtt_topic)
client.unsubscribe(mqtt_topic)

print('Disconnecting from %s'%client.broker)
client.disconnect()

These two methods should call the unsubscribe and disconnect methods.

That's it! The next page will go over some of the more advanced features of this library.

MiniMQTT Loop

You should always use a loop when writing CircuitPython code which uses the MiniMQTT module. The loop method checks incoming and processes outgoing MQTT messages along with keeping the network connection between your board and the MQTT broker alive. 

loop

Calling loop creates a non-blocking network loop. You can create new code below the call to loop, and it'll be executed. This type of loop should be run frequently to avoid disconnecting from the MQTT server. The loop method also does not handle network hardware (WiFi) or MQTT broker disconnection. You'll need to handle that yourself.

Here's an example of using a non-blocking loop.

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi

import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your WiFi credentials. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(
    board.NEOPIXEL, 1, brightness=0.2
)  # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
# Uncomment below for an externally defined RGB LED
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)

### Adafruit IO Setup ###

# Setup a feed named `testfeed` for publishing.
default_topic = aio_username + "/feeds/testfeed"


### Code ###
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print(f"Connected to MQTT broker! Listening for topic changes on {default_topic}")
    # Subscribe to all changes on the default_topic feed.
    client.subscribe(default_topic)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from MQTT Broker!")


def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print(f"New message on topic {topic}: {message}")


# Connect to WiFi
print("Connecting to WiFi...")
esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print("Connected!")

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message

# Connect the client to the MQTT broker.
mqtt_client.connect()

photocell_val = 0
while True:
    # Poll the message queue
    mqtt_client.loop()

    # Send a new message
    print(f"Sending photocell value: {photocell_val}")
    mqtt_client.publish(default_topic, photocell_val)
    photocell_val += 1
    time.sleep(3)

Here's an example of using a loop with a network interface. In this example, we use the loop to reset the network interface (wifi.reset) and reconnect the socket to the MQTT broker (mqtt_client.reconnect)

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi

import adafruit_minimqtt.adafruit_minimqtt as MQTT

# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
# with your WiFi credentials. Add your Adafruit IO username and key as well.
# DO NOT share that file or commit it into Git or other source control.

aio_username = os.getenv("aio_username")
aio_key = os.getenv("aio_key")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(
    board.NEOPIXEL, 1, brightness=0.2
)  # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
# Uncomment below for an externally defined RGB LED
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)

### Adafruit IO Setup ###

# Setup a feed named `testfeed` for publishing.
default_topic = aio_username + "/feeds/testfeed"

### Code ###


# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to MQTT broker! Listening for topic changes on %s" % default_topic)
    # Subscribe to all changes on the default_topic feed.
    client.subscribe(default_topic)


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from MQTT Broker!")


def message(client, topic, message):
    """Method callled when a client's subscribed feed has a new
    value.
    :param str topic: The topic of the feed with a new value.
    :param str message: The new value
    """
    print("New message on topic {0}: {1}".format(topic, message))


# Connect to WiFi
print("Connecting to WiFi...")
esp.connect_AP(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print("Connected!")

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    socket_pool=pool,
    ssl_context=ssl_context,
)

# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message

# Connect the client to the MQTT broker.
print("Connecting to MQTT broker...")
mqtt_client.connect()

# Start a blocking message loop...
# NOTE: NO code below this loop will execute
# NOTE: Network reconnection is handled within this loop
while True:
    try:
        mqtt_client.loop()
    except (ValueError, RuntimeError) as e:
        print("Failed to get data, retrying\n", e)
        esp.reset()
        time.sleep(1)
        esp.connect_AP(
            os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
        )
        mqtt_client.reconnect()
        continue
    time.sleep(1)

MiniMQTT Client Identifier

By default, MiniMQTT will generate a unique, randomly generated client identifier based off the CircuitPython device's microcontroller's UUID and a random number. The broker will see a client named something like cpy-3123

If you'd like to set a custom client_id (what the broker sees the CircuitPython device as), you can provide a string. Do make sure the client_id's you create are unique, or your broker will disconnect them.

client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    client_id='brentspyportal'
)

MiniMQTT Logging

MiniMQTT uses the CircuitPython logger module for printing different types of errors, depending on the priority the logger was set to.

To attach a logger to a MiniMQTT client, simply:

client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=aio_username,
    password=aio_key,
    log=True
)

Then, you will need to add another line setting the logger's level. While the logger is initialized to the INFO level by default, you may want to see more information about your current MQTT session.

To set the logger to a higher priority logging level, like DEBUG, add the following line after the MQTT client has been initialized:

client.set_logger_level(DEBUG)

MiniMQTT Last Will and Testament

MiniMQTT supports setting publishing a message to a specific topic when your MQTT client disconnects.

To use the last will - specify the topic you'd like to publish to and provide it with a message to publish when the client disconnects.

client.will_set('device/status/','Goodbye!')

This method must be called before the connect method. The last will and testament also must be allowed by your MQTT Broker - Adafruit IO does not support this feature.

This guide was first published on Jul 23, 2019. It was last updated on Jul 14, 2024.