Give your ItsyBitsy project a lift with the Adafruit AirLift Bitsy Add-On! This sweet add-on for the ItsyBitsy lets you use the powerful ESP32 as a WiFi co-processor. You probably have your favorite ItsyBitsy (like the ItsyBitsy M4) that comes with its own set of awesome peripherals and lots of libraries. But it doesn't have WiFi built in! So let's give that chip a best friend, the ESP32. This chip can handle all the heavy lifting of connecting to a WiFi network and transferring data from a site, even if its using the latest TLS/SSL encryption (it has root certificates pre-burned in).

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. Instead the Airlift Add-On allows you to send basic but powerful socket-based commands over 8MHz SPI for high speed data transfer. You can use the 3V 32u4, M0, or M4 ItsyBitsy in  Arduino although the '32u4 will not be able to do very complex tasks or buffer a lot of data because they do not have a lot of RAM. The add-on also works great with CircuitPython, though a SAMD51/Cortex M4 minimum required since we need a bunch of RAM. All you need is an SPI bus and 2 control pins plus a power supply that can provide up to 250mA during WiFi usage.

We placed an ESP32 module on a BitsyWing with a tri-state chip for MOSI so you can share the SPI bus. The add on comes fully assembled and tested, pre-programmed with ESP32 SPI WiFi co-processor firmware that you can use in CircuitPython to use this into  WiFi co-processsor over SPI + 2 pins. We also toss in some header so you can solder it in though you'll need stacking headers for your ItsyBitsy to put the add-on on top.

The firmware on board is a slight variant of the Arduino WiFiNINA core, which works great! 

Power Pins

  • GND - Common power/logic ground.
  • 3.3V - Power supply for the ublox WiFi module. You'll need up to 250mA to power the module. ItsyBitsy's tend to have a 500mA regulator so they should be OK, just make sure you're aware of your power budget

SPI and Control Pins

To keep transfers speedy, we use SPI not UART Serial. UART is too slow and hard to synchronize. This uses more pins but the experience is much better!

Classic SPI Pins:

  • SCK - SPI Clock from your microcontroller, logic level is 3.3V only
  • MISO - SPI Data from the AirLift to the microcontroller, logic level is 3.3V only. This is tri-stated when not selected, so you can share the SPI bus with other devices.
  • MOSI- SPI Data to the AirLift from the microcontroller, logic level is 3.3V only.
  • ESPCS - SPI Chip Select from the microcontroller to start sending commands to the AirLift, logic level is 3.3V only

Required Control Pins:

  • ESPBUSY - this pin is an input from the AirLift, it will let us know when its ready for more commands to be sent. This is 3.3V logic out. This pin must be connected.
  • ESPRST- this pin is an output to the AirLift. Set low to put the AirLift into reset. You should use this pin, even though you might be able to run for a short while without it, it's essential to 'kick' the chip if it ever gets into a locked up state. Logic level is 3.3V
Warning! The initial batch has the silk screen labels for ESPBUSY and ESPRST swapped! The order should be ECS/RST/BSY not ECS/BSY/RST. The schematic, code, and fritzing object have been fixed. The silk will be corrected for the next run

Optional Control Pins:

  • ESPGPIO0 - this is the ESP32 GPIO0 pin, which is used to put the WiFi module it into bootloading mode if you want to update the firmware. It is also used if you like when the ESP32 is acting as a server, to let you know data is ready for reading. It's not required, you'll need to solder the pad on the bottom of the Bitsy Add-on to connect it.
  • ESPRX ESPTX - Serial data in and Serial data out, used for bootloading new firmware only. Leave disconnected when not uploading new WiFi firmware to the AirLift (which is a rare occurrence). You'll need to solder the two pads on the bottom of the Bitsy Add-on to use these pins.

RGB LED

There is a small RGB LED to the left of the ESP32. These RGB LEDs are available in the Arduino and CircuitPython libraries if you'd like to PWM them for a visual alert. They're connected to the ESP32's pins 26 (Red), 25 (Green), and 27 (Blue).

The antenna on the new, smaller ESP32 module is very delicate! Avoid touching it and grab the add-on by the long sides to avoid smushing the antenna

