14. Interface and Application Programming

Assignment

Write an application that interfaces a user with an input and/or output device that you made. Compare as many tool options as possible and document your work on the group assignment page.

For this week I built a desktop application using Python and Qt Designer to communicate with the ESP32 board I designed in previous weeks. The interface allows a user to set a medication alarm with day-of-week repetition, and when the alarm fires the app confirms pill intake and triggers a servo motor that opens the physical pill dispenser.

Group Assignment

System Overview

The project is divided into two main sides: the PC application (Python + Qt) and the ESP32 firmware (Arduino). They talk to each other through a USB serial connection at 115200 baud using plain text commands — simple enough to read by eye in the Serial Monitor.

Hardware used

ComponentPinFunction
OLED SH1106 128×64SDA=GPIO0, SCL=GPIO1Displays current time and alarm status
RTC DS3231Same I2C busKeeps real time even without power
BuzzerGPIO 25Audible alarm when it is time to take the pill
LEDGPIO 26Visual indicator — blinks with the buzzer
Servo motorGPIO 27Rotates 90° to open the pill compartment
Push button (PCB)GPIO 34Physical confirm button on the board

Serial protocol

PC → ESP32ESP32 → PC
SET_ALARM:HH:MM:LMMJVSD — sets alarm time and daysOK_ALARM — alarm saved confirmation
CONFIRM — user confirmed pill was takenALARM_ON — it is time to take the pill
ALARM_OFF — alarm has been dismissed
TIME:HH:MM:SS — current time (sent every second)

About this tab

Everything you need to install and organize before touching any code. Follow every step in order — skipping one will usually break the next.

Step 1 — Install Python

Python is the programming language the application runs on. You only need to install it once.

  1. Go to python.org/downloads and download the latest Python 3.11+ installer for Windows.
  2. Run the installer. On the first screen, check the box that says "Add Python to PATH" — this is critical, do not skip it.
  3. Click Install Now and wait for it to finish.
  4. Open a terminal (search cmd in the Windows start menu) and type the command below to verify it worked. You should see a version number printed.
python --version

Step 2 — Organize your project folder

Create a dedicated folder for the project so all files stay together. A good location is your Desktop or Documents folder. The folder should contain exactly these files:

File structure

FileWhat it does
pastillero.uiVisual layout — edit this in Qt Designer
frontend.pyGenerated automatically from the .ui file — do not edit by hand
backend.pyAll the application logic goes here
base_serial.pyHandles the serial connection in a background thread so the window does not freeze
requirements.txtList of Python libraries the project needs
pastillero_esp32.inoArduino sketch that runs on the ESP32

Step 3 — Install the Python libraries

Open a terminal, navigate to your project folder using the cd command, and run:

python -m pip install -r requirements.txt

This installs PyQt6 (the graphical interface library) and pyserial (for serial communication with the ESP32). Wait until the terminal shows "Successfully installed" before continuing.

Key terminal commands for Windows

CommandWhat it does
cd nombre_carpetaEnters a folder. Example: cd Desktop\pastillero
cd ..Goes back one folder level
dirLists all files in the current folder
python backend.pyRuns the application
python -m pip install pyserialInstalls the pyserial library manually
python -m PyQt6.uic.pyuic pastillero.ui -o frontend.pyConverts the Qt Designer file into Python code
Ctrl + CStops a running program in the terminal

What is Qt Designer?

Qt Designer is a visual drag-and-drop editor that lets you build the graphical interface without writing code. You place buttons, labels and text boxes on a canvas, and it generates the Python file automatically. Think of it like Canva but for app windows.

File structure — who does what

The Python side is split into three files following the same template the professor gave us. Each file has one clear job so the code stays readable and easy to modify.

base_serial.py — the serial worker

This file runs the serial port in a background thread. A thread is like a second worker running at the same time as the main program. Without it, the window would freeze every time it waits for data from the ESP32. You do not need to edit this file — just keep it in the folder.

The three things it does: detect available COM ports, open or close the connection, and continuously read incoming lines and pass them to a callback function in backend.py.

backend.py — the application logic

