Overview
Where's my friend? Build this simple IoT project to display a friend's or loved one's location on an Adafruit PyPortal.
Using Adafruit's ItsaSnap iOS app and Apple Shortcuts, you can send a pre-selected GPS location from an iPhone to a shared (private) Adafruit IO feed. The PyPortal connects to the internet, fetches a location value of a shared feed, and displays the location on the screen along with a corresponding icon and text.
About this project, your privacy, and Adafruit IO
Adafruit IO's data feeds are private (not viewable by others) by default and secure (we will never sell or give this data away to another company).
This project uses the ItsaSnap iOS App to bridge Adafruit IO and Apple Shortcuts. Apple Shortcuts utilizes your iPhone's GPS (with permission) to write a string of text (i.e.: "park" or "work") to identify where a friend is. It does not send specific GPS location data to the Adafruit IO servers.
The data sent by the Shortcuts app is stored on a private Adafruit IO data feed, only viewable by you and the friend sharing their location with you.
ItsaSnap for iOS
It's a Snap (ItsaSnap) is a simple iOS app designed for exploring Adafruit IO. It allows you to send data to your Adafruit IO feeds. You can also link it to control or monitor your internet-connected projects. This lets you control and check Adafruit devices from your phone, anywhere.
It also allows us to use Apple's Shortcuts app to create a code-free automation from your iOS device.
Parts
The following products and parts are required to complete this project:
Note: The code in this guide is designed for the original Adafruit PyPortal. If you have the PyPortal Pynt or PyPortal Titano, you can still complete this project, but you will need to adjust some of the code to accommodate the Titano/Pynt's different display sizes.
The following parts are not required to complete this project but are nice to have.
Page last edited August 07, 2024
Text editor powered by tinymce.
Get Started with Adafruit IO
Adafruit IO is integrated with your adafruit.com account so you don't need to create yet another online account! You need an Adafruit account to use Adafruit IO because we want to make sure the data you upload is available to only you (unless you decide to publish your data).
I have an Adafruit.com Account already
If you already have an Adafruit account, then you already have access to Adafruit IO. It doesn't matter how you signed up, your account will make all three available.
To access Adafruit IO, simply visit https://io.adafruit.com to start streaming, logging, and interacting with your data.
Create an Adafruit Account (for Adafruit IO)
An Adafruit account makes Adafruit content and services available to you in one place. Your account provides access to the Adafruit shop, the Adafruit Learning System, and Adafruit IO. This means only one account, one username, and one password are necessary to engage with the content and services that Adafruit offers.
If you do not have an Adafruit account, signing up for a new Adafruit account only takes a couple of steps.
Begin by visiting https://accounts.adafruit.com.
Click the Sign Up button under the "Need An Adafruit Account?" title, below the Sign In section.
This will take you to the Sign Up page.
Fill in the requested information, and click the Create Account button.
This takes you to your Adafruit Account home page. From here, you can access all the features of your account.
You can also access the Adafruit content and services right from this page. Along the top of the page, you'll see a series of links beginning with "Shop". To access any of these, simply click the link.
For example, to begin working with Adafruit IO, click the IO link to the right of the rest of the links. This is the same for the other links as well.
That's all there is to creating a new Adafruit account, and navigating to Adafruit IO.
Page last edited August 07, 2024
Text editor powered by tinymce.
Create a new Shared Adafruit IO Feed
Adafruit IO's feed-sharing feature allows you to invite someone to view (or write to) a specific feed. This is a great feature if you're building a project but don't want to make your feed public to strangers, only your friends. In this project, the person sharing their location will create a shared feed and invite the person with the PyPortal to view it.
In a web browser, navigate to io.adafruit.com/feeds and click "+ New Feed"
Give your new feed a name. The description is optional. Click Create to create the new feed.
Under My Feeds, you should see the location feed you created. Click the link to be brought to the Feed's page.
On the feed page, click the Sharing text on the sidebar
You'll be greeted with the Sharing Settings window.
You can invite somebody you know using their Adafruit IO Username or email address. You'll have two options for controlling someone's access to your feed:
- If you give someone Read access, they can view the feed you share.
- If you give someone Read + Write access, they can read from your feed and write to your feed.
For this project, the PyPortal only needs to display the feed's last value.
Enter your friend's email or Adafruit IO username, give them the Read Access Level, and click Send Invitation.
Accepting a Shared Feed Request
Now it's the other person's turn. After you click Send Invitation, they should receive an email from Adafruit IO informing them that you've shared a feed. After they've reviewed and accepted it, move on to the next page.
Page last edited August 07, 2024
Text editor powered by tinymce.
Download the ItsaSnap by Adafruit App
This section will guide you through downloading and installing the ItsaSnap by Adafruit app.
Download and Install ItsaSnap
To install and download ItsaSnap for your iOS device,
- Open the App Store.
- Tap on the "Search" icon at the bottom of the screen.
- Type "ItsaSnap by Adafruit" or "ItsaSnap" in the search bar and tap "Search".
- Locate the ItsaSnap app in the search results and tap on it
5. Tap the "Get" button to download and install the app on your device.
You may be prompted to enter your Apple ID password or use Face ID/Touch ID to confirm the installation
After you have downloaded and installed ItsaSnap, open the app.
You will need to enter your Adafruit username and Adafruit IO Key (which is different from your Adafruit account password).
Your Adafruit IO key is a long string of letters and numbers. To make it easier to add to the app, we've also included a QR code scanner that allows you to easily copy the Adafruit IO Key from the Adafruit IO website to the app.
On the ItaSnap app, the QR code scanner is located below the Adafruit IO Key and above the login button. To access it, press the 'Scan QR code for IO Key' button. This will open the QR code scanner.
To find the QR code for your Adafruit IO account, navigate go to the overview page. Once there, click the yellow button with a key in the center (it's next to the "New Device" button) to reveal your Adafruit IO Key.
After clicking it, a window with your information will appear. Then, find the QR code and scan it, and log in.
Page last edited August 07, 2024
Text editor powered by tinymce.
Set Up Apple Shortcuts
ItsASnap includes several actions for the Apple Shortcuts App, allowing you to integrate Adafruit IO functionality. This project focuses on sending specific location data (only sending data when you're at a fixed location, like a park or the train station) from your iPhone to a shared Adafruit IO feed.
This step will demonstrate how to use ItsASnap with Apple Shortcuts to send a value to an Adafruit IO feed when you arrive at a location - in this instance, we'll choose a park.
Create a new Shortcuts Automation
Launch Apple Shortcuts. To create a new automation, go to the automation screen and tap the "+" button.
Here you can select which types of triggers can initiate an action. For now, we'll use "Arrive" to set a location where the action will be triggered.
Select "Arrive" as the trigger for the automation. This trigger requires a specific location to initiate the automation process.
We'll want to track when we're at work. Add the work location (I work for Adafruit so I added the Adafruit HQ) into the location field; it will then be displayed on the map.
Additionally, you can configure the radius of the location to specify where you want the trigger to activate.
Once you're all set - tap Done.
Configure your When action is configured to execute at any time and run immediately. Tap Next.
On the next step, tap New Blank Automation to configure ItsaSnap.
Within the Blank Automation tab, use the search bar to search for ItsaSnap. Then, select the Send Value shortcut.
Once done, you should be able to view the automation.
Add more Shortcuts for Different Locations
While adding one automation for the park is useful - there are other places your friend goes during his day like work, home, and commuting via the NYC Subway.
You can repeat the process above to add more shortcuts for different locations. For each location shortcut you configure, ensure the feed being written to is the location feed.
For this guide, we created the following four automation using the following settings:
- The value home to the location feed when we've arrived home.
- The value work to the location feed when we've arrived at work
- The value commute to the location feed when we've left work.
- The value gym to the location feed when we've arrived at the park to workout.
Page last edited August 07, 2024
Text editor powered by tinymce.
PyPortal CircuitPython Setup
To use all the amazing features of your PyPortal with CircuitPython, you must first install a number of libraries. This page covers that process.
Adafruit CircuitPython Bundle
Download the Adafruit CircuitPython Library Bundle. You can find the latest release here:
Download the adafruit-circuitpython-bundle-*.x-mpy-*.zip bundle zip file where *.x MATCHES THE VERSION OF CIRCUITPYTHON YOU INSTALLED, and unzip a folder of the same name. Inside you'll find a lib folder. You have two options:
- You can add the lib folder to your CIRCUITPY drive. This will ensure you have all the drivers. But it will take a bunch of space on the 8 MB disk
- Add each library as you need it, this will reduce the space usage but you'll need to put in a little more effort.
At a minimum we recommend the following libraries, in fact we more than recommend. They're basically required. So grab them and install them into CIRCUITPY/lib now!
- adafruit_esp32spi - This is the library that gives you internet access via the ESP32 using (you guessed it!) SPI transport. You need this for anything Internet
- adafruit_requests - This library allows us to perform HTTP requests and get responses back from servers. GET/POST/PUT/PATCH - they're all in here!
- adafruit_connection_manager - used by adafruit_requests.
- adafruit_pyportal - This is our friendly wrapper library that does a lot of our projects, displays graphics and text, fetches data from the internet. Nearly all of our projects depend on it!
- adafruit_portalbase - This library is the base library that adafruit_pyportal library is built on top of.
- adafruit_touchscreen - a library for reading touches from the resistive touchscreen. Handles all the analog noodling, rotation and calibration for you.
- adafruit_io - this library helps connect the PyPortal to our free datalogging and viewing service
- adafruit_imageload - an image display helper, required for any graphics!
- adafruit_display_text - not surprisingly, it displays text on the screen
- adafruit_bitmap_font - we have fancy font support, and its easy to make new fonts. This library reads and parses font files.
- adafruit_slideshow - for making image slideshows - handy for quick display of graphics and sound
- neopixel - for controlling the onboard neopixel
- adafruit_adt7410 - library to read the temperature from the on-board Analog Devices ADT7410 precision temperature sensor (not necessary for Titano or Pynt)
- adafruit_bus_device - low level support for I2C/SPI
- adafruit_fakerequests - This library allows you to create fake HTTP requests by using local files.
Page last edited August 07, 2024
Text editor powered by tinymce.
Create Your settings.toml File
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.
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_ssidyour_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_ssidyour_wifi_passwordyour_aio_usernameyour_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.
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 (decimal numbers), octal (
0o567) and binary (0b11011) are not supported.
- Floats (decimal numbers), octal (
- Use
\uescapes for weird characters,\xand\oooescapes are not available in .toml files- Example:
\U0001f44dfor 👍 (thumbs up emoji) and\u20acfor € (EUR sign)
- Example:
- 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.
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.
Page last edited August 07, 2024
Text editor powered by tinymce.
Internet Connect!
Connect to WiFi
OK, now that you have your settings.toml file set up - you can connect to the Internet.
To do this, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, 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, open the directory examples/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
Your CIRCUITPY drive should now look similar to the following image:
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
from os import getenv
import adafruit_connection_manager
import adafruit_requests
import board
import busio
from digitalio import DigitalInOut
# Use this import for adafruit_esp32spi version 11.0.0 and up.
# Note that frozen libraries may not be up to date.
# import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi
# Get wifi details and more from a settings.toml file
# tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD
ssid = getenv("CIRCUITPY_WIFI_SSID")
password = getenv("CIRCUITPY_WIFI_PASSWORD")
print("ESP32 SPI webclient test")
TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_URL = "http://wifitest.adafruit.com/testwifi/sample.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 AirLift Shield:
# esp32_cs = DigitalInOut(board.D10)
# esp32_ready = DigitalInOut(board.D7)
# esp32_reset = DigitalInOut(board.D5)
# If you have an AirLift Featherwing or ItsyBitsy Airlift:
# esp32_cs = DigitalInOut(board.D13)
# esp32_ready = DigitalInOut(board.D11)
# esp32_reset = DigitalInOut(board.D12)
# If you have an externally connected ESP32:
# NOTE: You may need to change the pins to reflect your wiring
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", ":".join(f"{byte:02X}" for byte in esp.MAC_address))
for ap in esp.scan_networks():
print(f"\t{ap.ssid:<23} RSSI: {ap.rssi}")
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(ssid, password)
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi)
print("My IP address is", esp.ipv4_address)
print(f"IP lookup adafruit.com: {esp.pretty_ip(esp.get_host_by_name('adafruit.com'))}")
print(f"Ping google.com: {esp.ping('google.com')} ms")
# 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.
Don't forget you'll also need to create the settings.toml file as seen above, with your WiFi ssid and password.
In a serial console, you should see something like the following. For more information about connecting with a serial console, view the guide Connecting to the Serial Console.
>>> import wifitest
ESP32 SPI webclient test
ESP32 found and in idle mode
Firmware vers. 1.7.5
MAC addr: 24:C9:DC:BD:0F:3F
HomeNetwork RSSI: -46
HomeNetwork RSSI: -76
Fios-12345 RSSI: -92
FiOS-AB123 RSSI: -92
NETGEAR53 RSSI: -93
Connecting to AP...
Connected to HomeNetwork RSSI: -45
My IP address is 192.168.1.245
IP lookup adafruit.com: 104.20.39.240
Ping google.com: 30 ms
Fetching text from http://wifitest.adafruit.com/testwifi/index.html
----------------------------------------
This is a test of Adafruit WiFi!
If you can read this, its working :)
----------------------------------------
Fetching json from http://wifitest.adafruit.com/testwifi/sample.json
----------------------------------------
{'fun': True, 'company': 'Adafruit', 'founded': 2005, 'primes': [2, 3, 5], 'pi': 3.14, 'mixed': [False, None, 3, True, 2.7, 'cheese']}
----------------------------------------
Done!
Going over the example above, here's a breakdown of what the program is doing:
- Initialize the ESP32 over SPI using the SPI port and 3 control pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
#...
else:
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
- Get the socket pool and the SSL context, and then tell the
adafruit_requestslibrary about them.
pool = adafruit_connection_manager.get_radio_socketpool(esp) ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) requests = adafruit_requests.Session(pool, ssl_context)
- Verify 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:", ":".join("%02X" % byte for byte in esp.MAC_address))
- Perform a scan of all access points it can see and print out the name and signal strength.
for ap in esp.scan_networks():
print("\t%-23s RSSI: %d" % (ap.ssid, ap.rssi))
- Connect to the AP we've defined here, then print out the local IP address. Then attempt 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...")
while not esp.is_connected:
try:
esp.connect_AP(ssid, password)
except OSError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi)
print("My IP address is", esp.ipv4_address)
print(
"IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))
)
Now we're getting to the really interesting part of the example program. We've written a library for web fetching web data, named adafruit_requests. It is a lot like the regular Python library named requests. This library allows you to send HTTP and HTTPS requests easily and provides helpful methods for parsing the response from the server.
- Here is the part of the example program is fetching text data from a URL.
TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" # Further up in the program
# ...
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print('-' * 40)
print(r.text)
print('-' * 40)
r.close()
- Finally, here the program is fetching some JSON data. The
adafruit_requestslibrary will parse the JSON into a Python dictionary whose structure is the same as the structure of the JSON.
JSON_URL = "http://wifitest.adafruit.com/testwifi/sample.json" # Further up in the program
# ...
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print('-' * 40)
print(r.json())
print('-' * 40)
r.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.
To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, 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, open the directory examples/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
import os
import adafruit_connection_manager
import board
import busio
from adafruit_esp32spi import adafruit_esp32spi
from digitalio import DigitalInOut
import adafruit_requests
# Get WiFi details, ensure these are setup in settings.toml
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
password = os.getenv("CIRCUITPY_WIFI_PASSWORD")
# 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)
# If you have an AirLift Featherwing or ItsyBitsy Airlift:
# esp32_cs = DigitalInOut(board.D13)
# esp32_ready = DigitalInOut(board.D11)
# esp32_reset = DigitalInOut(board.D12)
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
radio = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
print("Connecting to AP...")
while not radio.is_connected:
try:
radio.connect_AP(ssid, password)
except RuntimeError as e:
print("could not connect to AP, retrying: ", e)
continue
print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi)
# Initialize a requests session
pool = adafruit_connection_manager.get_radio_socketpool(radio)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio)
requests = adafruit_requests.Session(pool, ssl_context)
JSON_GET_URL = "https://httpbin.org/get"
# Define a custom header as a dict.
headers = {"user-agent": "blinka/1.0.0"}
print(f"Fetching JSON data from {JSON_GET_URL}...")
with requests.get(JSON_GET_URL, headers=headers) as response:
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)
Your CIRCUITPY drive should now look similar to the following image:
WiFi Manager
The way the examples above connect to WiFi works but it's a little finicky. Since WiFi is not necessarily so reliable, you may have disconnects and need to reconnect. For more advanced uses, we recommend using the WiFiManager class. 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 using the WiFiManager and also how to fetch the current time from a web source.
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
import time
from os import getenv
import board
import busio
import neopixel
import rtc
from digitalio import DigitalInOut
# Use these imports for adafruit_esp32spi version 11.0.0 and up.
# Note that frozen libraries may not be up to date.
# import adafruit_esp32spi
# from adafruit_esp32spi.wifimanager import WiFiManager
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi.adafruit_esp32spi_wifimanager import WiFiManager
# Get wifi details and more from a settings.toml file
# tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD
ssid = getenv("CIRCUITPY_WIFI_SSID")
password = getenv("CIRCUITPY_WIFI_PASSWORD")
print("ESP32 local time")
TIME_API = "https://time.now/developer/api/ip"
# 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)
# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
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_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
"""Uncomment below for ItsyBitsy M4"""
# status_pixel = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
"""Uncomment below for an externally defined RGB LED (including Arduino Nano Connect)"""
# 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_pixel = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)
wifi = WiFiManager(esp, ssid, password, status_pixel=status_pixel)
the_rtc = rtc.RTC()
response = None
while True:
try:
print("Fetching json from", TIME_API)
response = wifi.get(TIME_API)
break
except OSError as e:
print("Failed to get data, retrying\n", e)
continue
json = response.json()
current_time = json["datetime"]
the_date, the_time = current_time.split("T")
year, month, mday = (int(x) for x in the_date.split("-"))
the_time = the_time.split(".")[0]
hours, minutes, seconds = (int(x) for x in the_time.split(":"))
# We can also fill in these extra nice things
year_day = json["day_of_year"]
week_day = json["day_of_week"]
is_dst = json["dst"]
now = time.struct_time((year, month, mday, hours, minutes, seconds, week_day, year_day, is_dst))
print(now)
the_rtc.datetime = now
while True:
print(time.localtime())
time.sleep(1)
Further Information
For more information on the basics of doing networking in CircuitPython, see this guide:
Page last edited August 07, 2024
Text editor powered by tinymce.
Code with CircuitPython
Once you've finished setting up your PyPortal with CircuitPython and have connected to the internet, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2024 Brent Rubell, written for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import board
import displayio
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_pyportal import PyPortal
# Adafruit IO shared feed key
IO_FEED_KEY = 'location'
# Fetch the location every 5 minutes
SLEEP_DELAY_MINUTES = 5
# Set the backlight brightness, 0.0 (off) to 1.0 (max brightness)
BACKLIGHT_BRIGHTNESS = 0.5
# Location text and images
LOCATION_IMAGES = { 'home': 'images/home.bmp', 'work': 'images/office.bmp',
'gym': 'images/workout.bmp', 'commute': 'images/subway.bmp' }
# Create the PyPortal object
pyportal = PyPortal(status_neopixel=board.NEOPIXEL)
# Configure the PyPortal's display
display = board.DISPLAY
display.rotation = 0
display.brightness = BACKLIGHT_BRIGHTNESS
# Display label and image coordinates
TEXT_AREA_X = display.width // 6
TEXT_AREA_Y = 20
TEXT_AREA_LOCATION_X = display.width // 3
TEXT_AREA_LOCATION_Y = display.height - 20
IMAGE_SPRITE_X = (display.width // 3) - 10
IMAGE_SPRITE_Y = display.height // 5
# Create a displayIO Group
group = displayio.Group()
# Draw the background
bg_group = displayio.Group()
rect = Rect(0, 0, display.width, display.height, fill=0xFFFFFF)
bg_group.append(rect)
group.append(bg_group)
# Use the default font
font = terminalio.FONT
# Draw a label for the header text
text_area = label.Label(font, text="Where's My Friend?", color=0x000000, scale=2)
text_area.x = TEXT_AREA_X
text_area.y = TEXT_AREA_Y
group.append(text_area)
# Draw a label for the location text
text_area_location = label.Label(font, text="", color=0x000000, scale=3)
text_area_location.x = TEXT_AREA_LOCATION_X
text_area_location.y = TEXT_AREA_LOCATION_Y
group.append(text_area_location)
# Create a group for the icon only
icon_group = displayio.Group()
group.append(icon_group)
# Show the group
display.root_group = group
def set_image(image_group, filename):
"""Sets the image file for a given group for display."""
print(f"Set image to {filename}")
if image_group:
image_group.pop()
image = displayio.OnDiskBitmap(filename)
image_sprite = displayio.TileGrid(image,
pixel_shader=image.pixel_shader)
image_sprite.x = IMAGE_SPRITE_X
image_sprite.y = IMAGE_SPRITE_Y
image_group.append(image_sprite)
prv_location = None
while True:
try:
print("Fetching location data...")
# Fetch the location data from Adafruit IO
feed = pyportal.get_io_feed(IO_FEED_KEY)
# If the location value is in the list of images
if feed['last_value'] in LOCATION_IMAGES:
# Check if the location has changed from the last time
# we fetched the location
if prv_location == feed['last_value']:
print("Location has not changed!")
else: # Location has changed
print(f"Location: {feed['last_value']}")
# Load the image for the current location
set_image(icon_group, LOCATION_IMAGES[feed['last_value']])
# Update the location text
text_area_location.text=f"@ {feed['last_value']}"
# Show the refreshed group
display.root_group = group
# Update the previous location
prv_location = feed['last_value']
else:
print("Location not found in images!")
# Update the location text
text_area_location.text="@ unknown"
# Show the refreshed group
display.root_group = group
except RuntimeError as e:
print("Failed to fetch location data: ", e)
# Wait 5 minutes (300 seconds) before fetching the location feed again
print("Sleeping, fetching the location again in 5 minutes!")
time.sleep(SLEEP_DELAY_MINUTES * 60)
Upload the Code and Libraries to the PyPortal
After downloading the Project Bundle, plug your PyPortal into the computer's USB port with a known good USB data + power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the CIRCUITPY drive.
- lib folder
- images folder
- code.py
The CIRCUITPY drive should look like this after copying the lib folder, images folder, and the code.py file.
How the CircuitPython Code Works
At the top of the code, we import libraries used for this project and declare variables which you can use to customize your project:
IO_FEED_KEYis the shared feed's name.-
SLEEP_DELAY_SECONDSis how often the code should fetch the location from the Adafruit IO feed, in seconds. -
BACKLIGHT_BRIGHTNESSconfigures the TFT backlight's brightness. -
LOCATION_IMAGESadds text and images for the location feed's values (more on this later!)
import time
import board
import displayio
import terminalio
import adafruit_imageload
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_pyportal import PyPortal
# Adafruit IO shared feed key
IO_FEED_KEY = 'location'
# Fetch the location every 10 seconds
SLEEP_DELAY_SECONDS = 10
# Set the backlight brightness, 0.0 (off) to 1.0 (max brightness)
BACKLIGHT_BRIGHTNESS = 0.5
# Location text and images
LOCATION_IMAGES = { 'home': 'images/home.bmp', 'work': 'images/office.bmp', 'gym': 'images/workout.bmp', 'commute': 'images/subway.bmp' }
Next, the PyPortal object is created along with the white background, labels for the header and location text, and a group for the icon.
# Create the PyPortal object
pyportal = PyPortal(status_neopixel=board.NEOPIXEL)
# Configure the PyPortal's display
display = board.DISPLAY
display.rotation = 0
display.brightness = BACKLIGHT_BRIGHTNESS
# Calculate the text area and image sprite locations based on the PyPortal model
if IS_PYPORTAL_TITANO:
TEXT_AREA_X = display.width // 5
TEXT_AREA_Y = 25
TEXT_AREA_LOCATION_X = (display.width // 3) - 2
TEXT_AREA_LOCATION_Y = display.height - 25
IMAGE_SPRITE_X = display.width // 3
IMAGE_SPRITE_Y = display.height // 4
else:
TEXT_AREA_X = display.width // 5
TEXT_AREA_Y = 25
TEXT_AREA_LOCATION_X = (display.width // 3) - 2
TEXT_AREA_LOCATION_Y = display.height - 25
IMAGE_SPRITE_X = display.width // 3
IMAGE_SPRITE_Y = display.height // 4
# Create a displayIO Group
group = displayio.Group()
# Draw the background
bg_group = displayio.Group()
rect = Rect(0, 0, display.width, display.height, fill=0xFFFFFF)
bg_group.append(rect)
group.append(bg_group)
# Use the default font
font = terminalio.FONT
# Draw a label for the header text
text_area = label.Label(font, text="Where is My Friend?", color=0x000000, scale=2)
text_area.x = TEXT_AREA_X
text_area.y = TEXT_AREA_Y
group.append(text_area)
# Draw a label for the location text
text_area_location = label.Label(font, text="@ the park", color=0x000000, scale=3)
text_area_location.x = TEXT_AREA_LOCATION_X
text_area_location.y = TEXT_AREA_LOCATION_Y
group.append(text_area_location)
# Create a group for the icon only
icon_group = displayio.Group()
group.append(icon_group)
# Show the group
display.root_group = group
The set_image() method is from Richard Albritton's Making a PyPortal User Interface with DisplayIO guide. This method simplifies the process of switching between images from within the while True loop.
def set_image(group, filename):
"""Sets the image file for a given group for display."""
print(f"Set image to {filename}")
if group:
group.pop()
if not filename:
return # we're done, no icon desired
try:
if image_file:
image_file.close
except NameError:
pass
image_file = open(filename, "rb")
image = displayio.OnDiskBitmap(image_file)
image_sprite = displayio.TileGrid(image, pixel_shader=getattr(image, 'pixel_shader', displayio.ColorConverter()))
image_sprite.x = IMAGE_SPRITE_X
image_sprite.y = IMAGE_SPRITE_Y
group.append(image_sprite)
Within the while True loop, we first fetch the Adafruit IO feed's most recent value.
print("Fetching location data...")
# Fetch the location data from Adafruit IO
feed = pyportal.get_io_feed(IO_FEED_KEY)
We check if the feed's value is associated with the list of locations, LOCATION_IMAGES and if the feed's value has changed since the last time it was fetched.
# If the location value is in the list of images
if feed['last_value'] in LOCATION_IMAGES:
# Check if the location has changed from the last time
# we fetched the location
if prv_location == feed['last_value']:
print("Location has not changed!")
If the value of the location feed has changed, we print out the location feed. Then, we use the set_image() method to load the desired icon onto the screen.
else: # Location has changed
print(f"Location: {feed['last_value']}")
# Load the image for the current location
set_image(icon_group, LOCATION_IMAGES[feed['last_value']])
Within LOCATION_IMAGES there is a dictionary of locations and associated images (in bitmap (bmp) format).
If you want to display different locations from the pre-selected ones - Change the dictionary's values and the image's file path.
LOCATION_IMAGES = { 'home': 'images/home.bmp', 'work': 'images/office.bmp', 'gym': 'images/workout.bmp', 'commute': 'images/subway.bmp' }
The text on the bottom of the screen is updated to display "@ [CURRENT LOCATION]" and the display is forced to refresh. Finally, we update the prv_location variable to store the current feed value.
# Update the location text
text_area_location.text=f"@ {feed['last_value']}"
# Show the refreshed group
display.root_group = group
# Update the previous location
prv_location = feed['last_value']
At the end of the while True loop, we wait 5 minutes before attempting to fetch the location feed again.
# Wait 5 minutes (300 seconds) before fetching the location feed again
print("Sleeping, fetching the location again in 5 minutes!")
time.sleep(SLEEP_DELAY_SECONDS)
Page last edited August 07, 2024
Text editor powered by tinymce.
Usage
Viewing the location on PyPortal
Plug the PyPortal into USB power. You should see the display change, reflecting the current value of the location feed.
If no value has been written to this feed yet, your PyPortal's screen will display "@ Unknown".
When your friend moves within the range of a pre-set location, the Automation will write the location's name to the shared Adafruit IO feed.
Every five minutes, the PyPortal's display will update with the current location. If their location hasn't changed, the display will display the last known location.
Page last edited August 07, 2024
Text editor powered by tinymce.