Connect your Raspberry Pi Pico CircuitPython project to the internet by adding an AirLift breakout board. The Adafruit AirLift is a breakout board that lets you use the ESP32 as a WiFi co-processor for a Pico.

In this guide, you will wire up a Pico to an AirLift breakout and connect to the internet. Then, you'll learn how to fetch JSON and raw text data from the internet. Finally, an example is included for connecting your Pico to Adafruit IO, the Adafruit internet-of-things service, so you can interact with and visualize your project's data.

Why use AirLift?

Having WiFi managed by a separate chip means your code is simpler, you don't have to cache socket data, or compile in & debug an SSL library. With AirLift, you can send basic but powerful socket-based commands over 8MHz SPI for high speed data transfer. The ESP32 can handle all the heavy lifting of connecting to a WiFi network and transferring data from a site, and using the latest TLS/SSL encryption (it has root certificates pre-burned in).

The firmware on board is a slight variant of the Arduino WiFiNINA core, which works great, and our Adafruit IO Libraries for CircuitPython support AirLift!

Parts

Adafruit AirLift – ESP32 WiFi Co-Processor 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
Raspberry Pi Pico RP2040
The Raspberry Pi foundation changed single-board computing when they released the Raspberry Pi computer, now they're ready to...
Out of Stock
1 x USB Cable
USB cable - USB A to Micro-B - 3 foot long
1 x Breadboard
Full sized breadboard
1 x Breadboarding Wires
Breadboarding wire bundle

Prepare the header strip:

Cut the strip to length if necessary. It will be easier to solder if you insert it into a breadboard - long pins down

Add the breakout board:

Place the breakout board over the pins so that the short pins poke through the breakout pads

And Solder!

Be sure to solder all 12 pins for reliable electrical contact.

(For tips on soldering, be sure to check out our Guide to Excellent Soldering).

You're done! Check your solder joints visually and continue onto the next steps

Mu is a simple code editor that works with the Adafruit CircuitPython boards. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in so you get immediate feedback from your board's serial output!

Mu is our recommended editor - please use it (unless you are an experienced coder with a favorite editor already!)

Download and Install Mu

Download Mu from https://codewith.mu. Click the Download or Start Here links there for downloads and installation instructions. The website has a wealth of other information, including extensive tutorials and and how-to's.

 

Using Mu

The first time you start Mu, you will be prompted to select your 'mode' - you can always change your mind later. For now please select CircuitPython!

The current mode is displayed in the lower right corner of the window, next to the "gear" icon. If the mode says "Microbit" or something else, click the Mode button in the upper left, and then choose "CircuitPython" in the dialog box that appears.

Mu attempts to auto-detect your board, so please plug in your CircuitPython device and make sure it shows up as a CIRCUITPY drive before starting Mu

You can now explore Mu! The three main sections of the window are labeled below; the button bar, the text editor, and the serial console / REPL.

Now you're ready to code! Let's keep going...

This guide requires CircuitPython to be installed on your Pico

If you have not yet installed CircuitPython on your Raspberry Pi Pico, visit our Getting Started product page for the Pico and come back here when you've installed CircuitPython.

Pico Wiring

You MUST use the Pico's VSYS pin for powering the AirLift Breakout.
  • Pico VSYS to AirLift Vin
  • Pico GND to AirLift GND
  • Pico GP10 (SPI1 SCK) to AirLift SCK
  • Pico GP11 (SPI1 TX) to AirLift MOSI
  • Pico GP12 (SPI1 RX) to AirLift MISO
  • Pico GP13 (SPI1 CSn) to AirLift CS
  • Pico GP14 to AirLift BUSY
  • Pico GP15 to AirLift !RST

For more information about the SPI peripherals and pinouts of the Pico, check out this guide.

CircuitPython Installation of ESP32SPI Library

You'll need to install the Adafruit CircuitPython ESP32SPI library on your CircuitPython 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.  Our CircuitPython starter guide has a great page on how to install the library bundle.

