The examples on this page assume you are using the Adafruit Feather ESP32-S2 with BME280. If you are using a Feather ESP32-S2 without BME280, you can remove the BME280 code from these examples to use the LC709203 battery monitor alone.

The Adafruit Feather ESP32-S2 with BME280 comes with a BME280 temperature, humidity and pressure sensor built right into the board. Both the Feather ESP32-S2 and the Feather ESP32-S2 with BME280 come with an LC709203 lithium ion polymer (lipoly) battery monitor. Both the BME280 and the LC709203 are available over I2C.

Each sensor comes with its own Adafruit CircuitPython library that makes it simple to write code to read data from them. These examples will be using, among other things, the Adafruit CircuitPython BME280 and the Adafruit CircuitPython LC709203F libraries, both of which are available in the Adafruit CircuitPython Library Bundle.

The first example simply reads data from the sensors and prints it to the serial console. The temperature data will be skewed high because of the physical proximity to the ESP32-S2 module which will heat up over time while in use. The example is designed to show you how to get data from the sensor.

The second example takes the sensor data and sends it to Adafruit IO. It is designed to run on battery power and utilises deep sleep to keep the board cool for temperature readings. You must have an Adafruit IO account and Wi-Fi access for this example to work.

The temperatures reported by the BME280 when the Feather has been running for a period of time, or when charging a LiPoly battery, may be upwards of 15 degrees Fahrenheit higher than ambient temperature due to the heat produced by the ESP32-S2 module and the charging circuitry. If you require running the board, charging the battery and accurate temperature values, consider switching to a STEMMA QT connected external temperature sensor.

Sensor Location

No wiring is necessary because the sensors are built into the Feather.

  • The BME280 temperature, humidity and pressure sensor (highlighted in green) is located in the center of the board. Its I2C address is 0x77.
  • The LC709203 battery monitor (highlighted in red) is located slightly above and to the left of the BME280. Its I2C address is 0x0B. (0xb, which is returned by I2C scan, is the same as 0x0B, it simply has leading zeros.)

BME280 and LC709203 Simple Data Example

To run this example, you need to first install the BME280 and LC709203F 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, and copy the entire lib folder and the code.py file to your CIRCUITPY drive.

Your CIRCUITPY/lib folder should contain the following folder and file:

  • adafruit_bme280/
  • adafruit_lc709203f.mpy
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: Unlicense
"""
CircuitPython Simple Example for BME280 and LC709203 Sensors
"""
import time
import board
import digitalio
from adafruit_bme280 import basic as adafruit_bme280
from adafruit_lc709203f import LC709203F, PackSize

# Pull the I2C power pin low
i2c_power = digitalio.DigitalInOut(board.I2C_POWER_INVERTED)
i2c_power.switch_to_output()
i2c_power.value = False

# Create sensor objects, using the board's default I2C bus.
i2c = board.I2C()
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
battery_monitor = LC709203F(board.I2C())
battery_monitor.pack_size = PackSize.MAH400

# change this to match your location's pressure (hPa) at sea level
bme280.sea_level_pressure = 1013.25

while True:
    print("\nTemperature: {:.1f} C".format(bme280.temperature))
    print("Humidity: {:.1f} %".format(bme280.relative_humidity))
    print("Pressure: {:.1f} hPa".format(bme280.pressure))
    print("Altitude: {:.2f} meters".format(bme280.altitude))
    print("Battery Percent: {:.2f} %".format(battery_monitor.cell_percent))
    print("Battery Voltage: {:.2f} V".format(battery_monitor.cell_voltage))
    time.sleep(2)

Now, connect to the serial console to see the sensor data printed out!

That's all there is to reading the BME280 and LC709203 data from the Feather ESP32-S2 using CircuitPython!

For more details, check out the guides for the BME280 and the LC709203.

You'll notice the BME280 temperature values are a bit high - anywhere up to 10-15 degrees F higher than your ambient temperature. Depending on whether you have a charging battery plugged in, the increased temperature is due to one or both of two things: the heat radiating from the ESP32-S2 module and/or the heat from the battery charging circuitry. How can you get reliable temperature data from your Feather ESP32-S2 with BME280? Easy: CircuitPython and Adafruit IO.

BME280 and LC709203 Adafruit IO Example

Adafruit IO gives you the option to disconnect the Feather ESP32-S2 from your computer and run it off of USB power or a battery, and still be able to see the data. This example takes the same data printed to the serial console in the example above, and also sends it to Adafruit IO.

Adafruit IO Example Secrets

This example requires you to provide your Wi-Fi credentials, and your Adafruit IO username and key. To do this, you'll want to create a secrets.py file on your CIRCUITPY drive, with the following structure.

To find your Adafruit IO key, follow the initial steps on this page.