This is the only file you write yourself. It imports the interface from frontend.py, connects each button to a function, and defines what happens when data arrives from the ESP32. The three key functions to understand:

enviarAlarma() — sends the alarm to the ESP32

Reads the time selected in the Time Edit widget and which day checkboxes are checked, then builds a text command and sends it through serial. The format is:

SET_ALARM:08:30:1110100
Hour:Minute followed by 7 digits (1=active, 0=inactive) for Mon through Sun.

confirmarToma() — confirms pill was taken

Sends the text CONFIRM to the ESP32. The board then turns off the buzzer and LED, rotates the servo to open the pill compartment, and sends back ALARM_OFF to update the interface.

procesarDato() — handles messages from the ESP32

This function is called automatically every time the ESP32 sends a line. It checks what the message says and updates the interface accordingly — enabling the Pill Taken button when ALARM_ON arrives, and updating the clock display every second when TIME:HH:MM:SS arrives.

Running the application

Open a terminal in the project folder and run:

python backend.py

The window opens immediately. Select the COM port of your ESP32 in the dropdown, click Connect, set a time and days, and press Send Alarm. The ESP32 will confirm with OK_ALARM in the status bar.

What the ESP32 does

The Arduino sketch runs a continuous loop that does four things simultaneously: reads serial commands from Qt, checks if it is alarm time by comparing the RTC with the stored alarm, drives the buzzer and LED when the alarm is active, and sends the current time to Qt every second.

Required Arduino libraries

Install all of these from Arduino IDE → Sketch → Include Library → Manage Libraries:

LibraryWhat it does
RTClib by AdafruitCommunicates with the DS3231 real-time clock
Adafruit SH110XDrives the SH1106 OLED display
Adafruit GFX LibraryRequired by SH110X for drawing text and shapes
ESP32ServoControls the servo motor on ESP32 (replaces the standard Servo library)

First time setup — syncing the RTC

The DS3231 keeps time even without power, but the first time you use it you need to set the correct time. In the sketch find this line and uncomment it (remove the //):

// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

Upload the sketch once with this line active. The RTC will be set to your computer's clock at compile time. Then comment it back out and upload again — otherwise the time resets every time the board restarts.

How the alarm logic works

Every second the sketch reads the RTC and compares the current hour and minute with the stored alarm values. It also checks the day of the week against the diasActivos[] array that was set by the SET_ALARM command. If all three match (hour, minute, and day) and the alarm has not already fired this minute, it calls activarAlarma() which sends ALARM_ON to Qt and starts the buzzer and LED blink cycle.

When the user confirms (either from the Qt button or the physical PCB button), confirmarToma() turns everything off, rotates the servo 90° for 2 seconds to open the pill compartment, then closes it and sends ALARM_OFF back to Qt.

Important — I2C pins: This sketch uses GPIO 0 (SDA) and GPIO 1 (SCL) for both the OLED and the RTC, matching the PCB routing from Week 11. If your board uses different pins, update the Wire.begin(SDA, SCL) call at the top of setup().

Final result

The complete system: Qt interface on the PC communicating with the ESP32 in real time. The OLED and the Qt clock display show the same time, both synchronized from the RTC.

Interface

The final interface has four sections: the real-time clock pulled from the RTC, the alarm configurator with time and day selection, the state card that changes color when the alarm fires, and the serial connection controls.

Interface in idle state
Interface — idle, connected, alarm set
Interface when alarm fires
Interface — alarm active, Pill Taken button enabled

Physical hardware

OLED showing time and alarm state
OLED display — synchronized with the Qt clock
Servo opening the pill compartment
Servo rotating to open the pill compartment after confirmation

Reflections

The biggest challenge was understanding how Qt's threading model works. The serial port runs in a background thread, but Qt only allows the main thread to touch the interface. The solution was a Bridge object with a pyqtSignal that safely passes messages from the serial thread to the main thread — a pattern that is common in Qt applications but not obvious at first.

Another learning was that regenerating frontend.py with pyuic6 overwrites any manual edits, so all styling logic lives in backend.py using setStyleSheet(). This keeps the workflow clean: design in Qt Designer, logic in Python.