Interactive Animations and Development Tools
Summary
Connects input and animation by implementing mode-based, button-triggered, photo-reactive, and sensor-driven animations, then covers the complete development toolchain: Git version control, GitHub repositories, Thonny IDE deep features, firmware flashing workflows, and the config.py hardware abstraction pattern.
Concepts Covered
This chapter covers the following 40 concepts from the learning graph:
- Multi-Pattern Program
- Pattern Switching Logic
- Animation State Variable
- Photo-Reactive Animation
- Button-Triggered Animation
- Sensor-Driven Animation
- Mode-Based Animation
- Animation Restart Logic
- Interactive Mode Controller
- Input Abstraction Layer
- Thonny IDE Overview
- Thonny REPL Panel
- Thonny Run Controls
- Syntax Highlighting
- Code Autocomplete
- Serial Console in Thonny
- REPL Interactive Testing
- File Upload Workflow
- Running Script on Pico
- Git Version Control
- git clone Command
- git add Command
- git commit Command
- git push Command
- git pull Command
- git status Command
- GitHub Repository
- Repository Structure
- README File Creation
- File Organization in Repo
- Firmware Flashing Workflow
- UF2 Drag-and-Drop Process
- Python Module Paths
- config.py Module Pattern
- Hardware Abstraction Layer
- Cross-Kit Compatibility
- Thonny Package Manager
- MicroPython Standard Library
- Code Backup with GitHub
- Commit Message Writing
Prerequisites
This chapter builds on concepts from:
- Chapter 1: Introduction and Computational Thinking Foundations
- Chapter 2: Python Basics: Variables, Data Types, and Operators
- Chapter 3: Python Functions, Modules, and Programming Best Practices
- Chapter 4: Python Control Flow, Loops, and Error Handling
- Chapter 5: Raspberry Pi Pico Hardware and Development Environment
- Chapter 6: MicroPython APIs, GPIO Control, and Electrical Fundamentals
- Chapter 9: NeoPixel Programming: Pixels, Colors, and the NeoPixel Library
- Chapter 12: Basic LED Animation Patterns
- Chapter 13: Intermediate Animation Techniques
- Chapter 14: Advanced Animation Patterns: Comet, Scanner, and Candle
- Chapter 18: Input Devices, Sensors, and State Machines
Pixel says...
Welcome to Chapter 19! This is where everything connects. You have animations from Chapters 12–14, and inputs from Chapter 18. Now you'll wire them together into programs people can actually interact with — then learn the pro tools to save, share, and version your work. This is the full-stack maker chapter. Let's light this up!
What You'll Learn
By the end of this chapter, you'll be able to:
- Build a complete multi-mode interactive animation program with button cycling
- Implement photo-reactive and sensor-driven animations
- Use Thonny's REPL, autocomplete, package manager, and console features
- Use the six core Git commands to save and manage your code
- Create and organize a GitHub repository for your projects
- Apply the
config.pyhardware abstraction pattern for cross-kit compatibility - Flash new firmware onto the Pico using the UF2 drag-and-drop process
What You'll Need
- Raspberry Pi Pico with NeoPixel strip connected
- Thonny IDE installed and connected
- A momentary push button wired with debouncing (from Chapter 18)
- Optional: photoresistor for sensor-driven animations
- A GitHub account (free at github.com)
- Git installed on your computer
Multi-Pattern Programs
A multi-pattern program is one that can run several different animations, switching between them based on user input. Before Chapter 18, your programs ran one animation forever. Now you can build programs where every button press changes the experience.
Before reading the design, here are two key terms: - Animation state variable — an integer that tracks which animation is currently active - Pattern switching logic — the code that reads the current state and calls the right animation function
Mode-Based Animation is the design pattern that holds it all together:
- Each animation gets a unique mode number (0, 1, 2 …)
- A button press increments the mode (with wrap-around)
- The main loop calls the animation function for the current mode
- Each animation function runs for one frame and returns
Animation Restart Logic
When the mode changes, many animations need to reset their state. Without this, a comet tail animation might start mid-trail when you switch into it — which looks glitchy.
Animation Restart Logic resets all animation-specific variables when the mode changes:
import machine, utime, math, neopixel
import config
strip = neopixel.NeoPixel(machine.Pin(config.NEOPIXEL_PIN), config.NUMBER_PIXELS)
# ── Button setup ──────────────────────────────────────────────────
btn = machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_UP)
prev_btn = 1
last_press = 0
DEBOUNCE_MS = 30
NUM_MODES = 4
mode = 0
prev_mode = -1 # forces a restart on first loop iteration
# ── Per-animation state ───────────────────────────────────────────
offset = 0 # rainbow offset
scan_pos = 0.0 # scanner position
scan_dir = 1.0
trail = [0.0] * config.NUMBER_PIXELS # comet trail
breath_t = 0.0 # breathing phase
def restart_animation(m):
global offset, scan_pos, scan_dir, trail, breath_t
offset = 0
scan_pos = 0.0
scan_dir = 1.0
trail = [0.0] * config.NUMBER_PIXELS
breath_t = 0.0
The restart_animation function initializes everything back to a known clean state whenever the mode changes.
Pattern Switching Logic
Before reading the complete loop, here's how the switching works:
- Check if
mode != prev_mode— if so, callrestart_animation()and record the newprev_mode - Check the button — if pressed, increment
mode - Run one frame of the current animation function
def frame_rainbow():
global offset
for i in range(config.NUMBER_PIXELS):
hue = int((i * 256 / config.NUMBER_PIXELS + offset) % 256)
# Simple hue-to-RGB (wheel function from Chapter 10)
if hue < 85:
strip[i] = (255 - hue * 3, hue * 3, 0)
elif hue < 170:
hue -= 85
strip[i] = (0, 255 - hue * 3, hue * 3)
else:
hue -= 170
strip[i] = (hue * 3, 0, 255 - hue * 3)
strip.write()
offset = (offset + 2) % 256
def frame_scanner():
global scan_pos, scan_dir
for i in range(config.NUMBER_PIXELS):
d = abs(i - scan_pos)
b = max(0, int((1 - d / 3) * 220)) if d < 3 else 0
strip[i] = (b, 0, b // 2)
strip.write()
scan_pos += scan_dir * 0.6
if scan_pos >= config.NUMBER_PIXELS - 1 or scan_pos <= 0:
scan_dir *= -1
def frame_comet():
global trail
for i in range(config.NUMBER_PIXELS):
trail[i] *= 0.82
pos = int(utime.ticks_ms() / 30) % config.NUMBER_PIXELS
trail[pos] = 1.0
for i in range(config.NUMBER_PIXELS):
b = int(trail[i] * 200)
strip[i] = (b, int(b * 0.4), 0)
strip.write()
def frame_breathe():
global breath_t
level = (math.sin(breath_t) + 1) / 2
for i in range(config.NUMBER_PIXELS):
strip[i] = (int(level * 80), int(level * 30), int(level * 200))
strip.write()
breath_t += 0.04
FRAMES = [frame_rainbow, frame_scanner, frame_comet, frame_breathe]
# ── Main loop ─────────────────────────────────────────────────────
while True:
# Restart on mode change
if mode != prev_mode:
restart_animation(mode)
prev_mode = mode
# Read button
state = btn.value()
if state == 0 and prev_btn == 1:
if utime.ticks_ms() - last_press > DEBOUNCE_MS:
mode = (mode + 1) % NUM_MODES
last_press = utime.ticks_ms()
prev_btn = state
# Run one frame
FRAMES[mode]()
utime.sleep_ms(20)
You should see a moving rainbow, then a scanner, then a comet tail, then breathing color — one button press per switch. Each animation starts cleanly from its initial state when you enter it.
Input Abstraction Layer
An Input Abstraction Layer is a helper module that handles all the low-level input details (pin setup, debouncing, state tracking) so the main program can just call button_pressed() without thinking about those details.
Before the code, here's the value: if you ever change which GPIO pin your button is on, you update one line in the abstraction layer — not every place in the main program that checks the button.
# input_helpers.py — save this file to the Pico alongside main.py
import machine, utime
class Button:
def __init__(self, pin_num, debounce_ms=30):
self.pin = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_UP)
self.debounce_ms = debounce_ms
self._prev = 1
self._last_press = 0
def just_pressed(self):
state = self.pin.value()
pressed = (state == 0 and self._prev == 1 and
utime.ticks_ms() - self._last_press > self.debounce_ms)
if pressed:
self._last_press = utime.ticks_ms()
self._prev = state
return pressed
With this class, btn.just_pressed() returns True exactly once per button press. Your main loop becomes clean:
from input_helpers import Button
btn = Button(15)
while True:
if btn.just_pressed():
mode = (mode + 1) % NUM_MODES
FRAMES[mode]()
utime.sleep_ms(20)
Interactive Mode Controller
The Interactive Mode Controller is the fully assembled program structure that manages: - Multiple animation patterns - Button cycling - Photo-reactive or sensor-driven override - Animation restart on mode change
Photo-Reactive Animation: the ADC reads the photoresistor and maps it to a brightness parameter that all animations use. In bright room conditions, animations run dimmer; in dark rooms, they run brighter.
ldr = machine.ADC(26)
def get_brightness():
raw = ldr.read_u16()
# Map 20000–55000 to 60–220 brightness (inverted: darker = brighter LEDs)
clamped = max(20000, min(55000, raw))
return int(220 - (clamped - 20000) / 35000 * 160)
Sensor-Driven Animation: instead of a fixed color, the animation hue or speed responds to sensor values:
def frame_sensor_rainbow():
global offset
speed = int(ldr.read_u16() / 65535 * 6) + 1 # 1–7 pixels per frame
for i in range(config.NUMBER_PIXELS):
hue = int((i * 256 / config.NUMBER_PIXELS + offset) % 256)
# ... wheel function ...
strip.write()
offset = (offset + speed) % 256
Moving the photoresistor from dark to bright changes the rainbow speed from slow to fast.
Button-Triggered Animation: a specific mode that only animates while the button is held, then stops when released. Useful for a "flash" or "strobe" effect:
def frame_hold_to_flash():
if btn.pin.value() == 0: # held
for i in range(config.NUMBER_PIXELS):
strip[i] = (220, 220, 220)
else:
for i in range(config.NUMBER_PIXELS):
strip[i] = (0, 0, 0)
strip.write()
Pixel says...
The "frame function returns immediately" rule is what makes all of this work. If an animation used time.sleep() inside it, the button wouldn't respond while that sleep ran. Frame-based animation gives both smooth visuals AND fast input response.
Thonny IDE Deep Features
You've been using Thonny since Chapter 5. Now let's explore every feature it provides.
Thonny REPL Panel
The Thonny REPL Panel (also called the Shell) is the interactive Python console at the bottom of the Thonny window. REPL stands for Read–Eval–Print Loop.
REPL Interactive Testing means typing Python expressions directly and seeing the result immediately — without writing a full file. This is ideal for hardware debugging:
>>> import machine
>>> machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_UP).value()
1
If you press the button while the cursor is after the parenthesis and press Enter again, you'll see 0 — confirming the button is wired correctly.
The REPL is also useful for testing math formulas, checking color values, and exploring the neopixel module without running a full program.
Thonny Run Controls
The Thonny toolbar has four key run controls:
| Control | What it does |
|---|---|
| Run (F5) | Runs the current file on the connected device |
| Stop (F2) | Stops the running program; returns to REPL prompt |
| Debug | Steps through code line by line |
| Open REPL | Opens a fresh REPL session |
Serial Console in Thonny: the Shell panel doubles as a serial console — any print() calls in your Pico code appear here in real time. This is your primary debugging tool.
Syntax Highlighting and Code Autocomplete
Syntax Highlighting colors different parts of your code automatically: keywords are one color, strings another, comments another. This makes errors easier to spot — a string that doesn't end will immediately show as a wrong color.
Code Autocomplete appears when you press Tab after typing part of an identifier. Type machine. and press Tab — a list of all available attributes appears. This speeds up writing and reduces spelling mistakes.
Thonny Package Manager
The Thonny Package Manager installs Python packages to the connected Pico. Open it from the Tools menu → Manage packages.
The MicroPython Standard Library is built into MicroPython and does not need to be installed:
machine— hardware control (Pin, ADC, PWM, I2C, SPI)utime— time functions (sleep, ticks_ms)neopixel— NeoPixel drivermath— math functions (sin, cos, sqrt, pi)urandom— random numbersujson— JSON encoding/decodinguos— filesystem operations
Packages NOT included in the standard MicroPython firmware must be installed via the package manager. For example, umqtt.simple (for MQTT messaging) must be installed before import.
File Upload Workflow
File Upload Workflow describes how a .py file moves from your computer to the Pico:
- Write and test your code in Thonny with the file open locally
- Use File → Save as → select "Raspberry Pi Pico" as the destination
- Save as
main.py(Pico runs this automatically on power-up) or any other name - Click Run (F5) to execute it immediately
Running Script on Pico: if a script is saved as main.py on the Pico, it runs every time the Pico receives power — even without a USB connection. This is how you make a standalone costume project that starts automatically when you plug in the battery.
A typical file organization on the Pico:
/ (root of Pico filesystem)
├── main.py ← runs automatically on power-up
├── config.py ← hardware settings
└── input_helpers.py ← button abstraction class
Python Module Paths: MicroPython searches for imported modules in the root of the Pico filesystem. If you import config, MicroPython looks for config.py in /. Place helper files in the root directory alongside main.py.
The config.py Module Pattern
The config.py Module Pattern is a file named config.py that stores hardware-specific settings in one place. Every other file imports from config instead of using hard-coded values.
Hardware Abstraction Layer — the config.py file acts as an abstraction layer between your animation code (which doesn't care which pin is used) and the hardware (which requires a specific pin).
A typical config.py for this course:
# config.py — hardware configuration for this kit
NEOPIXEL_PIN = 0 # GP0 → data line to NeoPixel strip
NUMBER_PIXELS = 30 # number of LEDs in the strip
BRIGHTNESS_LIMIT = 0.4 # maximum brightness (0.0–1.0) to save power
BTN_PIN = 15 # GP15 → button input
LDR_PIN = 26 # ADC0 (GP26) → photoresistor
Every program file starts with:
import config
strip = neopixel.NeoPixel(machine.Pin(config.NEOPIXEL_PIN), config.NUMBER_PIXELS)
Cross-Kit Compatibility: when a student borrows a kit with a different number of pixels or a different pin assignment, they only change config.py. All programs import from config, so they all adapt automatically.
If you compare this to the alternative — hard-coding 30 as the number of pixels in every program file — the advantage is clear. Changing kits would require editing every file.
Git Version Control
Git Version Control is software that tracks changes to your code files over time. Every time you make a meaningful change and "commit" it, Git saves a snapshot. You can compare snapshots, go back to an earlier version, and collaborate with others.
The following six commands cover 90% of everyday Git use.
git status Command
git status shows which files have been changed, added, or are not yet tracked. Always run this first before doing anything else.
$ git status
On branch main
Changes not staged for commit:
modified: main.py
Untracked files:
input_helpers.py
git add Command
git add stages a file for the next commit. "Staging" means marking it as "include this change in the next snapshot."
git add main.py input_helpers.py
Or to add every changed file at once:
git add .
git commit Command
git commit saves a permanent snapshot of all staged files. The -m flag lets you write a Commit Message on the same line.
git commit -m "Add button input abstraction and four-mode animation"
Commit Message Writing: a good commit message answers "what changed and why in one sentence." Use the imperative mood ("Add", "Fix", "Remove", not "Added", "Fixed", "Removed"). A reader should be able to understand the purpose of the snapshot without reading the code diff.
git push Command
git push uploads your local commits to GitHub. After committing, push to save your work remotely:
git push
If this is the first push on a new branch, you may need:
git push -u origin main
git pull Command
git pull downloads commits that exist on GitHub but not yet on your computer. Run this when you've made changes on another computer (like the school lab) and want them locally:
git pull
git clone Command
git clone downloads an entire repository from GitHub to your computer for the first time:
git clone https://github.com/your-username/moving-rainbow-projects.git
After cloning, Git automatically sets up the link to GitHub, so push and pull work immediately.
GitHub Repository
A GitHub Repository is a cloud-hosted home for a project's code, tracked by Git. GitHub adds a web interface, collaboration tools, and issue tracking on top of Git.
Repository Structure
Before creating your repository, plan a sensible file organization structure. A clear layout helps others (and future-you) understand the project at a glance.
Recommended structure for your Moving Rainbow repository:
moving-rainbow-projects/
├── README.md ← project overview (required!)
├── config.py ← hardware configuration
├── main.py ← the main program
├── input_helpers.py ← button/sensor helpers
├── animations/
│ ├── rainbow.py ← rainbow animation functions
│ ├── scanner.py ← scanner animation functions
│ └── comet.py ← comet animation functions
└── docs/
└── wiring.md ← wiring diagram and setup notes
README File Creation
A README file (always named README.md) appears on the GitHub repository front page. It should explain:
1. What the project does
2. What hardware it needs
3. How to install and run it
4. Any configuration steps
A minimal README.md:
# Moving Rainbow Project
An interactive NeoPixel LED animation controller using a Raspberry Pi Pico.
## Hardware needed
- Raspberry Pi Pico
- WS2812B LED strip (30 pixels)
- One momentary push button
## Setup
1. Copy `config.py` and `main.py` to the Pico using Thonny
2. Press the button to cycle between animation modes
Code Backup with GitHub: beyond collaboration, GitHub acts as an automatic off-site backup of your code. If your computer is lost, stolen, or damaged, you can git clone your repository to any other machine and continue working within minutes.
Diagram: Git Workflow
Interactive diagram: local ↔ GitHub file flow
Type: diagram sim-id: git-workflow-diagram Library: Mermaid Status: Specified
A Mermaid flowchart (left-to-right) with three columns:
Column 1 — Your computer (working files):
- "Edit code in Thonny" box
- Arrow down to "git add
Column 2 — Local Git repository: - Arrow from Staging Area labeled "git commit -m '...'" - "Local commit history" box with three stacked snapshots - Arrow up from "git pull" (from GitHub to local)
Column 3 — GitHub (cloud): - Arrow from Local labeled "git push" - "GitHub repository" box - Arrow down labeled "git clone" to "Another computer" (faded, showing the copy)
Each action box is clickable: clicking it shows a tooltip with the exact command syntax and what it does.
Canvas: 700 × 360 px. Responds to window resize.
Learning objective: Understanding — the student can describe the three locations where Git stores data (working directory, local repo, GitHub) and which command moves data between them.
Firmware Flashing Workflow
Firmware flashing replaces the software that runs permanently inside the Pico's flash memory. You flash new firmware when: - You want to upgrade to a newer version of MicroPython - The Pico is stuck and not responding to Thonny - You want to switch to CircuitPython (a different flavor of Python)
The UF2 Drag-and-Drop Process for flashing MicroPython onto a Pico:
- Download the latest
rp2-pico-*.uf2file from the MicroPython website - Hold the BOOTSEL button on the Pico while plugging in the USB cable
- Release BOOTSEL after the cable is plugged in — the Pico appears as a USB drive named
RPI-RP2 - Drag and drop the
.uf2file onto theRPI-RP2drive - The Pico restarts automatically and MicroPython is now running
The entire process takes about 30 seconds. After flashing, reconnect Thonny by selecting the port in the bottom-right corner of the Thonny window.
Watch out!
Flashing new firmware erases everything stored on the Pico's filesystem — including main.py, config.py, and any helper files. Always push your code to GitHub before reflashing, so you can restore it with git clone afterward. Losing a half-finished project to an accidental reflash is a real risk.
Try It Yourself
-
Four-mode controller: Build the complete four-mode interactive controller from this chapter. Test all four modes and verify that each animation starts from a clean state when you enter it.
-
Config swap: Change
config.NUMBER_PIXELSinconfig.pyto half your actual pixel count. Observe how all animations automatically adapt. Then restore the original value. -
First Git commit: Create a GitHub repository called
moving-rainbow, add yourmain.pyandconfig.py, write a README, and make your first commit and push. -
Sensor speed control: Add a photoresistor to your kit and modify the rainbow animation to change speed based on light level. Push the updated code to GitHub with a meaningful commit message.
Check Your Understanding
- What is an animation state variable and why is it needed?
- What does animation restart logic do and when does it run?
- What is the purpose of the Thonny REPL? Give one example of something you would test there.
- List the six core Git commands in order, and explain what each one does.
- What is the difference between
git commitandgit push? - What is the
config.pypattern and why does it enable cross-kit compatibility? - When would you flash new MicroPython firmware onto the Pico?
Chapter complete!
You've graduated from writing single-pattern programs to building complete interactive systems — with Git, GitHub, and the full pro development toolchain to back them up. Every skill you learned in Chapters 1–18 now has a professional workflow wrapped around it. This is how real engineers work. You've earned it!
What's Next
In Chapter 20, you'll plan and build a complete capstone project from scratch — applying every skill in this book to create something uniquely yours, document it, and present it to an audience.