HalesAir Β· Session 03
MicroPython
Fundamentals
Halesowen College Β· T Level Data Analytics
Setting up Thonny IDE Variables, loops & functions Reading from the BME680 Printing & formatting data
π₯οΈ
Part 1
Setting Up Thonny
The IDE designed for exactly this
Thonny β Your Development Environment
What Thonny does
- Text editor with Python syntax highlighting
- Connects directly to Pico W over USB
- Interactive REPL β type code, see results instantly
- Upload and run scripts on the device
- View errors clearly β great for beginners
- Free, cross-platform β Windows Β· Mac Β· Linux
Initial setup
1 Open Thonny β Tools β Options β Interpreter
2 Select MicroPython (Raspberry Pi Pico) from the dropdown
3 Select the correct COM port / USB serial device β the Pico W appears automatically when plugged in
The REPL shows >>> when connected. Type print("hello") to confirm.
REPL vs Scripts β Two Modes of Working
REPL (shell)
- ReadβEvalβPrint Loop
- Type one line β runs immediately
- Great for testing ideas, checking values
- Lost when you disconnect or reset
>>> print("hello")
hello >>> 2 + 2 4
Script file (.py)
- Saved to the Pico as
main.py - Runs automatically every time the Pico powers on
- This is how our deployed sensor works
- Save early, save often β click the green βΆ to run
Key rule: The file must be named main.py for it to auto-run on boot. Everything else can have any name.
π
Part 2
MicroPython Fundamentals
Variables Β· Types Β· Loops Β· Functions
Variables & Data Types
# Integers count = 10 pin_number = 4 # Floats temperature = 22.5 humidity = 55.1 # Strings unit = "sensor_A" location = "car park" # Booleans wifi_connected = True logging = False
Key rules
- No need to declare a type β Python infers it
- Use
type(x) to check: <class 'float'> - Snake_case for variable names:
my_variable - Variable names are case-sensitive:
Temp β temp
Python convention: Comments start with #. Write them constantly β your future self will thank you.
Sensor readings come back as floats β temperatures like 22.453125. We'll round these for display.
Loops β The Sensor's Heartbeat
The while True loop
import time count = 0 while True:
count += 1 print(f"Reading {count}")
time.sleep(10)
# β Runs forever β every 10 seconds
Stop a running loop: Press Ctrl+C in the Thonny shell. This sends a KeyboardInterrupt.
Also useful: for loops
# Repeat a fixed number of times for i in range(5):
print(f"Trial {i}")
# Loop over a list of sites sites = ["car_park", "courtyard"]
for site in sites:
print(site)
- Sensors use while True β run indefinitely
- for loops are useful in data analysis scripts
- Indentation is mandatory β 4 spaces, not tabs
Functions β Reusable Code Blocks
def read_sensor(sensor):
"""Read and return all four values.""" t = sensor.temperature
h = sensor.humidity
p = sensor.pressure
g = sensor.gas
return t, h, p, g # Call it temp, hum, pres, gas = read_sensor(sensor)
print(f"Temp: {temp:.1f}Β°C")
Why bother?
- Write once, use many times
- Makes your main loop clean and readable
- Easier to debug β test each function separately
def keyword + name + (parameters) + colon - Docstrings β the triple-quoted string β document what it does
{temp:.1f} β f-string formatting: .1f means 1 decimal place as a float. .0f = round to integer.
π‘οΈ
Part 3
Reading the BME680
Live data from your sensor
Installing the BME680 Library
What we need
- Bosch provides a driver, but we use a MicroPython port
- Library:
bme680.py by Robert Hammelrath - Wraps the raw I2C commands into simple Python properties
1 Download bme680.py from the shared project folder
2 In Thonny: File β Open β This Computer β select bme680.py
3 File β Save copy β MicroPython device to upload it to the Pico's flash
Verify it's there
>>> import os >>> os.listdir()
['main.py', 'bme680.py']
If bme680.py appears in the list, the library is on the device and ready to import.
Alternative: Use Thonny's Manage packages with micropython-bme680 if your Pico has internet access.
Your First Sensor Reading Script
from machine import I2C, Pin import bme680, time # Set up I2C on bus 0 i2c = I2C(0, sda=Pin(4), scl=Pin(5))
sensor = bme680.BME680_I2C(i2c)
# Read loop β every 10 seconds while True:
temp = sensor.temperature
hum = sensor.humidity
pres = sensor.pressure
gas = sensor.gas
print(f"Temp: {temp:.1f}Β°C " f"Hum: {hum:.0f}% " f"Pres: {pres:.0f} hPa " f"IAQ: {gas}")
time.sleep(10)
Line by line
from machine import β built-in Pico hardware library I2C(0, sda=Pin(4), scl=Pin(5)) β creates the I2C bus on our wired pins BME680_I2C(i2c) β wraps the sensor in a Python object .temperature etc. β properties return live float values f"..." β f-string formats numbers into readable text time.sleep(10) β pause 10 seconds before next read
The gas reading is raw resistance in ohms β higher = cleaner air. We'll convert to IAQ index in Session 05.
Type, run, observe, modify β in that order.
1 (5 min) Type the sensor script above into a new Thonny file. Save it to the Pico as main.py. Click βΆ and confirm you see readings in the shell.
2 (5 min) Change time.sleep(10) to time.sleep(2). Re-run. Breathe on the sensor β do you see humidity rise? Cup your hand over it β does temperature change?
3 (10 min) Add a reading_count variable that increments each loop. Print it alongside the sensor values: "Reading 1 | Temp: 22.1Β°Cβ¦"
4 (5 min) Wrap the sensor reading into a read_sensor() function. Call it from the main loop. Compare: is your code cleaner?
π‘ If you get a ValueError: No I2C device found, check your wiring β the sensor isn't responding. Re-run the I2C scan from Session 02.
π
Part 4
Formatting Outputs
Making data readable for humans and machines
f-Strings & Number Formatting
temp = 22.453125 hum = 54.812 # Basic f-string print(f"Temp is {temp}")
# β Temp is 22.453125 # 1 decimal place print(f"Temp: {temp:.1f}Β°C")
# β Temp: 22.5Β°C # 0 decimal places (whole number) print(f"Hum: {hum:.0f}%")
# β Hum: 55% # Padding to align columns print(f"T:{temp:6.1f} H:{hum:5.1f}")
# β T: 22.5 H: 54.8
Format spec breakdown
:.1f
1 digit after decimal, as a float
:.0f
0 decimal places β rounds the float to integer display
:6.1f
Total width 6 characters, 1 decimal β pads with spaces for aligned columns
For CSV output in Session 04, we'll swap the human format for {temp:.2f} β more precision for data analysis.
Adding a Timestamp
Every data point needs to know when it was collected
import time # time.time() = seconds since 2000-01-01 # (UTC, no RTC β use for relative time) t0 = time.time()
time.sleep(10)
elapsed = time.time() - t0 print(f"Elapsed: {elapsed:.0f} s")
# localtime() returns a tuple: # (year, month, day, hour, min, sec, β¦) now = time.localtime()
print(f"{now[0]}-{now[1]:02d}-{now[2]:02d} " f"{now[3]:02d}:{now[4]:02d}:{now[5]:02d}")
Why this matters
- Without a timestamp, data is just a list of numbers
- Time enables trend analysis, peak detection, and correlation
- The Pico W doesn't have a real-time clock (RTC) built in
- When connected to Wi-Fi, we'll sync time using NTP (Session 04)
- For local testing, elapsed seconds are sufficient
:02d pads a number to 2 digits with a leading zero β so 9 becomes 09. Essential for sortable timestamps.
Coming Up β Session 04
Date TBC Β· Data Logging & Transmission
What we'll cover
Writing sensor readings to a CSV file on the Pico, connecting to Wi-Fi, syncing time via NTP, and sending data to a remote server via HTTP POST β making your sensor a live data source.
For next time: Keep your main.py from today. We'll extend it to write to a CSV file first, then to the web.
Stretch task: Try adding a simple if condition β e.g. print a warning if temp > 27.
Questions? Get in touch:
jwilliams.science Β· HalesAir Project