Skip to content

Learn IoT Embedded Programming with CircuitPython on ESP32-S2

License

Notifications You must be signed in to change notification settings

tamberg/circuitpython-workshop

Repository files navigation

IoT Embedded Programming with CircuitPython

Workshop

The Internet of Things (IoT) is the convergence of internet and real world. IoT embedded devices typically have limited resources, but they are also becoming more performant with each generation. This allows an interpreted language like Python, which is less efficient but more convenient than C, to run on a microcontroller.

Topics

Objective

This workshop teaches the basics of embedded programming on the latest IoT hardware, with CircuitPython.

Target audience

This workshop is aimed at interested people with basic programming experience in Python.

Prerequisites

Participants need a laptop with MacOS, Windows or Linux, and one USB/USB-C port. IoT hardware including sensors is available on loan.

The workshop requires a Wi-Fi network that is accessible without a portal. Alternatively, a personal smartphone can be used as a hotspot.

Introduction

CircuitPython

The easiest way to program microcontrollers — https://circuitpython.org/

To program a CircuitPython microcontroller, plug it in via USB.

It shows up as a USB drive called CIRCUITPY.

(If not, see hardware setup.)

Toolchain setup

Code editor

CircuitPython works with any text editor, e.g. Mu Editor, VS Code, or nano.

$ nano /Volumes/CIRCUITPY/code.py

Serial monitor

To see output you'll need a serial monitor like PuTTY on Windows or screen on MacOS, Linux (or tio).

$ screen /dev/tty.u<TAB> 115200

If there is no output, use CTRL-D to reload

Hello, World!

Code done running.

Or press any other key to enter the REPL

>>>

(To end screen press CTRL-A-K.)

CircuitPython libraries

Download the library bundle 9.x ZIP file from https://circuitpython.org/libraries

You will selectively copy files from the ZIP to your microcontroller later on.

Run Python code

Plug in your board via USB and open the CIRCUITPY drive.

Copy required libraries from the bundle to the lib folder.

Copy your code to a file named code.py on the drive.

$ cp hello/code.py /Volumes/CIRCUITPY/code.py

Now you are ready to try GPIO & sensors.

Hardware setup

We use an ESP32-S2 microcontroller with Grove sensors and actuators.

ESP32-S2

Buy

Board

https://circuitpython.org/board/adafruit_qtpy_esp32s2/

ESP32-S2 ROM bootloader mode (once)

To get the ESP32-S2 into ROM bootloader mode

  • Press and hold the BOOT button
  • Then, press the RESET button
  • Release the BOOT button

Now the board should show up as a USB device, e.g. /dev/cu.usbmodem01 on MacOS or COM3 on Windows.

Install UF2 bootloader (once)

To install the UF2 bootloader, follow the steps to Install, Repair, or Update UF2 Bootloader at the bottom of https://circuitpython.org/board/adafruit_qtpy_esp32s2/ or try this:

Now the board should show up as a USB drive named QTPYS2BOOT.

Install CircuitPython (once)

To install CircuitPython or more precisely the CircuitPython interpreter, follow these steps:

Now the board should show up as a USB drive named CIRCUITPY.

Troubleshooting

Pinout

Schematic

Datasheets

Grove sensors & actuators

Buy

GPIO & sensors

Blink (digital output)

Control a LED or any other digital actuator.

/CIRCUITPY
└── code.py # copied from below
import board
import digitalio
import time

actuator = digitalio.DigitalInOut(board.D18)
actuator.direction = digitalio.Direction.OUTPUT

while True:
    actuator.value = True
    time.sleep(1)
    actuator.value = False
    time.sleep(1)
# No output, but LED should blink

Button (digital input)

Read a button or any other digital sensor.

/CIRCUITPY
└── code.py # copied from below
import board
import digitalio
import time

sensor = digitalio.DigitalInOut(board.D18)
sensor.direction = digitalio.Direction.INPUT
sensor.pull = digitalio.Pull.UP

while True:
    print(sensor.value)
    time.sleep(0.1)
False
False
True
...

DHT11 temperature & humidity

Read a DHT11 sensor using the adafruit_dht library.

/CIRCUITPY
├── code.py # copied from below
└── lib # libraries from bundle
    └── adafruit_dht.mpy
import adafruit_dht
import board
import time

sensor = adafruit_dht.DHT11(board.D18)

while True:
    try:
        temp = sensor.temperature
        humi = sensor.humidity
        print("{:.2f} °C, {:.2f} %".format(temp, humi))

    except RuntimeError as e:
        print("Oops, reading the sensor did not work.")

    time.sleep(5)
23.00 °C, 42.00 %
...

More

Search the library bundle docs for a sensor or actuator name.

Wi-Fi, HTTP & MQTT

Wi-Fi connect

Connect to the Internet using Wi-Fi.

/CIRCUITPY
└── code.py # copied from below
import wifi

WIFI_SSID = "MY_SSID" # TODO
WIFI_PASS = "MY_PASSWORD" # TODO

