pyportal Data Logger
This is a remix of the Adafruit IoT Data Logger, customized for MCC-US 1029
Step 1: Start by confirming which device you have – the standard PyPortal (no external sensor) or a PyPortal Pynt (has an external temperature sensor attached.) It matters just for the first step here.
If you have the standard PyPortal – download the most recent release of CircuitPython (a uf2 file), plug in your board, double-click the resent button to put it in “bootloader mode”, and then drag the .uf2 file to the PORTALBOOT drive. Your drive should reset and reappear as CIRCUITPY.
If you have the PyPortal Pynt – download the most recent release of CircuitPython (a uf2 file), plug in your board, double-click the resent button to put it in “bootloader mode”, and then drag the .uf2 file to the PORTALBOOT drive. Your drive should reset and reappear as CIRCUITPY.
From here on the steps are the same for both devices!
Step 2: Make sure you update the libraries on the CIRCUITPY board – download the DataLoggerLibraries.zip file here, or you can manually libraries from wherever you downloaded your library folder until the lib folder on CIRCUITPY looks like this:

Step 3: Double check that your secrets.py file has the correct wifi name, wifi password, as well as your Adafruit IO username and key. (You can sign up or log in at that link, and then once you are logged in, the key is found by clicking the big yellow circle with the black key icon in it).
Step 4: Set up your Dashboard on Adafruit IO to have a light block, a temperature block, a Status Indicator Block, and then two line graphs- one for Light and one for Temperature. You can follow the directions on the Adafruit site if you haven’t done this yet. Your dash should look like this:

Step 5: Open Mu and load the Pyportal code file. If you don’t have the Mu editor, grab it from codewith.mu. Open it up and make sure it’s in the right mode (click the Mode button in the upper left and select CircuitPython.) And then load your code.py file from your CIRCUITPY drive. Make sure ALL of the code you work with below is always, always named code.py.
The DataLogger
Now, it’s time to code! We’re going to start with a slightly modified version of the original Adafruit code. Cut and paste the following into the code.py file, and save it to run it:
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
# SPDX-License-Identifier: MIT
"""
PyPortal IOT Data Logger
"""
import time
import board
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import neopixel
from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError
import adafruit_adt7410
# set delay length for timer
iotimer = 30 # this is the length of the delay between data uploads, in seconds
# 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
# PyPortal ESP32 Setup
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)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# Set your Adafruit IO Username and Key in secrets.py
# (visit io.adafruit.com if you need to create an account,
# or if you need your Adafruit IO key.)
ADAFRUIT_IO_USER = secrets['aio_username']
ADAFRUIT_IO_KEY = secrets['aio_key']
# Create an instance of the Adafruit IO HTTP client
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
try:
# Get the 'temperature' feed from Adafruit IO
temperature_feed = io.get_feed('temperature')
light_feed = io.get_feed('light')
except AdafruitIO_RequestError:
# If no 'temperature' feed exists, create one
temperature_feed = io.create_new_feed('temperature')
light_feed = io.create_new_feed('light')
# Set up ADT7410 sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
adt.high_resolution = True
# Set up an analog light sensor on the PyPortal
adc = AnalogIn(board.LIGHT)
while True:
try:
light_value = adc.value
print('Light Level: ', light_value)
temperature = adt.temperature
print('Temperature: C', (temperature))
print('Sending to Adafruit IO...')
io.send_data(light_feed['key'], light_value)
io.send_data(temperature_feed['key'], temperature, precision=2)
print('Sent to Adafruit IO!')
except (ValueError, RuntimeError, ConnectionError, OSError) as e:
print("Failed to get data, retrying\n", e)
wifi.reset()
continue
print('Delaying {0} seconds...'.format(iotimer))
time.sleep(iotimer)
Let’s go over what is happening here: we are importing a bunch of libraries, then initializing the ESP chip (for wifi). Once it has internet access, the PyPortal checks for your Adafruit IO feeds and sets them up if they aren’t there. Finally, it initializes the light sensor and the temperature sensor (those of you using the Pynt have an external temperature sensor but the code is the same, yay standards!) Then our loop runs. On the PyPortal itself you should see a stream of data as it logs the light level and the temperature and sends that data. You can also see the results on your Adafruit Dash. Yay, you did it!
Using the Touchscreen
Right now, the code is automatically taking measurements every 30 seconds. What if we wanted to be able to tell it when to take data? Good news- the entire screen of your PyPortal is a touchscreen, so we can use it as a giant button. To do so, we’re going to make three modifications to the code.
Modification 1: add the touchscreen library to the end of our list of libraries. After the line import adafruit_adt7410, add the following. For this, you don’t need a line of space between the old code and the new:
import adafruit_touchscreen
Modification 2: Now add a block of code to set up the touchscreen. We’ll do it right after the light sensor set up code, to keep things need. After the line adc = AnalogIn(board.LIGHT) add the following code. For this, make sure you have an extra line both before and after the new code block:
# Set up Touchscreen
display = board.DISPLAY
ts = adafruit_touchscreen.Touchscreen(
board.TOUCH_XL,
board.TOUCH_XR,
board.TOUCH_YD,
board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(320, 240),
)
Modification 3: You’ve got the touchscreen libraries loaded, and you’ve used them to set up the screen, now it’s time to actually use it in the code! We’re going to add two lines in the loop itself (after the while True command). First, we’ll set up a variable to make our lives easier (touch = ts.touch_point), telling the device that any measurement taken by the touchscreen is stored as “touch”, then we’ll tell the code it should only run if there is a value in “touch” (ie, if you touched the screen.). Change your loop to look like this (or you can delete the existing While True loop and replace it with this):
while True:
touch = ts.touch_point
if touch:
try:
light_value = adc.value
print('Light Level: ', light_value)
temperature = adt.temperature
print('Temperature: C', (temperature))
print('Sending to Adafruit IO...')
io.send_data(light_feed['key'], light_value)
io.send_data(temperature_feed['key'], temperature, precision=2)
print('Sent to Adafruit IO!')
except (ValueError, RuntimeError, ConnectionError, OSError) as e:
print("Failed to get data, retrying\n", e)
wifi.reset()
continue
print('Delaying {0} seconds...'.format(iotimer))
time.sleep(iotimer)
Great, now all together, your code looks like this:
"""
PyPortal IOT Data Logger with TouchScreen Trigger
"""
import time
import board
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import neopixel
from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError
import adafruit_adt7410
import adafruit_touchscreen
# set delay length for timer
iotimer = 30 # this is the length of the delay between data uploads, in seconds
# 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
# PyPortal ESP32 Setup
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)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# Set your Adafruit IO Username and Key in secrets.py
# (visit io.adafruit.com if you need to create an account,
# or if you need your Adafruit IO key.)
ADAFRUIT_IO_USER = secrets['aio_username']
ADAFRUIT_IO_KEY = secrets['aio_key']
# Create an instance of the Adafruit IO HTTP client
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
try:
# Get the 'temperature' feed from Adafruit IO
temperature_feed = io.get_feed('temperature')
light_feed = io.get_feed('light')
except AdafruitIO_RequestError:
# If no 'temperature' feed exists, create one
temperature_feed = io.create_new_feed('temperature')
light_feed = io.create_new_feed('light')
# Set up ADT7410 sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
adt.high_resolution = True
# Set up an analog light sensor on the PyPortal
adc = AnalogIn(board.LIGHT)
# Set up Touchscreen
display = board.DISPLAY
ts = adafruit_touchscreen.Touchscreen(
board.TOUCH_XL,
board.TOUCH_XR,
board.TOUCH_YD,
board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(320, 240),
)
while True:
touch = ts.touch_point
if touch:
try:
light_value = adc.value
print('Light Level: ', light_value)
temperature = adt.temperature
print('Temperature: C', (temperature))
print('Sending to Adafruit IO...')
io.send_data(light_feed['key'], light_value)
io.send_data(temperature_feed['key'], temperature, precision=2)
print('Sent to Adafruit IO!')
except (ValueError, RuntimeError, ConnectionError, OSError) as e:
print("Failed to get data, retrying\n", e)
wifi.reset()
continue
print('Delaying {0} seconds...'.format(iotimer))
time.sleep(iotimer)
Go ahead and click Save to run it. You should immediately see a difference: instead of a stream of text, the PyPortal screen has just stopped with the words code.py output:
Go ahead and tap the screen! The familiar data log should come on. It does register a touch but you will likely need to push a little harder than you are used to with your phone, so if it doesn’t work at first just tap a little harder. Check your Adafruit Dash to see the results. And pay yourself on the back because you did it!
TouchScreen with countdown
The code still only runs every 30 seconds, but since you are the one who triggers it, it would be helpful to know how long 30 seconds is, wouldn’t it? In this next project, we will write our own countdown function using a loop. It will count backwards from 30 and then let you know when the screen is ready to be tapped again.
You’ll want to put the function outside of the While True loop, just to keep things neat. Go to the line under your touchscreen set up code and enter the following:
def countdowntimer():
print("30 Second Countdown")
x = range(iotimer, -1, -1)
for t in x:
print("Time:", t)
time.sleep(1)
print("Ready!")
We’re using some of the built-in libraries here, so no need for new libraries. Let’s go over the code: def sets up a function, and we’re calling it countdowntimer. Everything after the colon is the actual function, which will print “30 Second Countdown”, then print the countdown from 30 down to 1, and then print “Ready!”. The first and last lines that say “print” are pretty straightforward, so it’s that bit in the middle we’re going to focus on.
This is called a for loop, and it’s a way to iterate over a set group of items called a range. The range needs three pieces of info (or arguments): the start of the range, the end of the range, and the steps, or how much it should count by. Starting with the last item first, we want to count backwards so the third number is -1. For code display reasons we actually want it to go past 0, so we put -1 as the end of the range. But you’ll see that the first argument isn’t a number, it’s another variable, called iotimer. What’s with that?
Look up in the code, right up near the top, under the libraries. You’ll see these lines:
# set delay length for timer
iotimer = 30 # this is the length of the delay between data uploads, in seconds
We’ve actually had this variable in the code all along! The delay in data uploads, called iotimer, is set to 30 seconds. You could change it to anything if you wanted, but be warned: Adafruit won’t let you upload more frequently than 30 seconds without a paid account, otherwise you’re overloading their servers. So leave it alone for now.
Back to our loop: we’ve set our range, called x, as -1 to iotimer (or 30), iterated backwards by 1 (so a step of -1). The next bit is the actual loop, and in it, we set yet another variable, called t, which is a way of referring to each of the numbers in the range as they go by. So, what this code means is, as you count backwards from 30, call each number that you are on t, and then do the following to it:
print("Time:", t)
That just means print the word time and the number you are currently counting on. Then the code sleeps for 1 second. That’s it! You’ve written a function.
But see, it’s not doing anything unless we actually use it! So now, go down to the very bottom of your code, in the While True loop, and add the line countdowntimer(). You want it to be inline with the try/except code, so one line de-indented from the one before it. Your While True loop now looks like this:
while True:
touch = ts.touch_point
if touch:
try:
light_value = adc.value
print('Light Level: ', light_value)
temperature = adt.temperature
print('Temperature: C', (temperature))
print('Sending to Adafruit IO...')
io.send_data(light_feed['key'], light_value)
io.send_data(temperature_feed['key'], temperature, precision=2)
print('Sent to Adafruit IO!')
except (ValueError, RuntimeError, ConnectionError, OSError) as e:
print("Failed to get data, retrying\n", e)
wifi.reset()
continue
countdowntimer()
Python is picky about whitespace, so make sure your last line is indented properly. If you put it in line with the line before it, it won’t run at the right time. But the code will pass the error check!
Your full code now looks like this:
"""
PyPortal IOT Data Logger with TouchScreen Trigger and Countdown
"""
import time
import board
import busio
from digitalio import DigitalInOut
from analogio import AnalogIn
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import neopixel
from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError
import adafruit_adt7410
import adafruit_touchscreen
# set delay length for timer
iotimer = 30 # this is the length of the delay between data uploads, in seconds
# 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
# PyPortal ESP32 Setup
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)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# Set your Adafruit IO Username and Key in secrets.py
# (visit io.adafruit.com if you need to create an account,
# or if you need your Adafruit IO key.)
ADAFRUIT_IO_USER = secrets['aio_username']
ADAFRUIT_IO_KEY = secrets['aio_key']
# Create an instance of the Adafruit IO HTTP client
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
try:
# Get the 'temperature' feed from Adafruit IO
temperature_feed = io.get_feed('temperature')
light_feed = io.get_feed('light')
except AdafruitIO_RequestError:
# If no 'temperature' feed exists, create one
temperature_feed = io.create_new_feed('temperature')
light_feed = io.create_new_feed('light')
# Set up ADT7410 sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
adt.high_resolution = True
# Set up an analog light sensor on the PyPortal
adc = AnalogIn(board.LIGHT)
# Set up Touchscreen
display = board.DISPLAY
ts = adafruit_touchscreen.Touchscreen(
board.TOUCH_XL,
board.TOUCH_XR,
board.TOUCH_YD,
board.TOUCH_YU,
calibration=((5200, 59000), (5800, 57000)),
size=(320, 240),
)
def countdowntimer():
print("30 Second Countdown")
x = range(iotimer, -1, -1)
for t in x:
print("Time:", t)
time.sleep(1)
print("Ready!")
while True:
touch = ts.touch_point
if touch:
try:
light_value = adc.value
print('Light Level: ', light_value)
temperature = adt.temperature
print('Temperature: C', (temperature))
print('Sending to Adafruit IO...')
io.send_data(light_feed['key'], light_value)
io.send_data(temperature_feed['key'], temperature, precision=2)
print('Sent to Adafruit IO!')
except (ValueError, RuntimeError, ConnectionError, OSError) as e:
print("Failed to get data, retrying\n", e)
wifi.reset()
continue
countdowntimer()
Save it and run it, and you’ll get a functioning countdown clock for your data logger! Plug the battery pack in and take it on the go.
Once you are done here, you’re going to use Display IO to add a button.