Fab Academy 2026  ·  Week 15

Interface &
Application
Programming

This week I bridged the physical and digital worlds — programming a XIAO ESP32-C3 to talk to a custom Python GUI over serial, building a real-time LED control interface with Tkinter and pySerial, and comparing GUI development tools from block-based to code-first.

XIAO ESP32-C3 Python Tkinter pySerial Serial USB MIT App Inventor
XIAO ESP32-C3 board Python GUI interface Interface in action

Group Assignment

  1. Compare as many GUI tool options as possible

Individual Assignment

  1. Write an application for the embedded board that you made
  2. Interface a user with an input and/or output device(s)

Key Tools & Stack

  • XIAO ESP32-C3 — Microcontroller
  • Arduino IDE — Firmware (C++)
  • Spyder IDE — Python development
  • Tkinter — GUI library
  • pySerial — Serial communication
01
Theory

The Architecture of an Interface: Bridging Two Worlds

To master this week's assignment, I had to understand how the Physical World (my custom board) talks to the Digital World (my computer application). This communication happens through three essential layers that must work in perfect harmony.

Layer 1 — Language THE IDIOMA C++ / Arduino Runs on the microcontroller Python Runs on the computer Layer 2 — Protocol THE MESSAGE FORMAT Serial (USB) Continuous bit stream UDP / MQTT (Wi-Fi) Packaged envelopes wireless Layer 3 — Medium THE TRANSPORT USB Cable Direct stable connection Wi-Fi / Bluetooth Wireless mobility
Layer 1 — The Language

The code used to write the instructions. Two languages work as a team: C++ (Arduino) running on the XIAO ESP32C3, handling hardware inputs and outputs; and Python on the computer, creating the visual interface and processing data.

Layer 2 — The Protocol

The rules that define how data is organized so both devices understand each other. Serial sends data in a continuous stream; protocols like UDP or MQTT package information into wireless "envelopes".

Layer 3 — The Medium

The physical or wireless channel through which data travels. A USB cable provides a direct, stable connection; Wi-Fi/Bluetooth enables mobility using the ESP32C3's built-in wireless capabilities.

Project Goal: To create a workflow where these three layers act in harmony — the board detects a physical event, translates it into a compatible protocol, and sends it through the chosen medium so the Python interface can transform it into a visual experience for the user.
02
Individual Assignment

Communication Strategy & Hardware Setup

Upgrading to XIAO ESP32-C3

After experiencing limitations with my previous board's lack of networking capabilities in Week 11, I upgraded to the XIAO ESP32-C3 for Week 15. This microcontroller's integrated internet support provides the reliable connectivity my final project requires.

XIAO ESP32-C3 pinout diagram The assembled board with XIAO ESP32-C3
Compatibility note: This change did not present any issues, as the XIAO ESP32-C3 pin distribution is almost identical to the XIAO nRF52840, making it fully compatible with my existing board.
Three-Layer Communication Structure

To connect my hardware with the digital interface, I structured the communication into three layers, ensuring a stable link between the XIAO ESP32C3 and my computer.

XIAO ESP32-C3 C++ / Arduino 9600 baud USB-C Cable Serial Protocol Python Script pySerial reads COM port Sends 'H' or 'L' bytes Handles connection logic Tkinter renders Tkinter GUI TURN ON TURN OFF Port selector · Status label · Alerts
Language

C++ on the microcontroller, Python on the PC. Python was chosen for its efficiency in handling GUI libraries and data processing.

Protocol

Serial Communication at 9600 baud. Based on Week 11 logic, this ensures a synchronized, continuous data stream.

Medium

USB-C Cable. A wired connection was selected to eliminate latency, interference, and wireless pairing complexity.

Summary

Python + Serial + USB provides the most robust plug-and-play setup for real-time interaction between UI and hardware.

Technical Specs

ComponentDetail
MicrocontrollerXIAO ESP32-C3
Library (PC)pyserial
Library (GUI)Tkinter
ConnectionWired USB Serial @ 9600 baud
Output DeviceLED on Pin D7
Command Protocol'H' = LED ON, 'L' = LED OFF
03
Hardware & Firmware

The Board & Arduino Programming

Board History

For this week's assignment, I used the board made during Week 8: Electronics Production, which has serial communication pins enabled and an LED as output.

Board from Week 8 / Week 11 Board with XIAO ESP32-C3 integrated for Week 15
⚠️ Week 15 Troubleshooting: The board suffered several technical setbacks during testing and required multiple repairs. Due to university midterm exams and limited lab access, I could not mill a new board in time. A brand-new, optimized PCB will be fabricated in upcoming weeks.

Arduino Setup — Quick Guide

Quick Setup Guide: XIAO ESP32-C3 in Arduino IDE +
Step 1 Connect Hardware

Plug your board into the computer and ensure the COM3 port is recognized in Device Manager.

Step 2 Add Espressif URL

In File > Preferences, paste the official Espressif board manager URL into the "Additional Boards Manager URLs" field.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Step 3 Install ESP32 Support

Open the Boards Manager (left icon), search for ESP32, and click Install on the Espressif Systems package.