To stack the Airlift Add-on on top of the ItsyBitsy, you'll need stacking headers for the ItsyBitsy. For now you'll need to cut down a set of Feather Stacking Headers to fit the ItsyBitsy however we will have headers with the correctly length soon!

You can use the AirLift with Arduino. Unlike CircuitPython, it work work with most of the Arduino compatible ItsyBitsies, even the 3V ItsyBitsy 32u4. However, if you want to use libraries like ArduinoJSON or add sensors and SD card, you'll really want an ATSAMD21 (Cortex M0) or ATSAMD51 (Cortex M4), both of which have plenty or RAM

Library Install

We're using a variant of the Arduino WiFiNINA library, which is amazing and written by the Arduino team! The official WiFi101 library won't work because it doesn't support the ability to change the pins.

So! We made a fork that you can install.

Click here to download the library:

Within the Arduino IDE, select Install library from ZIP...

adafruit_products_image.png

First Test

OK now you have it wired and library installed, time to test it out!

Lets start by scanning the local networks. Load up the ScanNetworks example

adafruit_products_image.png

At the top you'll see a section where the GPIO pins are defined

adafruit_products_image.png

If you don't see this, you may have the wrong WiFiNINA library installed. Uninstall it and re-install the Adafruit one as above.

Compile and upload to your board wired up to the AirLift

adafruit_products_image.png

If you don't even get the MAC address printed out, check your wiring.

If you get the MAC address but cannot scan any networks, check your power supply. You need a solid 3.3V into Vin in order for the ESP32 not to brown out.

WiFi Connection Test

Now that you have your wiring checked, time to connect to the Internet!

Open up the WiFiWebClient example

adafruit_products_image.png

Open up the secondary tab, arduino_secrets.h. This is where you will store private data like the SSID/password to your network.

adafruit_products_image.png

You must change these string values before updating to your board!

After you've set it correctly, upload and check the serial monitor. You should see the following. If not, go back, check wiring, power and your SSID/password

adafruit_products_image.png

Secure Connection Example

Many servers today do not allow non-SSL connectivity. Lucky for you the ESP32 has a great TLS/SSL stack so you can have that all taken care of for you. Here's an example of a secure WiFi connection:

adafruit_products_image.png

Note we use WiFiSSLClient client; instead of WiFiClient client; to require an SSL connection!

adafruit_products_image.png

JSON Parsing Demo

This example is a little more advanced - many sites will have API's that give you JSON data. We'll use ArduinoJSON to convert that to a format we can use and then display that data on the serial port (which can then be re-directed to a display of some sort)

First up, use the Library manager to install ArduinoJSON.

Then load the example JSONdemo

adafruit_products_image.png

By default it will connect to to the Twitter banner image API, parse the username and followers and display them.

adafruit_products_image.png

Adapting Other Examples

Once you've got it connecting to the Internet you can check out the other examples. Most of the sketches included with the Adafruit fork of the WiFiNINA library above will have the pin mapping done for the ItsyBitsies. For other examples the only change you'll want to make is at the top of the sketches, add:

Download: file
#define SPIWIFI       SPI  // The SPI port
#define SPIWIFI_SS    13   // Chip select pin
#define ESP32_RESETN  12   // Reset pin
#define SPIWIFI_ACK   11   // a.k.a BUSY or READY pin
#define ESP32_GPIO0   -1

Note: These pin definitions leave the the ESP32's GPIO0 pin undefined (-1). If you wish to use this pin - solder the pad on the bottom of the Bitsy Add-on and set #define ESP32_GPIO0 to the correct pin for your microcontroller. For the ItsyBitsies it will be D10/10

And then before you check the status() of the module, call the function WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);

Download: file
    // check for the WiFi module:
  WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);
  while (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    delay(1000);
  }
  

CircuitPython

It's easy to use the Adafruit AirLift breakout with CircuitPython and the Adafruit CircuitPython ESP32SPI module.  This module allows you to easily add WiFi to your project.

You need plenty of RAM for CircuitPython/requests/json, so the ItsyBitsy M0 will not work! Please use an Itsy with at least 100KB of RAM, like the ItsyBitsy M4!

CircuitPython Microcontroller Pinout

