Skip to content

interface and application programming

Assignment

Group assignment:

  • Compare as many tool options as possible.
  • Document your work on the group work page and reflect on your individual page what you learned.

Individual assignment

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

Bonus Learning

OBS Screen Capture

I learned how to use OBS, obs download site, to do a screen capture. I watched a quick you tube from Kevin Stravert on how to use OBS to screen record. I also refreshed on how to use the screen capture that is in windows by default. The snipping tool (ctrl+shft+s). It also hasa video record option.

DaVinci Resolve 20.3.2

Kevin from above recommends DaVinci and I also had a note to it in one of the earlier lessons. Since my groupmates have been doing most of the video editing, I hadn't tried it yet. So looks like this is my week to learn. I went to the black magic design website to download the software.. Then I went back to Kevin and found his video on Davinci and followed along.

The Group Work

Link to our group page here

Kim Alexander has a lot of prior experience with code, so she was able to help us understand the different types of code we were looking at. That was really helpful.

Angela Horstman is super creative and went to another kind of coding with boxes that looked like mods. It was pretty cool. Amazing the different ways that you can make programs and interface with microprocessors.

For me, it was alot of googling and learning about what these different things even are. I learned how to make a program with React/Vue. My Claude discussions are here. I made a color choosing program called myapp.

react program

The Individual Work

My asset files are here.

Dr. Taylor sat and coded with us for the afternoon and showed us how he used a microprocessor and procsessing to complete the assignment. Then he shared his resources with us.

Mr. Dubick worked with us claude code and how that could be a helpful tool.

Garrett Nelson taught us about MQTT and provided us this MQTT link.

It's time to dive into the software on my final project as it meets all the criteria for this weeks assignment. I am starting with some code from week 11 and week 10. I am taking that code and working to fix it to do what I want it to do.

I wanted to add some timing in so that the user would have the motor turn at the same time every day to dispense the pills. I asked claude to help me create an interface application that a user could use to set a time that would user could set. I am not sure which week I started that in, but I began working with that code and using the prompts below.

PROMPT: I want the stepper motor to rotate one step based on the time of day that was input by the user

CLAUDE: 2:36 PM Here's the plan before I write the code — just to make sure we build it right:

User types a time (like 08:00) into Serial monitor Motor advances 1 slot at that time each day ESP32 tracks time internally after the user sets it A few quick questions:

Q: How should the user enter the time? A: Hour and minute (e.g. 08:30)

Q: Does the ESP32 need to know the current time, or just count down from when it's powered on? A: Know real current time (needs RTC or NTP)

Since you need real current time, the easiest approach for the ESP32-C6 is NTP (Network Time Protocol) — it grabs the time from the internet over WiFi. No extra hardware needed since the ESP32-C6 has WiFi built in.

I need two more things before I write the code:

Q: What timezone are you in?

A: Eastern (UTC-5/-4)

Q: Should the dispensed time be changeable without re-uploading code?

A: Yes - user sets it via Serial monitor each boot

How it works at boot:

  1. Connects to WiFi
  2. Syncs current time from NTP
  3. Asks you to type the dispense time (e.g. 08:30)
  4. Checks every 30 seconds and fires when the time matches
  5. Won't dispense twice in the same day

I realized I didn't want the code checking all the time becuase its a set it once in a while but really its there... and I dont want to loose it at a reboot.

PROMPT: Eventually I will want this information to come from an app and stay in the memory if the power goes out and comes back on. Do I need to change anything in the code. The time will not be changed often.

Good thinking to plan ahead. Yes, you'll need two additions eventually:

  1. NVS (Non-Volatile Storage) — saves the dispense time to the ESP32's flash memory so it survives a power outage. The Preferences library handles this and is built into Arduino for ESP32.
  2. WiFi communication — instead of Serial input, a simple web server or MQTT broker lets your app send the time to the ESP32.

The good news is the core motor code won't change at all. The only thing that changes is how the dispense time gets set and where it's stored.

Here's the upgrade priority order:

steps Description
Current code: Serial input → RAM (lost on power off)
Next step: Serial input → NVS flash (survives power off)
Final step: App → WiFi → NVS flash (survives power off)

I'd suggest we add the Preferences NVS storage now since it's a small change and solves the power outage problem immediately. Then when you're ready to build the app we just swap the Serial input for a WiFi endpoint.

Want me to add the Preferences storage to the current code? It would:

Save the dispense time to flash the first time you enter it

On reboot, load the saved time automatically and skip the Serial prompt

Only ask for a new time via Serial if none has been saved yet


PROMPT: i need a way to change the time later if i want to..

CLAUDE: Easy fix — we can check Serial on boot and give a short window to override the saved time:

Boot behavior:

  1. Load saved time from flash
  2. Print "Saved time is 08:30 — press any key in 5 seconds to change it"
  3. If no key pressed → run with saved time
  4. If key pressed → prompt for new time, save it, run

That way normally it just boots and goes, but you have an escape hatch to change it anytime by just opening the Serial monitor right after a reset.

Boot behavior:

  • First boot ever → asks for time, saves it
  • Every boot after → shows saved time, waits 5 seconds
  • Open Serial monitor quickly after reset and press any key to change it
  • No key press → runs automatically with saved time

When you're ready to add the app, we just add a WiFi endpoint that calls saveTime() and updates dispenseHour/dispenseMinute — everything else stays exactly the same.


At first, I wasn't able to get to the reset, but by increasing the delay after the Serial.begin, I was able to get to the part where I could change the time. I tried a time that was 30 seconds later and it worked .. the stepper motor moved 1/8 rotation.

Next I went to test my photosensor

photoresistor code

I brought in my code from my photo resistor from week 9 inputs and started to work with it changing the pin numbers to match what was on my board. I noticed that the numbers wer much higher and that they were going up when I put my hand over the sensor instead of down.

I loaded my code into Claude and started working.

PROMPT: strange light calues... really high when its dark like 3000,,, whats goingo n

The problem is ADC_11db attenuation — that's actually the issue with your photoresistor readings.

What's happening

analogSetAttenuation(ADC_11db) sets the ADC input range to 0–3.9V, which sounds good, but on the ESP32-C6 specifically, 11db attenuation is known to be noisy and inaccurate, especially at the low end of the range. You get garbage high readings even with little/no light.

Fix options

Option 1 — Drop the attenuation (easiest)

// Remove this line entirely, or change to:
analogSetAttenuation(ADC_0db);  // 0–1.1V range

The tradeoff is a smaller input voltage range, but photoresistors in a voltage divider usually don't need the full 3.9V range anyway.

Option 2 — Use 6db instead

analogSetAttenuation(ADC_6db);  // 0–2.2V range — better noise behavior

Option 3 — Average multiple readings (helps regardless)

int readLight() {
  long sum = 0;
  for (int i = 0; i < 8; i++) {
    sum += analogRead(lightPin);
    delay(5);
  }
  return sum / 8;
}

Then call readLight() instead of analogRead(lightPin).

Also check your wiring

Make sure your photoresistor is in a proper voltage divider with a pulldown resistor (typically 10kΩ) to GND — a floating pin will also give wild readings.

3.3V → [photoresistor] → A1 → [10kΩ] → GND

Try removing the analogSetAttenuation line first — that's almost certainly the culprit.

I removed the analogSetAttenuation line, but the numbers were still reversed.. I had my wiring backwards

So I flipped the logic and changed the threshold to match my currrent set up. I can't change the wiring this time.. V2... I will need to get new values for lighting with different pills.

Coding to Application.. going to layer 7

In networking week, I took some time to figure this out again... and better..

Working with Claude a bunch.. Here was our conversation.

Now I am back at it and really trying to work on the application to get a UI that I am not embaressed to show anyone.

My ESP32 runs a tiny HTTP server on port 80.When you type the IP address into the browser of a device connected to the same network, you can see the interface. The WebServer library handles the socket and HTTP parsing. I need to register functions for each URL route.

I asked Claude to teach me about the parts of the UI.

PROMPT: this is my code. today i want to work on the applicatoin part of it. please explain where that is and how it does what it does

Looking at your code, the "application" — meaning the web UI and how the ESP32 serves it — lives in Section 7.

Here's how it all fits together:

The HTML page (the UI itself)

const char index_html[] PROGMEM = R"rawliteral( ... )rawliteral";

This is a big string — a complete HTML page — stored in flash memory (PROGMEM) rather than RAM. It contains the buttons, inputs, and the JavaScript that makes everything dynamic. When someone hits /, handleRoot() sends this string as the response.


The routes — how each button works

Every button in the UI calls fetch('/some-route') in JavaScript, which hits one of these registered routes:

Route Handler What it does
/ handleRoot Sends the HTML page
/status handleStatus Returns a JSON snapshot every 3 seconds
/schedule?time=HH:MM handleSchedule Sets scheduledHour / scheduledMinute
/schedule/clear handleScheduleClear Clears schedule, resets cycle
/window?minutes=N handleSetWindow Changes checkWindowMinutes
/dispense handleDispense Manually calls dispenseOnePill()
/reset handleReset Calls resetPillCycle()
/lightstatus handleLightStatus Raw ADC reading for tuning

How the UI stays live (the polling loop)

Inside the HTML, this JavaScript runs every 3 seconds:

setInterval(updateStatus, 3000);

updateStatus() fetches /status, which returns a JSON object like:

{"time":"14:32:01", "lightRaw":1842, "pillTaken":true, "checkActive":false, ...}

The JS then updates the text in the status card — that's why the time, light reading, and pill status update automatically without you refreshing the page.


The key connection between UI and firmware

The web handlers are just thin wrappers. They read URL parameters, update the state variables (like scheduledHour), and return a plain text or JSON response. The actual logic — dispensing, checking the window, sending Telegram — stays in Sections 3–6. The web server just gives you a way to poke those variables from a browser.

So loop() is doing two things simultaneously: server.handleClient() watches for incoming HTTP requests, while checkScheduledDispense() and checkPillWindow() run the pill logic. They share the same state variables, which is how a button press in the browser can trigger the motor.


State Machine & the Section of Code

My code works on as a small state machine. When I asked Claude about the sections, it was broken down into the pieces.

Prompt: what are the sections

Section 1: Config This is the only section you'd edit when setting up on a new device. It's all const declarations — nothing runs here, it just defines: - WiFi credentials - Telegram token and chat ID - Pin numbers (LED, STEP, DIR, EN, light sensor) - Motor tuning values (25 steps per dispense, 15,000µs step delay) - Light threshold (2700) - NTP server and timezone offset

Think of it as the settings file for the whole program.


Section 2: State Variables These are the variables that change while the program runs — the device's "memory" between loop() calls. Things like: - scheduledHour / scheduledMinute — what time to dispense - pillDispensed, pillCheckActive, alertSent — the state machine flags - dispenseTime — when the pill was dispensed (used to calculate the window) - checkWindowMinutes — how long to wait before alerting

Section 1 never changes. Section 2 is constantly being read and written by Sections 3–6.


The relationship between 1, 2, and 3:

A clean way to think about it: - Section 1 = constants (never change) - Section 2 = variables (always changing) - Section 3+ = functions (read from 1, read/write to 2, do things)

Sections 3–6 are the firmware logic — the "brain" of the device that actually does things. Here's each one:

Section 3: Motor - movement & update flags Controls physical movement. moveWheel(steps) pulses the STEP pin to drive the A4982 stepper driver — each pulse = one step. dispenseOnePill() calls moveWheel(), turns the LED on, sets the state flags, and fires the Telegram "pill is ready" message. resetPillCycle() clears all those flags and turns the LED off.


Section 4: Light Sensor Reads the photoresistor on GPIO1. readLight() takes 10 ADC samples and averages them to smooth out ESP32 ADC noise. isPillTaken() compares that average to your threshold (2700) — if it's below, the light is bright, meaning the pill cup is unblocked = pill was taken.


Section 5: Telegram sendTelegram() opens an HTTPS connection to api.telegram.org and fires a GET request with your message as a URL parameter. No library needed — just raw WiFiClientSecure. Everything else in the code calls this one function, so if you ever wanted to swap to email or SMS you'd only change this section.


Section 6: Scheduler + Pill Window Two functions that run every loop():

  • checkScheduledDispense() — checks the current time against scheduledHour/scheduledMinute. If they match and the pill hasn't been dispensed yet, it fires dispenseOnePill()

  • checkPillWindow() — after dispensing, polls the light sensor every 10 seconds. If pill is taken → reset cleanly. If the window expires (default 30 min) without the pill being taken → send the alert Telegram message, then end the cycle either way


Together these four sections form a simple state machine:

idle → dispensed → checking every 10s → taken ✅ or alert sent 🔔 → idle

The web server (Section 7) just lets you poke the state variables that these sections read and write.

Style of the page

I want to be able to edit the style of the webpage that shows. I used to know a little HTML, but I didnt really use CSS or style sheets back then. I asked claude for a quick explanation of where the HTML is that I can modify.