You can manually install the necessary libraries from the bundle:

  • adafruit_bus_device
  • adafruit_minimqtt
  • adafruit_io
  • adafruit_esp32_spi
  • adafruit_requests

CircuitPython Usage

Connect to the board's serial REPL via Mu so you are at the CircuitPython >>> prompt. Once that's done, load up the following example in Mu and save it to the Pico as code.py.

import board
import busio
from digitalio import DigitalInOut

from adafruit_esp32spi import adafruit_esp32spi
import adafruit_requests as requests

print("Raspberry Pi RP2040 - ESP32SPI hardware test")

esp32_cs = DigitalInOut(board.GP13)
esp32_ready = DigitalInOut(board.GP14)
esp32_reset = DigitalInOut(board.GP15)

spi = busio.SPI(board.GP10, board.GP11, board.GP12)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])

for ap in esp.scan_networks():
    print("\t%s\t\tRSSI: %d" % (str(ap['ssid'], 'utf-8'), ap['rssi']))

print("Done!")

Once all the files are copied from your computer to the Pico, you should have the following files on your CIRCUITPY drive.

The output to the serial monitor should look something like the following:

Make sure you see the same output! If you don't, check your wiring. Once you've succeeded, continue onto the next page!

If you can read the Firmware and MAC address but fails on scanning SSIDs, check that your AirLift is wired to the Pico's VSYS pin.

Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. 

To get connected, you will need to start by creating a secrets file.

What's a secrets file?

We expect people to share tons of projects as they build CircuitPython WiFi widgets. What we want to avoid is people accidentally sharing their passwords or secret tokens and API keys. So, we designed all our examples to use a secrets.py file, that is in your CIRCUITPY drive, to hold secret/private/custom data. That way you can share your main project without worrying about accidentally sharing private stuff.

Your secrets.py file should look like this:

# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : 'home ssid',
    'password' : 'my password',
    'timezone' : "America/New_York", # http://worldtimeapi.org/timezones
    }

Inside is a python dictionary named secrets with a line for each entry. Each entry has an entry name (say 'ssid') and then a colon to separate it from the entry key 'home ssid' and finally a comma ,

At a minimum you'll need the ssid and password for your local WiFi setup. As you make projects you may need more tokens and keys, just add them one line at a time. Other non-secret data like your timezone can also go here, just because it's called secrets doesn't mean you can't have general customization data in there!

For the correct time zone string, look at http://worldtimeapi.org/timezones and remember that if your city is not listed, look for a city in the same time zone, for example Boston, New York, Philadelphia, Washington DC, and Miami are all on the same time as New York.

Of course, don't share your secrets.py - keep that out of GitHub, Discord or other project-sharing sites.

Connect to WiFi

OK now you have your secrets setup - you can connect to the Internet using the ESP32SPI and the Requests modules.

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

Before continuing make sure your board's lib folder or root filesystem has the above files copied over:

  • adafruit_bus_device
  • adafruit_minimqtt
  • adafruit_io
  • adafruit_esp32_spi
  • adafruit_requests

Connect to the board's serial REPL via Mu so you are at the CircuitPython >>> prompt. Once that's done, load up the following example in Mu and save it to the Pico as code.py.

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

import board
import busio
from digitalio import DigitalInOut
import adafruit_requests as requests
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

print("Raspberry Pi RP2040 - ESP32 SPI webclient test")

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"

# Raspberry Pi RP2040 Pinout
esp32_cs = DigitalInOut(board.GP13)
esp32_ready = DigitalInOut(board.GP14)
esp32_reset = DigitalInOut(board.GP15)

spi = busio.SPI(board.GP10, board.GP11, board.GP12)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

requests.set_socket(socket, esp)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])

for ap in esp.scan_networks():
    print("\t%s\t\tRSSI: %d" % (str(ap["ssid"], "utf-8"), ap["rssi"]))

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["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)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print(
    "IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))
)
print("Ping google.com: %d ms" % esp.ping("google.com"))