Since all CircuitPython-running ItsyBitsies follow the same pinout, you do not need to change any of the pins listed below.

To use the ESP32's pins, copy the following lines into your code:

Download: file
esp32_cs = DigitalInOut(board.D13)
esp32_reset = DigitalInOut(board.D12)
esp32_ready = DigitalInOut(board.D11)

Then, include the following code to use the pin:

Download: file
esp32_gpio0 = DigitalInOut(board.D10)

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_esp32spi
  • adafruit_bus_device

Before continuing make sure your board's lib folder or root filesystem has the adafruit_esp32spi and adafruit_bus_device folders copied over.

Next make sure you are set up to connect to the serial console

CircuitPython Usage

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

Download: file
import board
import busio
from digitalio import DigitalInOut

from adafruit_esp32spi import adafruit_esp32spi

print("ESP32 SPI hardware test")

esp32_cs = DigitalInOut(board.D13)
esp32_reset = DigitalInOut(board.D12)
esp32_ready = DigitalInOut(board.D11)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
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!")

Connect to the serial console to see the output. It should look something like the following:

Make sure you see the same output! If you don't, check your wiring. Note that we've changed the pinout in the code example above to reflect the CircuitPython Microcontroller Pinout at the top of this page.

Once you've succeeded, continue onto the next page!

If you can read the Firmware and MAC address but fails on scanning SSIDs, check your power supply, you may be running out of juice to the ESP32 and it's resetting

Internet Connect!

Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. Note that access to enterprise level secured WiFi networks is not currently supported, only WiFi networks that require SSID and password.

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:

Download: file
# 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
    'github_token' : 'fawfj23rakjnfawiefa',
    'hackaday_token' : 'h4xx0rs3kret',
    }

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. See for example other tokens such as one for accessing github or the hackaday API. Other non-secret data like your timezone can also go here, just cause its 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. Lets use the ESP32SPI and the Requests libraries - you'll need to visit the CircuitPython bundle and install:

  • adafruit_bus_device
  • adafruit_esp32spi
  • adafruit_requests
  • neopixel

Into your lib folder. Once that's done, load up the following example using Mu or your favorite editor:

This first connection example doesn't use a secrets file - you'll hand-enter your SSID/password to verify connectivity first! See the detailed instructions after the code below.
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_requests as requests

print("ESP32 SPI webclient test")

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


# 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 ItsyBitsy Airlift:
# esp32_cs = DigitalInOut(board.D13)
# esp32_ready = DigitalInOut(board.D11)
# esp32_reset = DigitalInOut(board.D12)

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

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(b"MY_SSID_NAME", b"MY_SSID_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!")

And save it to your board, with the name code.py

As mentioned, this first connection example doesn't use a secrets file - you'll hand-enter your SSID/password to verify connectivity first!

Then go down to this line

esp.connect_AP(b'MY_SSID_NAME', b'MY_SSID_PASSWORD')

and change MY_SSID_NAME and MY_SSID_PASSWORD to your access point name and password, keeping them within the '' quotes. (This example doesn't use the secrets' file, but it's also very stand-alone so if other things seem to not work you can always re-load this to verify basic connectivity.) You should get something like the following:

In order, the example code...

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

Download: file
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

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

Tells our requests library the type of socket we're using (socket type varies by connectivity type - we'll be using the adafruit_esp32spi_socket for this example). We'll also set the interface to an esp object. This is a little bit of a hack, but it lets us use requests like CPython does.

Download: file
requests.set_socket(socket, esp)

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

Download: file
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:

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

Connects to the AP we've 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)

Download: file
    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 now we're getting to the really interesting part. With a SAMD51 or other large-RAM (well, over 32 KB) device, we can do a lot of neat tricks. Like for example we 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

Download: file
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 nRF52840, M4 and other high-RAM boards)

Download: file
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()

Requests

We've written a requests-like library for web interfacing named Adafruit_CircuitPython_Requests. This library allows you to send HTTP/1.1 requests without "crafting" them and provides helpful methods for parsing the response from the server.

# adafruit_requests usage with an esp32spi_socket
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_requests as requests

# 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(b"MY_SSID_NAME", b"MY_SSID_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)

