Implement a User Interface (UI) using programming and explore protocols to communicate with a microcontroller board that you designed and made.
This is the schedule for this week 😮💨
For the group assignment, we tried out a few interface tools—Processing, p5.js, and Node RED using our XIAO ESP32-C3 board from earlier weeks to send data from the microcontroller to a screen. Processing and p5.js used USB serial to show live data on a computer or browser, while Node RED used Wi-Fi with MQTT to send data wirelessly to a dashboard. We compared how each one works, how easy they are to set up, and what they’re best used for.Overall, we learned how different communication methods affect interface design.
You can access our group assignment here for more details.
So for this week's individual assignment, we have to write an application that lets a user interact with the embedded board we made like either reading data from an input device, controlling an output device, or both. At first, I thought this week was very similar to Networking and Communications week but unlike the networking and communications week where the focus was on boards talking to each other, this week is about creating an interface that allows a human to interact with the board through an application with a visual UI
Tkinter short for "Tk interface" is the standard library built into Python used to create Graphical User Interfaces (GUIs). A Graphical User Interface (GUI) is a way to interact with a computer using visuals like icons, menus, and buttons instead of typing commands. It makes things easier by letting you click or tap to do tasks on your computer. So Tkinter is the tool you use to build desktop applications that have windows, buttons, and text boxes instead of just running in a text only command line window.
Before starting with Tkinter, I first had to install Python on my computer, since Tkinter runs using Python.I went to python.org/downloads and downloaded Python 3.11 or a newer version.Then run the downloaded .exe file
The Python installer popped up in the terminal, and I just followed the prompts by typing y/yes for the configuration options until Python finished installing.
Then to check whether Python has been installed or not, I typed the command python --version and pressed Enter.
Now that I have installed python on my computer, I had to make a python file in VSCode
Tkinter is a library so it already comes with Python. You just import it at the top of your code.
import tkinter as tk
The as tk part just means instead of typing tkinter.Button every time you can just type tk.Button. It's a shortcut nickname.
root = tk.Tk()
Creates the actual window/pop up where all your widgets will live.
label = tk.Label(root, text="Hello, Tkinter!")
button = tk.Button(root, text="Click me!")
Creates a text label and a clickable button, attaching both to the root window.
Pack manager:
label.pack()
button.pack()
Stacks widgets vertically (or horizontally) like packing items in a box.
Grid manager:
label.grid(row=0, column=0)
button.grid(row=1, column=0)
Places widgets in a table like layout using rows and columns (like Excel cells).
label.config(font=("Arial", 12), fg="blue")
button.config(bg="green", command=some_function)
Changes how widgets look (font, fg=text color, bg=background) and behaves (command=what happens when clicked).
def some_function():
print("Button clicked!")
Creates a function that runs automatically when an event occurs (like clicking the button).
root.mainloop()
Keeps the window open and constantly waits for user actions (clicks, key presses, etc.). Without this, the window would flash and disappear instantly.
This was a simple GUI I created for practice:
I also wanted to make some progress on my final project this week, so I decided to test the posture correction part pf my final project.
I generated a flowchart form Mermaid flowcharts for the workflow this assignment:
I’ll be using the Xiao ESP32-C3 board I made during Output week, connected to a buzzer that will beep whenever a slouch is detected.
I connected the positive wire into pin D0 on the XIAO (GPIO 0). This is the signal pin that will turn the buzzer on and off.
I then connected the negative wire into any GND pin on the XIAO.
The two main libraries I used for posture detection and video processing were MediaPipe and OpenCV:
MediaPipe is a machine learning framework developed by Google that provides ready made models for tasks like pose detection, hand tracking, and face recognition. MediaPipe was chosen because it provides real time, on device pose estimation that runs efficiently on a standard CPU without requiring a GPU or cloud services.
OpenCV (Open Source Computer Vision Library) is a powerful library for image and video processing. OpenCV was used because it makes it easy to capture and process live webcam video in real time, which is important for a smooth posture tracking system.
I first downloaded all the required libraries
Open your terminal and make sure you are connected to the internet. Then use the following commands to install the libraries.
# 1. Install OpenCV (for camera and image processing)
pip install opencv-python
# 2. Install MediaPipe (for pose detection)
pip install mediapipe
# 3. Install Pillow (to show images in Tkinter)
pip install pillow
# 4. Install PySerial (to communicate with ourXIAO ESP32 C3 via USB)
pip install pyserial
After installing all the required libraries, I used Claude AI to help generate the code for both the GUI and the posture detection. I designed the GUI to use the laptop’s webcam to detect whether the user is slouching from a front view while sitting. This is also the main idea behind my final project. This was the prompt I used "Generate a Python script that uses my laptop’s front webcam to detect slouching in real time. Even though a front view hides the spine, there are 5 methods for the detection: shoulders narrowing, one shoulder tilting down which is a sign for side ways lean, and head dropping. The script should calculate a weighted posture score like for exampel, shoulder width 40%, forward head 30%, shoulder tilt 15%, head drop 15%, show a clean Tkinter GUI with live metrics showing all the stats, and talk to an ESP32‑C3 (COM3, 9600 baud) " Even though it’s very hard especially considering the camera is going to be infront of you instead of somewhere at the side where your spine could be seen, I indentified 4 main ways through which the computer can tell whether you're slouching or not from the front:
Claude generated a very long code for me to explain so I'll explain some of the most important parts of the code:
Importing Libraries
import cv2
import mediapipe as mp
import serial
from PIL import Image, ImageTk
import tkinter as tk
Connecting to Arduino
xiao = serial.Serial(PORT, BAUD, timeout=1)
Loading the AI Model
landmarker = PoseLandmarker.create_from_options(landmarker_options)
Reading Body Points from Camera
result = landmarker.detect(mp_image)
pts = get_landmarks_dict(result.pose_landmarks[0], w, h)
if vis < 0.35:
pts[idx] = None
Measuring Posture
shoulder_width = math.hypot(r_sho[0] - l_sho[0], r_sho[1] - l_sho[1])
forward_head_px = shoulder_mid_y - ear_mid_y
head_drop_ratio = nose_to_shoulder_y / max(face_width, 1.0)
depth_ratio = face_width / shoulder_width
Calibration
while calibrating and time.time() - start < 5.0:
cal_upright_metrics.append(last_metrics_raw.copy())
upright_baseline = {k: avg(k, cal_upright_metrics) for k in keys}
slouch_baseline = {k: avg(k, cal_slouch_metrics) for k in keys}
Scoring Posture (0–100)
sw = map_metric_to_score(metrics['shoulder_width'], up_base['shoulder_width'], sl_base['shoulder_width'])
combined = sw*0.50 + fh*0.20 + st*0.15 + hd_score*0.10
Smoothing the Score
score_buffer = deque(maxlen=61)
score_buffer.append(score_with_offset)
median_score = sorted(score_buffer)[len(score_buffer)//2]
Camera & Display
ret, frame = cap.read()
frame_disp = cv2.flip(frame, 1)
preview_rgb = cv2.cvtColor(preview, cv2.COLOR_BGR2RGB)
img = Image.fromarray(preview_rgb)
The code was quite long, and I had to modify and debug it many times. One issue I faced was the camera feed lagging and glitching because it was processing too many frames. To fix this, I lowered the camera resolution and adjusted the program to run the heavy posture detection only every few frames instead of every single one.
First, I lowered the camera resolution by changing these lines:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
That made each frame smaller and faster to process. Then, instead of running the posture detection on every frame, I added a counter so it only runs every 3rd frame:
if frame_counter % 3 == 0:
# run MediaPipe detection
That cut down the workload a lot.
Then uplaod the buzzer code as well and make sure to get the port number right. To check go to Device managers and click on Ports and check your port number. The Python posture code and the Arduino buzzer code must use the same COM port number (e.g., COM3) because they communicate over that serial connection. The Python script sends characters like 'S' (slouch) or 'O' (good posture) through the port, and the Arduino reads them to control the buzzer.
This was the buzzer code:
#define BUZZER_PIN D0
#define LEDC_CHANNEL 0
#define LEDC_RESOLUTION 8
char lastCmd = ' ';
void setup() {
Serial.begin(9600);
// attach buzzer pin to LEDC channel
ledcAttachChannel(BUZZER_PIN, 1000, LEDC_RESOLUTION, LEDC_CHANNEL);
ledcWrite(LEDC_CHANNEL, 0); // silent at start
}
void beep(int freq, int duration_ms) {
ledcWriteTone(BUZZER_PIN, freq);
delay(duration_ms);
ledcWriteTone(BUZZER_PIN, 0);
}
void loop() {
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd != lastCmd) {
lastCmd = cmd;
if (cmd == 'S') {
beep(2000, 400); // 2000Hz for 400ms — one sharp beep
}
if (cmd == 'O') {
ledcWriteTone(BUZZER_PIN, 0); // make sure silent
}
}
}
}
Pin definations:
#define BUZZER_PIN D0
#define LEDC_CHANNEL 0
#define LEDC_RESOLUTION 8
Last command variable:
This variable stores the previous serial command so the code only reacts when the command actually changes:
char lastCmd = ' ';
Setup:
Starts serial communication at 9600 baud, attaches the buzzer pin to the chosen LEDC channel, and sets the buzzer silent at the beginning:
void setup() {
Serial.begin(9600);
ledcAttachChannel(BUZZER_PIN, 1000, LEDC_RESOLUTION, LEDC_CHANNEL);
ledcWrite(LEDC_CHANNEL, 0);
}
The beep() function:
This function plays a tone at a given frequency for a specified duration (in milliseconds), then stops the sound:
void beep(int freq, int duration_ms) {
ledcWriteTone(BUZZER_PIN, freq);
delay(duration_ms);
ledcWriteTone(BUZZER_PIN, 0);
}
Loop – command handling:
Reads incoming serial commands. Only reacts when the command changes from the previous one. If the command is 'S', it calls beep(2000, 400) to produce one sharp beep. If the command is 'O', it immediately silences the buzzer:
void loop() {
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd != lastCmd) {
lastCmd = cmd;
if (cmd == 'S') {
beep(2000, 400);
}
if (cmd == 'O') {
ledcWriteTone(BUZZER_PIN, 0);
}
}
}
}
After running the buzzer code in arduino ide, close the serial monitor and open the python file. Then in the VS Code terminal, run this command to open the GUI.
It took me several trial and errors to get the posture detection to be able to detect a basic slouch and straight posture 😵 . Most of the times, even when I sat up straight, it fluctuated between 'good posture' and 'slouched' mainly because the thresholds for what counts as “good” or “slouched” were too close together. Small natural movements like shifting in my chair, would push my posture score just above or below the limit.
This was trial 2 😅 :
Trial 3 😐 :
Trial 4 🥲 :
Trial 5 😭 :
After a lot of changes, I got it partly working, but it still kept switching between good posture and slouching 😮💨 . The main issue was that it was checking every frame, so even small changes in the measurements would quickly make it say “slouching.”
To fix this, I got feedback from different AIs like DeepSeek, Claude, and Gemini. Most of them suggested making the system less sensitive, since you won’t have perfect posture while working and there will always be small movements. So instead of reacting to every tiny change, I set a threshold for what counts as slouching. I also made the system less sensitive and slower to react. I as in, I asked pointed out what lines to chnage to what, to Copilot.
The posture detection works, but I still need to improve it to make it more accurate, especially when used while working. I also need to account for situations where the user is looking down at their table, like when studying, so it doesn’t get misread as bad posture.
I think this week was really productive since I got to test a key part of my final project, and I had fun too so this week's a win win 🥳
That's all for this week. Thank you bye! (o゚v゚)ノ