One of the great things about the ESP32 is the built-in WiFi capabilities. This page covers the basics of getting connected using CircuitPython.
The first thing you need to do is update your code.py to the following. Click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, and copy the entire lib folder and the code.py file to your CIRCUITPY drive.
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries # # SPDX-License-Identifier: MIT import os import ipaddress import ssl import wifi import socketpool import adafruit_requests # URLs to fetch from TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_QUOTES_URL = "https://www.adafruit.com/api/quotes.php" JSON_STARS_URL = "https://api.github.com/repos/adafruit/circuitpython" print("ESP32-S2 WebClient Test") print(f"My MAC address: {[hex(i) for i in wifi.radio.mac_address]}") print("Available WiFi networks:") for network in wifi.radio.start_scanning_networks(): print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"), network.rssi, network.channel)) wifi.radio.stop_scanning_networks() 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')}") print(f"My IP address: {wifi.radio.ipv4_address}") ping_ip = ipaddress.IPv4Address("8.8.8.8") ping = wifi.radio.ping(ip=ping_ip) # retry once if timed out if ping is None: ping = wifi.radio.ping(ip=ping_ip) if ping is None: print("Couldn't ping 'google.com' successfully") else: # convert s to ms print(f"Pinging 'google.com' took: {ping * 1000} ms") pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) print(f"Fetching text from {TEXT_URL}") response = requests.get(TEXT_URL) print("-" * 40) print(response.text) print("-" * 40) print(f"Fetching json from {JSON_QUOTES_URL}") response = requests.get(JSON_QUOTES_URL) print("-" * 40) print(response.json()) print("-" * 40) print() print(f"Fetching and parsing json from {JSON_STARS_URL}") response = requests.get(JSON_STARS_URL) print("-" * 40) print(f"CircuitPython GitHub Stars: {response.json()['stargazers_count']}") print("-" * 40) print("Done")
Your CIRCUITPY drive should resemble the following.
To get connected, the next thing you need to do is update the settings.toml file.
The settings.toml 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 settings.toml file, that is on your CIRCUITPY drive, to hold secret/private/custom data. That way you can share your main project without worrying about accidentally sharing private stuff.
If you have a fresh install of CircuitPython on your board, the initial settings.toml file on your CIRCUITPY drive is empty.
To get started, you can update the settings.toml on your CIRCUITPY drive to contain the following code.
# SPDX-FileCopyrightText: 2023 Adafruit Industries # # SPDX-License-Identifier: MIT # This is where you store the credentials necessary for your code. # The associated demo only requires WiFi, but you can include any # credentials here, such as Adafruit IO username and key, etc. CIRCUITPY_WIFI_SSID = "your-wifi-ssid" CIRCUITPY_WIFI_PASSWORD = "your-wifi-password"
This file should contain a series of Python variables, each assigned to a string. Each variable should describe what it represents (say wifi_ssid
), followed by an = (equals sign), followed by the data in the form of a Python string (such as "my-wifi-password"
including the quote marks).
At a minimum you'll need to add/update your WiFi SSID and WiFi password, so do that now!
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.
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 settings.toml - keep that out of GitHub, Discord or other project-sharing sites.
If you connect to the serial console, you should see something like the following:
In order, the example code...
Checks the ESP32's MAC address.
print(f"My MAC address: {[hex(i) for i in wifi.radio.mac_address]}")
Performs a scan of all access points and prints out the access point's name (SSID), signal strength (RSSI), and channel.
print("Available WiFi networks:") for network in wifi.radio.start_scanning_networks(): print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"), network.rssi, network.channel)) wifi.radio.stop_scanning_networks()
Connects to the access point you defined in the settings.toml file, and prints out its local IP address.
print(f"Connecting to {os.getenv('WIFI_SSID')}") wifi.radio.connect(os.getenv("WIFI_SSID"), os.getenv("WIFI_PASSWORD")) print(f"Connected to {os.getenv('WIFI_SSID')}") print(f"My IP address: {wifi.radio.ipv4_address}")
Attempts to ping a Google DNS server to test connectivity. If a ping fails, it returns None
. Initial pings can sometimes fail for various reasons. So, if the initial ping is successful (is not None
), it will print the echo speed in ms. If the initial ping fails, it will try one more time to ping, and then print the returned value. If the second ping fails, it will result in "Ping google.com: None ms"
being printed to the serial console. Failure to ping does not always indicate a lack of connectivity, so the code will continue to run.
ping_ip = ipaddress.IPv4Address("8.8.8.8") ping = wifi.radio.ping(ip=ping_ip) * 1000 if ping is not None: print(f"Ping google.com: {ping} ms") else: ping = wifi.radio.ping(ip=ping_ip) print(f"Ping google.com: {ping} ms")
The code creates a socketpool using the wifi radio's available sockets. This is performed so we don't need to re-use sockets. Then, it initializes a a new instance of the requests interface - which makes getting data from the internet really really easy.
pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context())
To read in plain-text from a web URL, call requests.get
- you may pass in either a http, or a https url for SSL connectivity.
print(f"Fetching text from {TEXT_URL}") response = requests.get(TEXT_URL) print("-" * 40) print(response.text) print("-" * 40)
Requests can also display a JSON-formatted response from a web URL using a call to requests.get
.
print(f"Fetching json from {JSON_QUOTES_URL}") response = requests.get(JSON_QUOTES_URL) print("-" * 40) print(response.json()) print("-" * 40)
Finally, you can fetch and parse a JSON URL using requests.get
. This code snippet obtains the stargazers_count
field from a call to the GitHub API.
print(f"Fetching and parsing json from {JSON_STARS_URL}") response = requests.get(JSON_STARS_URL) print("-" * 40) print(f"CircuitPython GitHub Stars: {response.json()['stargazers_count']}") print("-" * 40)
OK you now have your ESP32 board set up with a proper settings.toml file and can connect over the Internet. If not, check that your settings.toml file has the right SSID and password and retrace your steps until you get the Internet connectivity working!
IPv6 Networking
Starting in CircuitPython 9.2, IPv6 networking is available on most Espressif wifi boards. Socket-using libraries like adafruit_requests and adafruit_ntp will need to be updated to use the new APIs and for now can only connect to services on IPv4.
IPv6 connectivity & privacy
IPv6 addresses are divided into many special kinds, and many of those kinds (like those starting with FC, FD, FE) are private or local; Addresses starting with other prefixes like 2002: and 2001: are globally routable. In 2024, far from all ISPs and home networks support IPv6 internet connectivity. For more info consult resources like Wikipedia. If you're interested in global IPv6 connectivity you can use services like Hurricane Electric to create an "IPv6 tunnel" (free as of 2024, but requires expertise and a compatible router or host computer to set up)
It's also important to be aware that, as currently implemented by Espressif, there are privacy concerns especially when these devices operate on the global IPv6 network: The device's unique identifier (its EUI-64 or MAC address) is used by default as part of its IPv6 address. This means that the device identity can be tracked across multiple networks by any service it connects to.
Enable IPv6 networking
Due to the privacy consideration, IPv6 networking is not automatically enabled. Instead, it must be explicitly enabled by a call to start_dhcp_client
with the ipv6=True
argument specified:
wifi.start_dhcp_client(ipv6=True)
Check IP addresses
The read-only addresses
property of the wifi.radio
object holds all addresses, including IPv4 and IPv6 addresses:
>>> wifi.radio.addresses ('FE80::7EDF:A1FF:FE00:518C', 'FD5F:3F5C:FE50:0:7EDF:A1FF:FE00:518C', '10.0.3.96')
The wifi.radio.dns
servers can be IPv4 or IPv6:
>>> wifi.radio.dns ('FD5F:3F5C:FE50::1',) >>> wifi.radio.dns = ("1.1.1.1",) >>> wifi.radio.dns ('1.1.1.1',)
Ping v6 networks
wifi.radio.ping
accepts v6 addresses and names:
>>> wifi.radio.ping("google.com") 0.043 >>> wifi.radio.ping("ipv6.google.com") 0.048
Create & use IPv6 sockets
Use the address family socket.AF_INET6
. After the socket is created, use methods like connect
, send
, recfrom_into
, etc just like for IPv4 sockets. This code snippet shows communicating with a private-network NTP server; this IPv6 address will not work on your network:
>>> ntp_addr = ("fd5f:3f5c:fe50::20e", 123) >>> PACKET_SIZE = 48 >>> >>> buf = bytearray(PACKET_SIZE) >>> with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s: ... s.settimeout(1) ... buf[0] = 0b0010_0011 ... s.sendto(buf, ntp_addr) ... print(s.recvfrom_into(buf)) ... print(buf) ... 48 (48, ('fd5f:3f5c:fe50::20e', 123)) bytearray(b'$\x01\x03\xeb\x00\x00\x00\x00\x00\x00\x00GGPS\x00\xeaA0h\x07s;\xc0\x00\x00\x00\x00\x00\x00\x00\x00\xeaA0n\xeb4\x82-\xeaA0n\xebAU\xb1')
Text editor powered by tinymce.