# esp._debug = True
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print("-" * 40)
print(r.text)
print("-" * 40)
r.close()

print()
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print("-" * 40)
print(r.json())
print("-" * 40)
r.close()

print("Done!")

Once all the files are copied from your computer to the Pico, you should have the following files on your CIRCUITPY drive.

The code should run and you should get something like the following in your serial window:

In order, the example code:

Initializes the ESP32 over SPI using the SPI port and 3 control pins.

# Raspberry Pi RP2040 Pinout
esp32_cs = DigitalInOut(board.GP13)
esp32_ready = DigitalInOut(board.GP14)
esp32_reset = DigitalInOut(board.GP15)

spi = busio.SPI(board.GP10, board.GP11, board.GP12)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

This tells the requests library the type of socket being used (socket type varies by connectivity type - this is using the adafruit_esp32spi_socket for the example). It also sets the interface to an esp object. This is a little bit of a hack, but it lets the code use requests like CPython does.

requests.set_socket(socket, esp)

Verifies an ESP32 is found, checks the firmware and MAC address

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])

Performs a scan of all access points it can see and prints out the name and signal strength:

for ap in esp.scan_networks():
    print("\t%s\t\tRSSI: %d" % (str(ap['ssid'], 'utf-8'), ap['rssi']))

Connects to the AP defined here, then prints out the local IP address, attempts to do a domain name lookup and ping google.com to check network connectivity (note sometimes the ping fails or takes a while, this isn't a big deal).

print("Connecting to AP...")
esp.connect_AP(b'MY_SSID_NAME', b'MY_SSID_PASSWORD')
print("Connected to", str(esp.ssid, 'utf-8'), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print("IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com")))
print("Ping google.com: %d ms" % esp.ping("google.com"))

OK to get to the really interesting part. With a large-RAM (well, over 32 KB) device like the Pico, a lot of neat tricks can be done. For example you can implement an interface a lot like requests - which makes getting data really really easy.

To read in all the text from a web URL call requests.get - you can pass in https URLs for SSL connectivity

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print('-'*40)
print(r.text)
print('-'*40)
r.close()

Or, if the data is in structured JSON, you can get the json pre-parsed into a Python dictionary that can be easily queried or traversed. (Again, only for Pico, nRF52840, M4 and other high-RAM boards)

JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print('-'*40)
print(r.json())
print('-'*40)
r.close()

Adafruit IO is an internet of things platform (designed by Adafruit!) to help connect your project to the internet. You're going to send the Pico's CPU temperature to Adafruit IO and display it on a dashboard page. You'll add a toggle block to turn on or off the Pico's LED from the internet.

Secrets File Setup for Adafruit IO

To connect to Adafruit IO, you will need to start by modifying your secrets file to include your Adafruit IO credentials.

Navigate to io.adafruit.com and click My Key to obtain your Adafruit IO username and Active Key. Add these credentials into the secrets file, which will now look something like the following:

# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : 'home ssid',
    'password' : 'my password',
    'timezone' : "America/New_York", # http://worldtimeapi.org/timezones
    'aio_username': 'MY_ADAFRUIT_IO_USERNAME',
    'aio_password': 'MY_ADAFRUIT_IO_PASSWORD'
    }

Create Adafruit IO Feeds

Next, set up two new Adafruit IO feeds - temperature and led. The temperature feed will store the value of the Pico's CPU temperature and the LED feed will store the state of a toggle switch block on an Adafruit IO dashboard.

Create an Adafruit IO Dashboard

Navigate to the Adafruit IO Dashboards page and create a new dashboard named Pico RP2040.

Click the cog in the upper right hand corner of the dashboard to bring up the Dashboard Settings.

From the Dashboard settings, click Create New Block