# Initialize a requests object with a socket and esp32spi interface
requests.set_socket(socket, esp)

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_GET_URL = "http://httpbin.org/get"
JSON_POST_URL = "http://httpbin.org/post"

print("Fetching text from %s" % TEXT_URL)
response = requests.get(TEXT_URL)
print("-" * 40)

print("Text Response: ", response.text)
print("-" * 40)
response.close()

print("Fetching JSON data from %s" % JSON_GET_URL)
response = requests.get(JSON_GET_URL)
print("-" * 40)

print("JSON Response: ", response.json())
print("-" * 40)
response.close()

data = "31F"
print("POSTing data to {0}: {1}".format(JSON_POST_URL, data))
response = requests.post(JSON_POST_URL, data=data)
print("-" * 40)

json_resp = response.json()
# Parse out the 'data' key from json_resp dict.
print("Data received from server:", json_resp["data"])
print("-" * 40)
response.close()

json_data = {"Date": "July 25, 2019"}
print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data))
response = requests.post(JSON_POST_URL, json=json_data)
print("-" * 40)

json_resp = response.json()
# Parse out the 'json' key from json_resp dict.
print("JSON Data received from server:", json_resp["json"])
print("-" * 40)
response.close()

The code first sets up the ESP32SPI interface. Then, it initializes a request object using an ESP32 socket and the esp object.

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

# 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(b'MY_SSID_NAME', b'MY_SSID_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)

# Initialize a requests object with a socket and esp32spi interface
requests.set_socket(socket, esp)

HTTP GET with Requests

The code makes a HTTP GET request to Adafruit's WiFi testing website - http://wifitest.adafruit.com/testwifi/index.html.

To do this, we'll pass the URL into requests.get(). We're also going to save the response from the server into a variable named response.

While we requested data from the server, we'd what the server responded with. Since we already saved the server's response, we can read it back. Luckily for us, requests automatically decodes the server's response into human-readable text, you can read it back by calling response.text.

Lastly, we'll perform a bit of cleanup by calling response.close(). This closes, deletes, and collect's the response's data. 

Download: file
print("Fetching text from %s"%TEXT_URL)
response = requests.get(TEXT_URL)
print('-'*40)

print("Text Response: ", response.text)
print('-'*40)
response.close()

While some servers respond with text, some respond with json-formatted data consisting of attribute–value pairs.

CircuitPython_Requests can convert a JSON-formatted response from a server into a CPython dict. object.

We can also fetch and parse json data. We'll send a HTTP get to a url we know returns a json-formatted response (instead of text data). 

Then, the code calls response.json() to convert the response to a CPython dict

Download: file
print("Fetching JSON data from %s"%JSON_GET_URL)
response = requests.get(JSON_GET_URL)
print('-'*40)

print("JSON Response: ", response.json())
print('-'*40)
response.close()

HTTP POST with Requests

Requests can also POST data to a server by calling the requests.post method, passing it a data value.

Download: file
data = '31F'
print("POSTing data to {0}: {1}".format(JSON_POST_URL, data))
response = requests.post(JSON_POST_URL, data=data)
print('-'*40)

json_resp = response.json()
# Parse out the 'data' key from json_resp dict.
print("Data received from server:", json_resp['data'])
print('-'*40)
response.close()

You can also post json-formatted data to a server by passing json_data into the requests.post method.

Download: file
    json_data = {"Date" : "July 25, 2019"}
print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data))
response = requests.post(JSON_POST_URL, json=json_data)
print('-'*40)

json_resp = response.json()
# Parse out the 'json' key from json_resp dict.
print("JSON Data received from server:", json_resp['json'])
print('-'*40)
response.close()
  

Advanced Requests Usage

Want to send custom HTTP headers, parse the response as raw bytes, or handle a response's http status code in your CircuitPython code?

We've written an example to show advanced usage of the requests module below.

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

# 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(b"MY_SSID_NAME", b"MY_SSID_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)

# Initialize a requests object with a socket and esp32spi interface
requests.set_socket(socket, esp)

JSON_GET_URL = "http://httpbin.org/get"

# Define a custom header as a dict.
headers = {"user-agent": "blinka/1.0.0"}

print("Fetching JSON data from %s..." % JSON_GET_URL)
response = requests.get(JSON_GET_URL, headers=headers)
print("-" * 60)

json_data = response.json()
headers = json_data["headers"]
print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"]))
print("-" * 60)

# Read Response's HTTP status code
print("Response HTTP Status Code: ", response.status_code)
print("-" * 60)

# Read Response, as raw bytes instead of pretty text
print("Raw Response: ", response.content)

# Close, delete and collect the response data
response.close()

WiFi Manager

That simpletest example works but its a little finicky - you need to constantly check WiFi status and have many loops to manage connections and disconnections. For more advanced uses, we recommend using the WiFiManager object. It will wrap the connection/status/requests loop for you - reconnecting if WiFi drops, resetting the ESP32 if it gets into a bad state, etc.

Here's a more advanced example that shows the WiFi manager and also how to POST data with some extra headers:

import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager

print("ESP32 SPI webclient test")

# 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

# 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)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

counter = 0

while True:
    try:
        print("Posting data...", end="")
        data = counter
        feed = "test"
        payload = {"value": data}
        response = wifi.post(
            "https://io.adafruit.com/api/v2/"
            + secrets["aio_username"]
            + "/feeds/"
            + feed
            + "/data",
            json=payload,
            headers={"X-AIO-KEY": secrets["aio_key"]},
        )
        print(response.json())
        response.close()
        counter = counter + 1
        print("OK")
    except (ValueError, RuntimeError) as e:
        print("Failed to get data, retrying\n", e)
        wifi.reset()
        continue
    response = None
    time.sleep(15)

You'll note here we use a secrets.py file to manage our SSID info. The wifimanager is given the ESP32 object, secrets and a neopixel for status indication.

Note, you'll need to add a some additional information to your secrets file so that the code can query the Adafruit IO API:

  • aio_username
  • aio_key

You can go to your adafruit.io View AIO Key link to get those two values and add them to the secrets file, which will now look something like this:

Download: file
# 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' : '_your_ssid_',
    'password' : '_your_wifi_password_',
    'timezone' : "America/Los_Angeles", # http://worldtimeapi.org/timezones
    'aio_username' : '_your_aio_username_',
    'aio_key' : '_your_aio_key_',
    }

Next, set up an Adafruit IO feed named test

We can then have a simple loop for posting data to Adafruit IO without having to deal with connecting or initializing the hardware!

Take a look at your test feed on Adafruit.io and you'll see the value increase each time the CircuitPython board posts data to it!

Upgrade External ESP32 Airlift Firmware

Bridging the ESP32's Optional Control Pins

External AirLift boards have three optional ESP32 control pins which are not connected by default:

  • ESPGPIO0
  • ESPRX
  • ESPTX

Before continuing the steps on this page - you will need to add solder bridges on the ESPTX, EXPRX and GPIO0 pads on the bottom of breakout.

Make sure you solder all three of these pads together. You will not be able to upload firmware to your ESP32 if they are not connected.
wireless_adafruit_products_jumpers.jpg
AirLift BitsyWing with ESP32 Optional Control Pins highlighted.
This section is only for an AirLift FeatherWing with a Feather M4, or an AirLift BitsyWing with an ItsyBitsy M4. If you are using a different hardware combination - use the "Code - Arduino Passthrough" section instead.

Uploading Serial Passthrough Code for Feather M4 or ItsyBitsy M4

First, back up any code and files you have on your CIRCUITPY drive. It will be overwritten by the code you're going to upload to your board. You should not end up losing any files on the QSPI flash, but it's a good idea to back them up anyways. 

Download the UF2 for your board to your Desktop.

Find the reset button on your board. It's a small, black button, and on most of the boards, it will be the only button available. 

Tap this button twice to enter the bootloader. If it doesn't work on the first try, don't be discouraged. The rhythm of the taps needs to be correct and sometimes it takes a few tries. 

Once successful, the RGB LED on the board will flash red and then stay green. A new drive will show up on your computer. The drive will be called boardnameBOOT where boardname is a reference to your specific board. For example, a Feather will have FEATHERBOOT and a Trinket will have TRINKETBOOT etc. Going forward we'll just call the boot drive BOOT