Step 4 Select the Board

Navigate to Tools > Board > esp32 and manually select XIAO_ESP32C3 from the list to enable the correct pin configuration.

Step 5 Upload & Verify

Select the correct COM port under Tools > Port and upload your sketch. The board's built-in LED will blink to confirm a successful flash.

Arduino Firmware

The board is configured to monitor the serial buffer for incoming data: an 'H' signal activates the GPIO pin, while an 'L' signal deactivates it.

// Week 15: Interface and Application Programming
// Board: XIAO ESP32-C3
// Component: LED on Pin D7

const int ledPin = D7;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    char incomingByte = Serial.read();

    if (incomingByte == 'H') {
      digitalWrite(ledPin, HIGH);   // Turn LED ON
      Serial.println("LED turned ON");
    } 
    else if (incomingByte == 'L') {
      digitalWrite(ledPin, LOW);    // Turn LED OFF
      Serial.println("LED turned OFF");
    }
  }
}
Receives 'H'

Sets GPIO D7 HIGH — the LED turns on. Sends confirmation message back over serial.

Receives 'L'

Sets GPIO D7 LOW — the LED turns off. Sends confirmation message back over serial.

04
Software Development

Python GUI — Spyder, Tkinter & pySerial

Why Python & Spyder?

I chose to work with Python because I was already familiar with it from university courses in data analysis and charting. For the IDE, I opted for Spyder — a Scientific Python Development Environment with integrated debugging, interactive execution, and library management.

I implemented the Spyder Standalone version to maintain a dedicated environment for hardware projects, keeping it independent from other Python installations on my system.

Spyder IDE interface overview
AI-assisted workflow: I used Gemini to assist with the code structure, always reviewing the output to ensure it integrated perfectly with my board's serial communication protocol.

Setting Up pySerial in Spyder

Prerequisites — Installing pySerial in Spyder Standalone +

If you are new to Spyder 5, certain libraries may not be pre-installed. First, verify if pySerial is available by running in the IPython Console:

try:
    import serial
    print("Library 'pyserial' is already installed!")
except ImportError:
    print("Library 'pyserial' NOT found.")

If you get a ModuleNotFoundError, force the installation directly into Spyder's internal path using this command in the IPython Console:

import subprocess
import sys

# Forces Spyder to install in its own executable folder
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyserial"])
⚠️ Crucial step: After installation completes, you must restart the kernel — select Restart kernel or press Ctrl + . — and click "Yes". Without this step, Spyder won't recognize the new package.

Confirm the installation succeeded with a clean verification:

import serial
print("Success! Library is ready to use.")
✅ If the console shows "Success! Library is ready to use." — you are good to go.
The Python GUI — Full Source Code

This code uses the Tkinter library for the graphical interface and PySerial to establish a robust communication link with the XIAO ESP32-C3 microcontroller.

import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports


# --- Window Configuration ---
root = tk.Tk()
root.title("Micaela's Interface - ESP32 Control")
root.geometry("400x300")
root.configure(bg='#f0f0f0')

arduino = None

# --- Control Functions ---
def connect_board():
    global arduino
    port = port_selector.get()
    try:
        arduino = serial.Serial(port, 9600, timeout=1)
        status_label.config(text=f"Status: Connected to {port}", foreground="green")
        messagebox.showinfo("Success", f"Connected to {port} successfully!")
    except Exception as e:
        status_label.config(text="Status: Connection Failed", foreground="red")
        messagebox.showerror("Error", f"Could not connect: {e}")

def send_signal(state):
    if arduino and arduino.is_open:
        arduino.write(state.encode())
        print(f"Signal sent: {state}")
    else:
        messagebox.showwarning("Warning", "Please connect to a port first!")

# --- UI Elements ---
style = ttk.Style()
style.configure("TButton", padding=6, font=('Helvetica', 10))

title_label = tk.Label(root, text="XIAO ESP32-C3 LED Control",
                       font=('Helvetica', 14, 'bold'), bg='#f0f0f0')
title_label.pack(pady=15)

# Port Selector
frame_port = tk.Frame(root, bg='#f0f0f0')
frame_port.pack(pady=10)

tk.Label(frame_port, text="Select Port:", bg='#f0f0f0').grid(row=0, column=0, padx=5)
available_ports = [p.device for p in serial.tools.list_ports.comports()]
port_selector = ttk.Combobox(frame_port, values=available_ports, width=15)
port_selector.grid(row=0, column=1, padx=5)
if available_ports: port_selector.current(0)

btn_connect = ttk.Button(frame_port, text="Connect", command=connect_board)
btn_connect.grid(row=0, column=2, padx=5)

# Action Buttons
btn_frame = tk.Frame(root, bg='#f0f0f0')
btn_frame.pack(pady=20)

btn_on = tk.Button(btn_frame, text="TURN ON", bg="#4CAF50", fg="white",
                   font=('Helvetica', 10, 'bold'), width=12, height=2,
                   command=lambda: send_signal('H'))
btn_on.grid(row=0, column=0, padx=10)

btn_off = tk.Button(btn_frame, text="TURN OFF", bg="#F44336", fg="white",
                    font=('Helvetica', 10, 'bold'), width=12, height=2,
                    command=lambda: send_signal('L'))
btn_off.grid(row=0, column=1, padx=10)

# Status Label
status_label = ttk.Label(root, text="Status: Disconnected",
                         font=('Helvetica', 9, 'italic'))
status_label.pack(side="bottom", pady=10)

root.mainloop()
Spyder IDE with the Python GUI code loaded

Key Features & Final Result

Technical Implementation
Feature 1 Dynamic Port Detection

I implemented a port detection system using serial.tools.list_ports, allowing users to select the active COM port through a ttk.Combobox dropdown that auto-populates all available ports on launch.

COM port selector dropdown in the GUI
Feature 2 Command Protocol

The interface sends encoded bytes ('H' for ON, 'L' for OFF) via a 9600 baud serial connection to toggle the board's digital pins. The board echoes back a confirmation message visible in the Spyder console.

Feature 3 User Experience & Error Handling

To ensure a robust, business-ready design, I added exception handling with visual pop-up alerts and clear color-coded buttons providing immediate visual feedback on connection status.

Successful connection popup alert Error handling popup alert
Final Result — Live Demo
⚠️ Critical Usage Notes
  • Serial Port Conflict: Ensure the Arduino Serial Monitor is closed before running the Python script; the COM port will appear as "busy" and the connection will fail.
  • Execution Order: Upload the Arduino code first. Once complete, run the Spyder script. They cannot share the serial port simultaneously.
  • Resetting the App: If you need to restart, close the GUI, verify the Arduino status, then run the Spyder code again to re-establish the link.
05
Group Assignment

GUI Tool Comparison — App Inventor vs Python

Full group documentation available on the official Fab Academy ULima page: Visit Fab Academy ULima →
↗ Andres Mamani's Documentation
Tool Overview
MIT App Inventor interface overview

MIT App Inventor is a visual, block-based programming environment specifically designed for creating Android applications. Its logic is very similar to Scratch, making it extremely intuitive for those familiar with puzzle-piece programming.

Advantages
  • Drag-and-drop design without writing code
  • Ideal for rapid mobile visualization
  • Built-in blocks for Bluetooth, GPS, and sensors
  • QR code to test live on real phone instantly
Disadvantages
  • Restricted to predefined components
  • Primarily focused on Android platform
  • Limited AI integration (manual block mapping)
  • Less suitable for complex serial USB workflows

First Impressions — "Hello Codi!" App

As part of my interface research, I experimented with MIT App Inventor following a tutorial to create the "Hello Codi!" app, where touching a bee triggers a sound effect.

Hello Codi bee app in App Inventor designer view
Object-Oriented Logic

Every element (Button, Sound, Label) functions as an independent object with customizable properties — similar to how components work in modern UI frameworks.

Event-Driven Programming

Using the Blocks editor, I managed visual logic through action blocks like when Button1.Click — without writing a single line of manual code.

Block logic view for the bee app

Python is a high-level language and Tkinter is its standard library for creating Desktop Graphical User Interfaces (GUIs). This combination gives total control over logic, appearance, and advanced libraries.

Advantages
  • Total control over logic and appearance
  • AI-friendly — easily generate, paste, and debug code with tools like Gemini or ChatGPT
  • Cross-platform: same script runs on Windows, macOS, and Linux
  • Robust USB Serial integration via pySerial
Disadvantages
  • Requires understanding of programming syntax and logic
  • Coding a layout takes more time than dragging components
  • Desktop-only (Tkinter does not target mobile)

Development Workflow

Step 01 Scripting Import libraries Step 02 UI Layout Window & grid Step 03 Logic Serial functions Step 04 Run Execute .py → window opens instantly
Feature MIT App Inventor Python (Tkinter)
Logic StyleBlock-based (like Scratch)Text-based scripting
Primary TargetMobile users (Android)Desktop users (Win/Mac/Linux)
Hardware LinkBluetooth / Wi-FiUSB Serial (COM ports)
AI IntegrationLimited (manual block mapping)High (copy/paste code logic)
Learning CurveVery low — drag and dropMedium — requires syntax knowledge
CustomizationRestricted to built-in componentsUnlimited control
Best ForQuick mobile prototypingRobust desktop + serial projects
MIT App Inventor 1. Designer View Drag & drop buttons and labels on virtual phone screen 2. Blocks View Snap logic pieces together like puzzle blocks 3. Scan QR → test on real phone Python + Tkinter 1. Write / generate code · import tkinter, serial 2. Define window · layout with grid/pack 3. Functions → open serial · send 'H' / 'L' 4. Execute .py → instant desktop window
Verdict: MIT App Inventor is ideal for quickly developing mobile prototypes without programming knowledge. Python with Tkinter offers greater flexibility and superior serial communication integration, making it the best choice for hardware-interfaced desktop projects — and the one selected for this assignment.
06
Downloads

Design & Code Files

Get in Touch

  • micaela.cordova@up.edu.pe
  • micaela.cordova.carmelino@gmail.com