Update the following with your info, keeping the " around them:

  • Replace "your-wifi-ssid" with your Wi-Fi SSID
  • Replace "your-wifi-password" with your Wi-Fi password
  • Replace "your-aio-username" with your Adafruit IO username
  • Replace "your-super-long-aio-key" with your Adafruit IO key

Your secrets.py file should look like the code below, but with the above updated info.

# 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-wifi-ssid",
    "password": "your-wifi-password",
    "aio_username": "your-aio-username",
    "aio_key": "your-super-long-aio-key",
}

Adafruit IO Example Code

To run this example, you need to first install the BME280, LC709203F, Adafruit IO, and Adafruit Requests 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, and copy the entire lib folder and the code.py file to your CIRCUITPY drive.

Your CIRCUITPY/lib folder should contain the following folders and files:

  • adafruit_bme280/
  • adafruit_io/
  • adafruit_lc709203f.mpy
  • adafruit_requests.mpy
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: Unlicense
"""
CircuitPython Adafruit IO Example for BME280 and LC709203 Sensors
"""
import time
import ssl
import alarm
import board
import digitalio
import wifi
import socketpool
import adafruit_requests
from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError
from adafruit_lc709203f import LC709203F, PackSize
from adafruit_bme280 import basic as adafruit_bme280
try:
    from secrets import secrets
except ImportError:
    print("WiFi and Adafruit IO credentials are kept in secrets.py, please add them there!")
    raise

# Duration of sleep in seconds. Default is 600 seconds (10 minutes).
# Feather will sleep for this duration between sensor readings / sending data to AdafruitIO
sleep_duration = 600

# Update to match the mAh of your battery for more accurate readings.
# Can be MAH100, MAH200, MAH400, MAH500, MAH1000, MAH2000, MAH3000.
# Choose the closest match. Include "PackSize." before it, as shown.
battery_pack_size = PackSize.MAH400

# Setup the little red LED
led = digitalio.DigitalInOut(board.LED)
led.switch_to_output()

# Pull the I2C power pin low
i2c_power = digitalio.DigitalInOut(board.I2C_POWER_INVERTED)
i2c_power.switch_to_output()
i2c_power.value = False

# Set up the BME280 and LC709203 sensors
bme280 = adafruit_bme280.Adafruit_BME280_I2C(board.I2C())
battery_monitor = LC709203F(board.I2C())
battery_monitor.pack_size = battery_pack_size

# Collect the sensor data values and format the data
temperature = "{:.2f}".format(bme280.temperature)
temperature_f = "{:.2f}".format((bme280.temperature * (9 / 5) + 32))  # Convert C to F
humidity = "{:.2f}".format(bme280.relative_humidity)
pressure = "{:.2f}".format(bme280.pressure)
battery_voltage = "{:.2f}".format(battery_monitor.cell_voltage)
battery_percent = "{:.1f}".format(battery_monitor.cell_percent)


def go_to_sleep(sleep_period):
    # Create a an alarm that will trigger sleep_period number of seconds from now.
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + sleep_period)
    # Exit and deep sleep until the alarm wakes us.
    alarm.exit_and_deep_sleep_until_alarms(time_alarm)


# Fetch the feed of the provided name. If the feed does not exist, create it.
def setup_feed(feed_name):
    try:
        # Get the feed of provided feed_name from Adafruit IO
        return io.get_feed(feed_name)
    except AdafruitIO_RequestError:
        # If no feed of that name exists, create it
        return io.create_new_feed(feed_name)


# Send the data. Requires a feed name and a value to send.
def send_io_data(feed, value):
    return io.send_data(feed["key"], value)


# Wi-Fi connections can have issues! This ensures the code will continue to run.
try:
    # Connect to Wi-Fi
    wifi.radio.connect(secrets["ssid"], secrets["password"])
    print("Connected to {}!".format(secrets["ssid"]))
    print("IP:", wifi.radio.ipv4_address)

    pool = socketpool.SocketPool(wifi.radio)
    requests = adafruit_requests.Session(pool, ssl.create_default_context())

# Wi-Fi connectivity fails with error messages, not specific errors, so this except is broad.
except Exception as e:  # pylint: disable=broad-except
    print(e)
    go_to_sleep(60)

# 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.)
aio_username = secrets["aio_username"]
aio_key = secrets["aio_key"]

# Initialize an Adafruit IO HTTP API object
io = IO_HTTP(aio_username, aio_key, requests)

# Turn on the LED to indicate data is being sent.
led.value = True
# Print data values to the serial console. Not necessary for Adafruit IO.
print("Current BME280 temperature: {0} C".format(temperature))
print("Current BME280 temperature: {0} F".format(temperature_f))
print("Current BME280 humidity: {0} %".format(humidity))
print("Current BME280 pressure: {0} hPa".format(pressure))
print("Current battery voltage: {0} V".format(battery_voltage))
print("Current battery percent: {0} %".format(battery_percent))

