Session 1: Designing Task-Oriented Application Architectures
Synopsis
Presents ways to divide an embedded application into cooperating services, workers, and supervisors with clear ownership of resources and responsibilities.
Session Content
Session 1: Designing Task-Oriented Application Architectures
Session Overview
Duration: ~45 minutes
Audience: Python developers with basic programming knowledge
Platform: Raspberry Pi Pico 2 W
Firmware: MicroPython
IDE: Thonny
Learning Outcomes
By the end of this session, learners will be able to:
- Explain what a task-oriented application architecture is.
- Identify application tasks and split them into cooperative units.
- Design simple event-driven flows for embedded systems.
- Use uasyncio to structure concurrent tasks in MicroPython.
- Build a basic Pico 2 W application with parallel task behavior.
1) Theory: Why Task-Oriented Architecture Matters
1.1 Embedded systems are often task-driven
On the Pico 2 W, applications often need to: - Read sensors periodically - Control LEDs or motors - Respond to buttons or interrupts - Maintain Wi-Fi connectivity - Communicate over the network - Avoid blocking other activities
A task-oriented architecture organizes these responsibilities as separate, cooperative tasks rather than one long blocking program.
1.2 Benefits
- Responsiveness: The device can react quickly to inputs.
- Maintainability: Each task has a clear purpose.
- Scalability: New features can be added without rewriting everything.
- Predictability: Timing and periodic work are easier to manage.
1.3 Common architectural styles for MicroPython IoT
- Polling loop: Simple but can become blocking.
- State machine: Good for sequential device behavior.
- Event-driven: Reacts to signals, timers, or interrupts.
- Task-oriented with
uasyncio: Best for multiple cooperative activities.
1.4 What tasks look like in Pico projects
Examples:
- blink_task() for an LED status indicator
- read_sensor_task() for periodic readings
- button_task() for button monitoring
- wifi_task() for connectivity management
- publish_task() for sending data to a cloud service
2) Core Concepts
2.1 Blocking vs non-blocking behavior
A blocking delay like time.sleep(2) pauses the entire program.
With asynchronous programming:
- Tasks yield control using await asyncio.sleep(...)
- Other tasks continue running while one task waits
2.2 Cooperative multitasking
uasyncio does not preempt tasks. Each task must:
- Do a small amount of work
- Yield control regularly
- Avoid long blocking calls
2.3 Task communication
Tasks can communicate through:
- Shared variables
- uasyncio.Event
- uasyncio.Queue
For this session, we'll start with: - Shared state - Simple task separation
3) Development Environment Setup
3.1 Install Thonny
- Install Thonny IDE on your computer.
- Connect the Raspberry Pi Pico 2 W via USB.
- Open Thonny.
- In the bottom-right interpreter selector, choose:
- MicroPython (Raspberry Pi Pico)
3.2 Flash MicroPython firmware
- Hold the BOOTSEL button on the Pico 2 W.
- Plug it into USB.
- It appears as a USB storage device.
- Copy the latest Pico MicroPython UF2 firmware to the device.
- The board reboots automatically.
3.3 Verify connection in Thonny
Open the Shell and run:
import sys
print(sys.platform)
Expected output:
rp2
3.4 Recommended project files
For this session, create:
- main.py — application entry point
- lib/ — optional folder for helper modules in future sessions
4) Architecture Design: A Simple Task Model
4.1 Example application
We will design a small system with: - A blinking onboard LED as a heartbeat - A simulated sensor task - A status task that reports system activity
4.2 Task responsibilities
- LED task: Shows the board is alive
- Sensor task: Produces periodic readings
- Logger task: Prints summary data
4.3 Design principle
Each task should: - Have one job - Run independently - Yield often - Not assume other tasks are synchronous
5) Hands-On Exercise 1: Cooperative LED and Status Tasks
5.1 Goal
Build a MicroPython program that runs two asynchronous tasks: - Blink the onboard LED every second - Print a status message every 3 seconds
5.2 Wiring
No external wiring required.
Use the onboard LED on the Pico 2 W.
5.3 Code: main.py
# main.py
# Session 1: Task-Oriented Application Architectures
# Raspberry Pi Pico 2 W + MicroPython + uasyncio
import uasyncio as asyncio
from machine import Pin
# Onboard LED on most Pico boards
led = Pin("LED", Pin.OUT)
async def blink_task():
"""Blink the onboard LED once per second."""
while True:
led.toggle()
print("LED:", "ON" if led.value() else "OFF")
await asyncio.sleep(1)
async def status_task():
"""Print a periodic status message."""
counter = 0
while True:
counter += 1
print("Status task running. Tick:", counter)
await asyncio.sleep(3)
async def main():
"""Run all application tasks concurrently."""
print("Starting task-oriented application...")
await asyncio.gather(
blink_task(),
status_task(),
)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
5.4 Expected output
Example Shell output:
Starting task-oriented application...
LED: ON
Status task running. Tick: 1
LED: OFF
LED: ON
LED: OFF
Status task running. Tick: 2
LED: ON
5.5 Discussion
blink_task()andstatus_task()run concurrently.- Neither task blocks the other.
await asyncio.sleep(...)gives control back to the scheduler.
6) Hands-On Exercise 2: Adding a Simulated Sensor Task
6.1 Goal
Extend the architecture with a third task that simulates a sensor reading.
6.2 Code: main.py
# main.py
# Task-oriented architecture with three cooperative tasks
import uasyncio as asyncio
from machine import Pin
import random
led = Pin("LED", Pin.OUT)
async def blink_task():
"""Blink the onboard LED every second."""
while True:
led.toggle()
print("LED:", "ON" if led.value() else "OFF")
await asyncio.sleep(1)
async def status_task():
"""Print application health every 3 seconds."""
counter = 0
while True:
counter += 1
print("Status task tick:", counter)
await asyncio.sleep(3)
async def sensor_task():
"""Simulate a periodic sensor reading."""
while True:
reading = random.randint(20, 35)
print("Sensor reading:", reading, "°C")
await asyncio.sleep(2)
async def main():
"""Run all tasks concurrently."""
print("Starting application with sensor simulation...")
await asyncio.gather(
blink_task(),
status_task(),
sensor_task(),
)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
6.3 Expected output
Starting application with sensor simulation...
LED: ON
Sensor reading: 27 °C
Status task tick: 1
LED: OFF
Sensor reading: 29 °C
LED: ON
LED: OFF
Status task tick: 2
Sensor reading: 24 °C
6.4 What this demonstrates
- Independent timing for each task
- Shared execution without threads
- A foundation for real sensor integration
7) Hands-On Exercise 3: Replace Simulation with a Real Button Task
7.1 Goal
Add a button to control application behavior through a task.
7.2 Required hardware
- 1 push button
- 1 breadboard
- 2 jumper wires
7.3 Wiring
Connect: - One side of the button to GND - The other side to GP15
We will use the internal pull-up resistor.
7.4 Code: main.py
# main.py
# Task-oriented architecture with a button input
import uasyncio as asyncio
from machine import Pin
led = Pin("LED", Pin.OUT)
button = Pin(15, Pin.IN, Pin.PULL_UP)
async def blink_task():
"""Blink the onboard LED every second."""
while True:
led.toggle()
await asyncio.sleep(1)
async def button_task():
"""Monitor the button and report press/release events."""
last_state = button.value()
while True:
current_state = button.value()
if current_state != last_state:
if current_state == 0:
print("Button pressed")
else:
print("Button released")
last_state = current_state
await asyncio.sleep(0.05)
async def main():
"""Run the LED and button tasks together."""
print("Starting button monitoring...")
await asyncio.gather(
blink_task(),
button_task(),
)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
7.5 Expected output
Starting button monitoring...
Button pressed
Button released
Button pressed
7.6 Learning points
- Polling can still be cooperative if the loop yields often.
- Task architecture makes it easy to add input handling.
- Debouncing can be added later if needed.
8) Design Exercise: Architect a Weather Station Task Set
8.1 Scenario
Design a Pico 2 W weather station that: - Reads temperature every 10 seconds - Updates an OLED display every 1 second - Sends data to the cloud every 60 seconds - Blinks an LED while Wi-Fi is connected
8.2 Suggested task breakdown
read_sensor_task()display_task()network_task()publish_task()heartbeat_task()
8.3 Questions to answer
- Which task should run most frequently?
- Which task can tolerate delay?
- Which task must never block the others?
- What shared state will tasks need?
8.4 Example task responsibilities
- Sensor task writes latest value to shared data
- Display task reads the latest value and refreshes screen
- Publish task sends stored readings
- Heartbeat task indicates network status
9) Best Practices for Task-Oriented MicroPython Apps
9.1 Keep tasks small
Each task should do one thing well.
9.2 Avoid blocking calls
Avoid long sleep() calls in tasks. Prefer:
- await asyncio.sleep(...)
- Short periodic checks
9.3 Use clear naming
Examples:
- wifi_connect_task
- sensor_read_task
- mqtt_publish_task
9.4 Define shared state carefully
Use simple data structures like dictionaries:
app_state = {
"temperature": None,
"wifi_connected": False,
}
9.5 Always handle cleanup
Use:
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
10) Quick Knowledge Check
10.1 Questions
- Why is
time.sleep()problematic in multi-task embedded applications? - What does
await asyncio.sleep()do? - What is cooperative multitasking?
- Why should each task have a single responsibility?
- What kinds of tasks would a Pico 2 W IoT device commonly need?
10.2 Model answers
- It blocks the entire program.
- It pauses the current task and lets others run.
- Tasks share CPU time by voluntarily yielding.
- It improves maintainability and clarity.
- Sensor reading, connectivity, UI updates, publishing, input handling.
11) Session Summary
In this session, you learned how to:
- Think in tasks instead of a single blocking loop
- Structure embedded applications around responsibilities
- Use uasyncio to run concurrent behavior on the Pico 2 W
- Build a foundation for responsive IoT applications
12) Stretch Goal
Create a program with: - One task reading a sensor - One task blinking an LED based on sensor threshold - One task printing an alert when a value exceeds a limit
Example logic: - Temperature above 30°C → LED blinks faster - Temperature below 25°C → LED blinks slowly - Alert message printed when threshold is crossed
13) Reference
- MicroPython Wiki: https://github.com/micropython/micropython/wiki
- MicroPython Quick Reference: https://docs.micropython.org/en/latest/rp2/quickref.html
- Raspberry Pi MicroPython Documentation: https://www.raspberrypi.com/documentation/microcontrollers/micropython.html
- Raspberry Pi Pico Series Documentation: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#pico2