PROMPT: where is the design of the webpage and how can i modify it. Currently it displays gibberish on teh top

The webpage design lives inside this string in Section 7:

const char index_html[] PROGMEM = R"rawliteral(
   ... all the HTML/CSS/JS ...
)rawliteral";

Everything between those two markers is a standard HTML file. You can edit it exactly like a webpage.


The structure inside it:

The <style> block at the top controls all the visuals:

<style>
  body { font-family: Arial; text-align: center; margin: 20px; background: #fafafa; }
  .card { padding: 15px; border-radius: 10px; box-shadow: ... }
  .btn  { border: none; color: white; padding: 12px 24px; ... }
  .green / .red / .blue / .gray  ← button colors
</style>

The <body> has four .card divs: 1. Status card (time, light, pill state) 2. Schedule card (time picker + set/clear buttons) 3. Alert window card (minute input) 4. Manual controls card (dispense + reset buttons)


About the gibberish at the top — that's almost certainly the 💊 emoji in your <h1> tag:

<h1>💊 Pill Dispenser</h1>

Some browsers on ESP32 web servers have encoding issues with emoji. The fix is to either add a charset declaration or just remove the emoji:

<!-- Add this inside <head> -->
<meta charset="UTF-8">

<!-- Or just change the h1 to -->
<h1>Pill Dispenser</h1>

The <meta charset="UTF-8"> line is the cleaner fix and will also protect any other special characters you add later. Just drop it right after <meta name="viewport" ...> in the <head> section.


codepen.io

OK - so now time to play a little and see what I can change in the style area.

I went to https://codepen.io/editor/pen where I was able to upload my html code and then play around with the layour and the buttons and change things around. Then I wanted to add my logo to the top. I asked claude for some help becuase the file i have is an SVGand its pretty big. I could load it in raw daat, but it was pages. So I asked or some options.

code io

PROMPT: if i want to have small file size for my svg logo how would you do taht?

Yes — a few things:

In Inkscape before saving

  • Path → Object to Path on everything (converts text/shapes to paths)
  • Path → Combine where possible (merges separate paths into one)
  • Delete any hidden layers or objects you don't need
  • File → Clean Up Document (removes unused defs, gradients, etc.)

svgomg.net

Save with optimization

Instead of Plain SVG, run it through SVGO — the standard SVG optimizer. Easiest ways to access it:

  • svgomg.net — paste your SVG or upload the file, it strips all the Inkscape metadata, comments, and redundant code. Shows you the size reduction live. This is the easiest option.
  • Command linenpx svgo yourfile.svg if you have Node installed

Inkscape adds a lot of bloat — editor metadata, named layers, sodipodi tags, XML comments — that browsers don't need. SVGO strips all of that and can cut file size by 50-80%.

For your ESP32 specifically

File size matters extra because the SVG will be stored in flash and served over WiFi. Keep in mind:

  • Simpler shapes = fewer path points = smaller file
  • Avoid complex gradients (they add a lot of code)
  • The PROGMEM string you already use for your HTML is the right approach for storing it

svgomg.net is where I'd start — just paste and download the optimized version.

After doing all that shrinking, I then opened the SVG that I had made small in notepad and pasted the code in after the line

 <svg viewBox="221 365 1091 215" style="width: 300px;">

remember me image

Functionality

Now I want to work more on changing the functionality. I would like to add the second notification and the abilty to change how often it notifies. I would like to have a first message to the Pill Taker that the pill is ready. Then the rest are reminders base on user set repeat window until 21 minutes have passed. The care taker is notified if (A) the pill is taken, (B) the pill is not taken after 15 minutes, (C) a second reminder if the pill is not taken after 20 minutes. I will ask claude to add this functionality along with the ability to set the reminder repeat window for the Pill Taker. I will also ask them to set how many reminders they have left before the caretaker is notified, based on the repeat time that they set.

notification Image

It makes my page too long to put all the prompts and answers in this page.. so i will put the whole stuff on the page linked here Claude Chats to add functionality and I will just pull out the information on this main page.

KEY POINTS:

  • Added constants for the new things that I wanted to add.
  • Used my own telegram ID for both Pill Taker and Care Taker until I can get a second phone number/ telegram account to be a caretaker. I may use a google voice account. We will see.. .lets just get this working first.

HERO SHOT

This is video I took of all the different pieces and then put into Davinci and had them play all at once so you can see all the different things happening simultaneously.