# Adafruit IO sending can run into issues if the network fails!
# This ensures the code will continue to run.
try:
    print("Sending data to AdafruitIO...")
    # Send data to Adafruit IO
    send_io_data(setup_feed("bme280-temperature"), temperature)
    send_io_data(setup_feed("bme280-temperature-f"), temperature_f)
    send_io_data(setup_feed("bme280-humidity"), humidity)
    send_io_data(setup_feed("bme280-pressure"), pressure)
    send_io_data(setup_feed("battery-voltage"), battery_voltage)
    send_io_data(setup_feed("battery-percent"), battery_percent)
    print("Data sent!")
    # Turn off the LED to indicate data sending is complete.
    led.value = False

# Adafruit IO can fail with multiple errors depending on the situation, so this except is broad.
except Exception as e:  # pylint: disable=broad-except
    print(e)
    go_to_sleep(60)

go_to_sleep(sleep_duration)

You can connect to the serial console to see the current readings printed out.

Depending on when you first connect to the serial console, there may be a delay before anything is displayed. The readings are taken silently before engaging in the Wi-Fi connection and sending data to Adafruit IO. The data is printed to the serial console immediately before sending to Adafruit IO.

Customisation

This example is designed to run one time and then put the board to sleep for a period of time, before repeating the process. You can customise the period of time it sleeps between readings.

Update sleep_duration = 600 at the beginning of your code to the number of seconds you would like to have between data readings. The default is 600 seconds, or 10 minutes.

Be aware, to avoid the board heating up due to the Wi-Fi connection and data transmission to Adafruit IO, you must sleep for at least five minutes (300 seconds) between readings. Anything five minutes or longer will work.

If you customise your code to run more often than every 300 seconds, your temperature readings will be higher than ambient due to the ESP32-S2 warming up. Keep the sleep_duration 300 seconds or more to ensure accurate readings!

Update battery_pack_size = PackSize.MAH400 to match your battery's mAh. This increases the accuracy of the battery monitor readings.

The options are MAH100, MAH200, MAH400, MAH500, MAH1000, MAH2000, or MAH3000. You must include the PackSize. before the MAHXXX.

If none of the options match your battery size, choose the one that is the closest. For example, if your battery is 1200mAh, choose MAH1000.

Code Walk-Through

First you import all of the necessary modules and libraries.

Next, you customise your sleep duration and battery pack size, as discussed above.

Then, you setup the hardware. The red LED is used to visually indicate when data is being sent to Adafruit IO. The I2C power pin is enabled and pulled low. The BME280 and the LC708203 are both setup using I2C. And finally, the battery pack size is set.

Following setup, you get the current data readings. It is important to obtain the data as early as possible in the code to ensure that the heat from the ESP32-S2 module has not affected the temperature readings. The Wi-Fi connection and sending data to Adafruit IO are done after the data is read to ensure the most accurate temperature data.

This example is designed to obtain the data readings as quickly as possible after waking up the board, before connecting to Wi-Fi and sending data to Adafruit IO. This ensures the temperature readings from the BME280 are as accurate as possible.

Then there are three functions.

The first function creates an alarm that will trigger after a number of seconds, and then tells the board to sleep until the alarm is triggered.

The second function sets up an Adafruit IO feed by first trying to get the feed, and if it does not exist, creating the feed on Adafruit IO.

The third function takes data and sends it to a specified feed on Adafruit IO.

The are two sections wrapped it in a try / except blocks. This is because Wi-Fi can run into connection issues, and Adafruit IO can fail to send for various reasons. If either of these fail, your code would crash and stop running. The try / except ensures your code will continue running. Each section tries to run the code under the try, and if it fails, tells the board to to go to sleep for 60 seconds before waking up and running the code again.

The first try / except block is around the Wi-Fi connection code. You try to connect to Wi-Fi using the credentials you provided in secrets.py, and setup for communication using the Wi-Fi.

Then, you get your Adafruit IO credentials from secrets.py, and initialise the Adafruit IO HTTP connection.

The next section begins with turning on the red LED to indicate the data is being sent to Adafruit IO. The data is first printed to the serial console.

The second try / except block is around the Adafruit IO data sending code. Using the second and third functions described above, the code tries to send each piece of data to its own feed on Adafruit IO. The LED is then turned off to indicate sending data is completed.

Finally, the code tells the board to go to sleep for the sleep duration specified at the beginning of your code.

That covers using the Adafruit Feather ESP32-S2 with BME280 and CircuitPython with Adafruit IO to track temperature, humidity, pressure and battery data!

This guide was first published on Nov 25, 2021. It was last updated on 2022-01-12 17:03:12 -0500.

This page (I2C: On-Board Sensors) was last updated on Jan 16, 2022.

Text editor powered by tinymce.