Add a toggle block to turn the Pico's LED on or off.

  • From the Create a New Block screen, select the Toggle Block.
  • Connect the led feed to the block by ticking the checkbox.
  • In the final screen, click Create Block.

Next, create a gauge block to display the Pico's CPU temperature.

  • Create a new gauge block.
  • Connect the temperature feed to the block by ticking the checkbox.
  • Under Block Settings, you may change the block's look and feel.
  • When you've finished customizing the block, click Create Block.

Once you've finished creating both blocks, your dashboard should look something like the following image:

Code 

Copy the code below into Mu and save as a file named code.py on your CIRCUITPY drive.

# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
from microcontroller import cpu
import board
import busio
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_io.adafruit_io import IO_MQTT

### WiFi ###

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# Raspberry Pi RP2040
esp32_cs = DigitalInOut(board.GP13)
esp32_ready = DigitalInOut(board.GP14)
esp32_reset = DigitalInOut(board.GP15)

spi = busio.SPI(board.GP10, board.GP11, board.GP12)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets)

# Configure the RP2040 Pico LED Pin as an output
led_pin = DigitalInOut(board.LED)
led_pin.switch_to_output()

# Define callback functions which will be called when certain events happen.
# pylint: disable=unused-argument
def connected(client):
    # Connected function will be called when the client is connected to Adafruit IO.
    print("Connected to Adafruit IO! ")


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))


# pylint: disable=unused-argument
def disconnected(client):
    # Disconnected function will be called when the client disconnects.
    print("Disconnected from Adafruit IO!")


def on_led_msg(client, topic, message):
    # Method called whenever user/feeds/led has a new value
    print("New message on topic {0}: {1} ".format(topic, message))
    if message == "ON":
        led_pin.value = True
    elif message == "OFF":
        led_pin.value = False
    else:
        print("Unexpected message on LED feed.")


# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected!")

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

# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=secrets["aio_username"],
    password=secrets["aio_key"],
)

# Initialize an Adafruit IO MQTT Client
io = IO_MQTT(mqtt_client)

# Connect the callback methods defined above to Adafruit IO
io.on_connect = connected
io.on_disconnect = disconnected
io.on_subscribe = subscribe

# Set up a callback for the led feed
io.add_feed_callback("led", on_led_msg)

# Connect to Adafruit IO
print("Connecting to Adafruit IO...")
io.connect()

# Subscribe to all messages on the led feed
io.subscribe("led")

prv_refresh_time = 0.0
while True:
    # Poll for incoming messages
    try:
        io.loop()
    except (ValueError, RuntimeError) as e:
        print("Failed to get data, retrying\n", e)
        wifi.reset()
        io.reconnect()
        continue
    # Send a new temperature reading to IO every 30 seconds
    if (time.monotonic() - prv_refresh_time) > 30:
        # take the cpu's temperature
        cpu_temp = cpu.temperature
        # truncate to two decimal points
        cpu_temp = str(cpu_temp)[:5]
        print("CPU temperature is %s degrees C" % cpu_temp)
        # publish it to io
        print("Publishing %s to temperature feed..." % cpu_temp)
        io.publish("temperature", cpu_temp)
        print("Published!")
        prv_refresh_time = time.monotonic()

Once all the files are copied from your computer to the Pico, you should have the following files on your CIRCUITPY drive.

Code Usage

The code should connect to your wireless network and Adafruit IO. 

Connecting to WiFi...
Connected!
Connecting to Adafruit IO...
Connected to Adafruit IO!
Subscribed to brubell/f/led with QOS level 0
CPU temperature is 24.32 degrees C
Publishing 24.32 to temperature feed...
Published!

Navigate to your Adafruit IO Dashboard and you should see a new value in the gauge block. Every 30 seconds, the Pico reads its internal CPU temperature and sends it to Adafruit IO. 

Toggle the switch block to turn the Pico's LED on or off:

This guide was first published on Mar 05, 2021. It was last updated on 2021-03-05 17:17:51 -0500.