The board is now in bootloader mode. Now find the UF2 file you downloaded. Drag that file to the BOOT drive on your computer in your operating system file manager/finder.

The lights should flash again, BOOT will disappear. Your board should re-enumerate USB and appear as a COM or Serial port on your computer. Make a note of the serial port by checking the Device Manager (Windows) or typing ls /dev/cu* or /dev/tty* (Mac or Linux) in a terminal.

If your board is listed in the terminal, proceed to the Uploading nina-fw with esptool section of this guide.

Code - Arduino Passthrough

With the ESP32's optional control pins soldered together, you'll be turning your Airlift breakout, shield, or wing into a USB to Serial converter. To do this, you'll need a special Arduino sketch named SerialESPPassthrough.ino.

Click Download: Project ZIP to download the code below.

/*
  SerialNINAPassthrough - Use esptool to flash the ESP32 module
  For use with PyPortal, Metro M4 WiFi...

  Copyright (c) 2018 Arduino SA. All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <Adafruit_NeoPixel.h>

unsigned long baud = 115200;

#if defined(ADAFRUIT_FEATHER_M4_EXPRESS) || \
  defined(ADAFRUIT_FEATHER_M0_EXPRESS) || \
  defined(ARDUINO_AVR_FEATHER32U4) || \
  defined(ARDUINO_NRF52840_FEATHER) || \
  defined(ADAFRUIT_ITSYBITSY_M0_EXPRESS) || \
  defined(ADAFRUIT_ITSYBITSY_M4_EXPRESS) || \
  defined(ARDUINO_AVR_ITSYBITSY32U4_3V)
  // Configure the pins used for the ESP32 connection
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI  // The SPI port
  #define SPIWIFI_SS    13   // Chip select pin
  #define ESP32_RESETN  12   // Reset pin
  #define SPIWIFI_ACK   11   // a.k.a BUSY or READY pin
  #define ESP32_GPIO0   10
  #define NEOPIXEL_PIN   8
#elif defined(ARDUINO_AVR_FEATHER328P)
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI  // The SPI port
  #define SPIWIFI_SS     4   // Chip select pin
  #define ESP32_RESETN   3   // Reset pin
  #define SPIWIFI_ACK    2   // a.k.a BUSY or READY pin
  #define ESP32_GPIO0   -1
  #define NEOPIXEL_PIN   8
#elif defined(TEENSYDUINO)
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI  // The SPI port
  #define SPIWIFI_SS     5   // Chip select pin
  #define ESP32_RESETN   6   // Reset pin
  #define SPIWIFI_ACK    9   // a.k.a BUSY or READY pin
  #define ESP32_GPIO0   -1
  #define NEOPIXEL_PIN   8
#elif defined(ARDUINO_NRF52832_FEATHER )
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI  // The SPI port
  #define SPIWIFI_SS    16  // Chip select pin
  #define ESP32_RESETN  15  // Reset pin
  #define SPIWIFI_ACK    7  // a.k.a BUSY or READY pin
  #define ESP32_GPIO0   -1
  #define NEOPIXEL_PIN   8
#elif !defined(SPIWIFI_SS)  // if the wifi definition isnt in the board variant
  // Don't change the names of these #define's! they match the variant ones
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI
  #define SPIWIFI_SS    10   // Chip select pin
  #define SPIWIFI_ACK    7   // a.k.a BUSY or READY pin
  #define ESP32_RESETN   5   // Reset pin
  #define ESP32_GPIO0   -1   // Not connected
  #define NEOPIXEL_PIN   8
#endif

Adafruit_NeoPixel pixel = Adafruit_NeoPixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(baud);
  pixel.begin();
  pixel.setPixelColor(0, 10, 10, 10); pixel.show();

  while (!Serial);
  pixel.setPixelColor(0, 50, 50, 50); pixel.show();

  delay(100);
  SerialESP32.begin(baud);

  pinMode(SPIWIFI_SS, OUTPUT);
  pinMode(ESP32_GPIO0, OUTPUT);
  pinMode(ESP32_RESETN, OUTPUT);
  
  // manually put the ESP32 in upload mode
  digitalWrite(ESP32_GPIO0, LOW);

  digitalWrite(ESP32_RESETN, LOW);
  delay(100);
  digitalWrite(ESP32_RESETN, HIGH);
  pixel.setPixelColor(0, 20, 20, 0); pixel.show();
  delay(100);
}

void loop() {
  while (Serial.available()) {
    pixel.setPixelColor(0, 10, 0, 0); pixel.show();
    SerialESP32.write(Serial.read());
  }

  while (SerialESP32.available()) {
    pixel.setPixelColor(0, 0, 0, 10); pixel.show();
    Serial.write(SerialESP32.read());
  }
}

Code Usage

Unzip the file, and open the SerialESPPassthrough.ino file in the Arduino IDE.

If you're using the AirLift FeatherWing, AirLift Shield or AirLift Bitsy Add-On, use the PassThrough UF2 instructions above

If you have an AirLift Breakout (or are manually wiring up any of the boards above), change the following pin definitions in the sketch to match your wiring:

Download: file
#elif !defined(SPIWIFI_SS)  // if the wifi definition isnt in the board variant
  // Don't change the names of these #define's! they match the variant ones
  #define SerialESP32   Serial1
  #define SPIWIFI       SPI
  #define SPIWIFI_SS    10   // Chip select pin
  #define SPIWIFI_ACK    7   // a.k.a BUSY or READY pin
  #define ESP32_RESETN   5   // Reset pin
  #define ESP32_GPIO0   -1   // Not connected
  #define NEOPIXEL_PIN   8
#endif

Using the Arduino IDE, upload the code to your board (Sketch->Upload).

After uploading, the board should enumerate USB and appear as a COM or Serial port on your computer.

Make a note of the serial port by checking the Device Manager (Windows) or typing in ls /dev/cu* or /dev/tty* (Mac or Linux) in a terminal

Burning nina-fw with esptool

Click the link below to download the latest nina-fw .bin file. Unzip it and save the .bin file to your desktop.

This section assumes you know how to use 'esptool' to upload firmware to your ESP! If you're not sure, check https://github.com/espressif/esptool and look for tutorials.

If you're using macOS or Linux - run the following command, replacing /dev/ttys6 with the serial port of your board and NINA_W102-1.6.0 with the binary file you're flashing to the ESP32.

esptool.py --port /dev/ttyS6 --before no_reset --baud 115200 write_flash 0 NINA_W102-1.6.0.bin

If you're using Windows - run the following command, replacing COM7 with the serial port of your board  and NINA_W102-1.6.0 with the binary file you're flashing to the ESP32

esptool.py --port COM7 --before no_reset --baud 115200 write_flash 0 NINA_W102-1.6.0.bin

The command should detect the ESP32 and will take a minute or two to upload the firmware. 

If ESPTool doesn't detect the ESP32, make sure you've uploaded the correct .UF2 file to the bootloader and are using the correct serial port.

Once the firmware is fully uploaded, the ESP32 will reset.

Verifying the Upgraded Firmware Version

To verify everything is working correctly, we'll load up either an Arduino sketch or CircuitPython code. At this point, you may also want desolder the connections between the Optional ESP32 control pins you made earlier using a solder sucker or a bit of solder wick.

Arduino

If you were previously using your ESP32 with Arduino, you should load up an Arduino sketch to verify everything is working properly and the version of the nina-fw correlates with the version the sketch reads.

Open up File->Examples->WiFiNINA->ScanNetworks and upload the sketch. Then, open the Serial Monitor. You should see the firmware version printed out to the serial monitor.

CircuitPython

If you were previously using your ESP32 project with CircuitPython, you'll need to first reinstall CircuitPython firmware (UF2) for your board. The QSPI flash should have retained its contents. If you don't see anything on the CIRCUITPY volume, copy files from the backup you made earlier to CIRCUITPY.

To verify the new ESP32 WiFi firmware version is correct, follow the Connect to WiFi step in this guide and come back here when you've successfully ran the code. The REPL output should display the firmware version you flashed.

Downloads

This guide was first published on Sep 04, 2019. It was last updated on Sep 04, 2019.