print("Connecting to Wi-Fi \"{0}\"...".format(WIFI_SSID))
wifi.radio.connect(WIFI_SSID, WIFI_PASS) # waits for IP address
print("Connected, IP address = {0}".format(wifi.radio.ipv4_address))
Connecting to Wi-Fi "MY_SSID"...
Connected, IP address = 192.168.0.23

Code done running.

Once a device is connected to the Internet, it can send data to a cloud backend.

To see how this works, try the HTTP post or MQTT publish examples.

HTTP post

Post data to the https://thingspeak.com/ cloud backend using HTTPS.

Create a free ThingSpeak account to get a Write API Key.

/CIRCUITPY
├── code.py # copied from below
└── lib # libraries from bundle
    ├── adafruit_connection_manager.mpy
    └── adafruit_requests.mpy
import ssl
import time
import wifi
import socketpool

import adafruit_requests

WIFI_SSID = "MY_SSID" # TODO
WIFI_PASS = "MY_PASSWORD" # TODO
CLOUD_KEY = "****************" # TODO, ThingSpeak Write API Key
CLOUD_URL = "https://api.thingspeak.com/update.json"

print("Connecting to Wi-Fi \"{0}\"...".format(WIFI_SSID))
wifi.radio.connect(WIFI_SSID, WIFI_PASS) # waits for IP address
print("Connected, IP address = {0}".format(wifi.radio.ipv4_address))

socket = socketpool.SocketPool(wifi.radio)
context = ssl.create_default_context()
https = adafruit_requests.Session(socket, context)

while True:
    value = 23.0 # e.g. from sensor
    json_data = {
        "api_key": CLOUD_KEY,
        "field1": value, 
    }
    print("Posting to {0}\n> {1}".format(CLOUD_URL, json_data))
    response = https.post(CLOUD_URL, json=json_data)
    print("< {0}".format(response.json()))
    time.sleep(30) # s

Connecting to Wi-Fi "MY_SSID"...
Connected, IP address = 192.168.0.42
Posting to https://api.thingspeak.com/update.json
> {'field1': 23.0, 'api_key': '****************'}
< {'field1': 23.0, 'channel_id': 555, 'created_at': '2022-08-30T13:37:00Z', ...

Now, try to merge in the DHT11 example to send real sensor values.

Or learn more about Internet protocols and HTTP.

MQTT publish

Publish data to the https://thingspeak.com/ cloud backend using MQTT.

/CIRCUITPY
├── code.py # copied from below
└── lib # libraries from bundle
    ├── adafruit_connection_manager.mpy
    ├── adafruit_tricks.mpy
    └── adafruit_minimqtt
        ├── __init__.py
        ├── adafruit_minimqtt.mpy
        └── matcher.mpy
from random import randint
import ssl
import time
import wifi
import socketpool
import adafruit_minimqtt.adafruit_minimqtt as minimqtt

WIFI_SSID = "MY_SSID" # TODO
WIFI_PASS = "MY_PASSWORD" # TODO

# See https://ch.mathworks.com/help/thingspeak/mqtt-basics.html
MQTT_HOST = "mqtt3.thingspeak.com"
MQTT_PORT = 8883

# https://thingspeak.com/devices/mqtt > Add a device
MQTT_CLNT = "***********************" # TODO, Client ID
MQTT_USER = "***********************" # TODO, Username
MQTT_PASS = "***********************" # TODO, Password
THSP_CHAN = "000000" # TODO, ThingSpeak Channel ID

print("Connecting to Wi-Fi \"{0}\"...".format(WIFI_SSID))
wifi.radio.connect(WIFI_SSID, WIFI_PASS) # waits for IP address
print("Connected, IP address = {0}".format(wifi.radio.ipv4_address))

pool = socketpool.SocketPool(wifi.radio)
context = ssl.create_default_context()

def handle_connect(client, userdata, flags, rc):
    print("Connected to {0}".format(client.broker))

def handle_publish(client, userdata, topic, pid):
    print("Published to {0} with PID {1}".format(topic, pid))

mqtt_client = minimqtt.MQTT(
    broker = MQTT_HOST,
    port = MQTT_PORT,
    client_id = MQTT_CLNT,
    username = MQTT_USER,
    password = MQTT_PASS,
    socket_pool = pool,
    ssl_context = context)

mqtt_client.on_connect = handle_connect
mqtt_client.on_publish = handle_publish

print("\nConnecting to {0}...".format(MQTT_HOST))
mqtt_client.connect()

while True:
    value = 23 # e.g. from sensor
    mqtt_topic = "channels/" + THSP_CHAN + "/publish"
    mqtt_payload = "field1=" + str(value)
    mqtt_client.publish(mqtt_topic, mqtt_payload)
    time.sleep(5)

Learn more about the MQTT messaging protocol.

About

Learn IoT Embedded Programming with CircuitPython on ESP32-S2

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages