Week 14

Interface & Application Programming

Krypto

ASSIGNMENT

Write an application for the embedded board that interfaces a user with an input and/or output device. Document the design process, tools used, and the final working interface.

TOOLS

VS Code · HTML · CSS · JavaScript · ESP32 · MPU-6050 · GitLab Pages

Personal and Group Assignment

This week's personal assignment was to Write an application for the embedded board that you made. that interfaces a user with an input and/or output device(s)

Group Assignment

Here's the link to our group assignment of this week.

The Concept

Krypto Support — A web app for IVDD monitoring

For this week's assignment I built the interface for my final project: a web application called Krypto Support that monitors the movement and activity of my dog Krypto, who has been diagnosed with Intervertebral Disc Disease (IVDD). The app connects wirelessly via WiFi to an ESP32 microcontroller with an MPU-6050 IMU sensor, reads real-time motion data, and presents it in a dashboard designed for both the dog's owner and the veterinarian treating the condition.

The input device is the MPU-6050 IMU sensor — it captures acceleration and orientation data from Krypto's body. The embedded board is an ESP32 (and ultimately the XIAO ESP32-C6 in the final wearable), which processes the sensor data and serves it over WiFi. The application is the web dashboard that visualizes the data, detects impacts, tracks activity vs rest time, and generates a daily medical report that can be shared with the vet.

MPU-6050 ESP32 reads & processes Serves data over WiFi Web dashboard visualizes PDF report for vet

Why a web app?

A web app built with plain HTML, CSS and JavaScript requires no installation, runs on any device — iPhone, Android, laptop — and can be published instantly through GitLab Pages. For a wearable that needs to be accessed by both the dog's owner and a veterinarian in different locations, this is the most practical choice.

The ESP32 acts as a web server: it connects to the local WiFi network and exposes two endpoints — /datos for the live sensor JSON and /reset to clear the daily counters. The dashboard polls these endpoints every second using the browser's Fetch API, with no backend or cloud service required.

My Process

00. Visual Design — Canva Template

Before writing any code, I designed the visual identity of the app in Canva. This included defining the color palette, the logo concept, the layout of the main screens, and which metrics I wanted to show. Having a clear reference from the start made the coding process much faster — I wasn't making design decisions in code, I was translating decisions already made visually.

The identity is built around Krypto's personality: the yellow ring references his characteristic collar, the green background his energy, and the blue is the primary brand color carried through all interactive elements. The font is bold and uppercase to give the app a strong, confident character.

colorpalette

Brand identity & color palette

The four brand colors defined in Canva: yellow #ffde59, blue #0d31e2, cream #f6f3e2, and green #89c518. Each color has a specific role across the interface.

colorpalette

Screen layout template

The splash screen and dashboard layout sketched in Canva, showing the logo placement, metric cards, and the LOG IN button pinned to the bottom of the screen.

Color Hex Role in the interface
Yellow #ffde59 Logo ring, impact markers in the chart, accent on buttons
Blue #0d31e2 Primary brand color — header bar, pitch bar, LOG IN button, section titles
Cream #f6f3e2 App background — warm, non-clinical feel for a pet health app
Green #89c518 Active state indicator, roll bar, download PDF button

00b. Finding the ESP32 IP — I2C Scanner

Before connecting the dashboard to the ESP32, the sensor connection needs to be verified and the board's IP address retrieved. This I2C scanner sketch confirms the MPU-6050 is correctly wired and detected at address 0x68, then the WiFi firmware prints the assigned IP to the Serial Monitor so it can be entered in the dashboard's connection field.

I2C Scanner — verify MPU-6050 is detected

Upload this sketch first to confirm the sensor is wired correctly before uploading the full firmware. Open Serial Monitor at 115200 baud and press the EN/RST button — you should see Dispositivo encontrado en 0x68.

C++ #include <Wire.h> void setup() { Serial.begin(115200); delay(3000); Wire.begin(21, 22); // SDA=21, SCL=22 on ESP32 Dev Module Serial.println("Escaneando I2C..."); for (byte i = 8; i < 120; i++) { Wire.beginTransmission(i); if (Wire.endTransmission() == 0) { Serial.print("Dispositivo encontrado en 0x"); Serial.println(i, HEX); } } Serial.println("Listo."); } void loop() {}
Full firmware — WiFi server + MPU-6050 + complementary filter

This is the complete firmware uploaded to the ESP32. After uploading, open Serial Monitor at 115200 baud — the board prints its IP address once connected to WiFi. Enter that IP in the dashboard to start receiving live data.

C++ #include <Wire.h> #include <MPU6050.h> #include <WiFi.h> #include <WebServer.h> const char* ssid = "YOUR_WIFI"; const char* password = "YOUR_PASSWORD"; MPU6050 mpu(0x68); WebServer server(80); float pitch = 0, roll = 0, accelMag = 0; unsigned long tiempoAnterior = 0; int impactos = 0; long segundosActivo = 0, segundosReposo = 0; float pitchPromedio = 0; int muestrasTotal = 0; bool estaActivo = false; const float UMBRAL_IMPACTO = 2.5; const float UMBRAL_ACTIVO = 0.3; void setup() { Serial.begin(115200); Wire.begin(21, 22); mpu.initialize(); WiFi.begin(ssid, password); Serial.print("Conectando WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" IP: " + WiFi.localIP().toString()); server.on("/datos", HTTP_GET, []() { String json = "{"; json += ""pitch":" + String(pitch, 1) + ","; json += ""roll":" + String(roll, 1) + ","; json += ""accel":" + String(accelMag, 2) + ","; json += ""impactos":" + String(impactos) + ","; json += ""activo":" + String(segundosActivo) + ","; json += ""reposo":" + String(segundosReposo) + ","; json += ""posturaAvg":" + String(pitchPromedio, 1) + ","; json += ""estaActivo":" + String(estaActivo ? "true" : "false"); json += "}"; server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "application/json", json); }); server.on("/reset", HTTP_GET, []() { impactos = segundosActivo = segundosReposo = muestrasTotal = 0; pitchPromedio = 0; server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "text/plain", "OK"); }); server.begin(); tiempoAnterior = millis(); } void loop() { server.handleClient(); int16_t ax, ay, az, gx, gy, gz; mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); unsigned long ahora = millis(); float dt = (ahora - tiempoAnterior) / 1000.0; tiempoAnterior = ahora; // Complementary filter float ap = atan2(ay, az) * 180.0 / PI; float ar = atan2(-ax, az) * 180.0 / PI; pitch = 0.98 * (pitch + (gx / 131.0) * dt) + 0.02 * ap; roll = 0.98 * (roll + (gy / 131.0) * dt) + 0.02 * ar; // Acceleration magnitude in g accelMag = sqrt( pow(ax / 16384.0, 2) + pow(ay / 16384.0, 2) + pow(az / 16384.0, 2) ); if (accelMag > UMBRAL_IMPACTO) impactos++; float mov = abs(accelMag - 1.0); estaActivo = mov > UMBRAL_ACTIVO; if (estaActivo) segundosActivo++; else segundosReposo++; muestrasTotal++; pitchPromedio += (pitch - pitchPromedio) / muestrasTotal; delay(100); }
# What you see in Serial Monitor after uploading
Escaneando I2C...
Dispositivo encontrado en 0x68
Listo.
# Then after uploading the full firmware:
Conectando WiFi......
IP: 192.168.1.45

01. Building the Interface — VS Code

With the visual reference ready, I built the full interface in VS Code as a single index.html file containing HTML, CSS and JavaScript. The app has four screens managed by a simple show/hide system: the splash screen, the login/registration screen, and the main dashboard. No framework was used — plain HTML and vanilla JS keep the file self-contained and deployable anywhere without a build step.

The logo image was embedded directly into the HTML as a base64-encoded PNG, so the app works as a single file with no external assets. The Live Server extension in VS Code allowed me to preview changes instantly in the browser as I edited the code.

  • 1Created a folder krypto-dashboard/ and opened it in VS Code
  • 2Installed the Live Server extension by Ritwick Dey for real-time browser preview
  • 3Built the splash screen with the Krypto logo and LOG IN button
  • 4Added the login/register screen with user and pet profile forms
  • 5Built the dashboard with metric cards, orientation bars, acceleration chart and report generator
  • 6Added jsPDF library to generate downloadable PDF reports for the veterinarian
  • 7Embedded the Krypto logo as base64 so the entire app lives in one file
colorpalette

VS Code with Live Server

The index.html open in VS Code with the Live Server preview running in Chrome. Changes to the code reflect immediately in the browser without manual refresh.

colorpalette

Splash screen

The opening screen with the Krypto logo, app name in bold uppercase blue, and the LOG IN button anchored to the bottom of the screen — matching the Canva template exactly.

ESP32 firmware — sending data over WiFi

The ESP32 connects to the local WiFi network and exposes two HTTP endpoints. The dashboard fetches /datos every second to get the live sensor JSON.

C++ // ESP32 firmware — WiFi server + MPU-6050 data #include <Wire.h> #include <MPU6050.h> #include <WiFi.h> #include <WebServer.h> const char* ssid = "YOUR_WIFI"; const char* password = "YOUR_PASSWORD"; MPU6050 mpu(0x68); WebServer server(80); // Complementary filter angles float pitch = 0, roll = 0, accelMag = 0; int impactos = 0; long segundosActivo = 0, segundosReposo = 0; float pitchPromedio = 0; bool estaActivo = false; void setup() { Wire.begin(21, 22); // SDA, SCL — ESP32 Dev Module mpu.initialize(); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) delay(500); server.on("/datos", HTTP_GET, []() { String json = "{"; json += "\"pitch\":" + String(pitch, 1) + ","; json += "\"roll\":" + String(roll, 1) + ","; json += "\"accel\":" + String(accelMag, 2) + ","; json += "\"impactos\":" + String(impactos) + ","; json += "\"activo\":" + String(segundosActivo) + ","; json += "\"reposo\":" + String(segundosReposo) + ","; json += "\"posturaAvg\":" + String(pitchPromedio, 1) + ","; json += "\"estaActivo\":" + String(estaActivo ? "true" : "false"); json += "}"; server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "application/json", json); }); server.begin(); }
Dashboard JavaScript — fetching and rendering data

The dashboard polls the ESP32 every second using the Fetch API and updates all UI elements — metric cards, orientation bars, and the acceleration chart — without reloading the page.

JS // Poll ESP32 every second and update the UI async function obtener() { try { const r = await fetch(`http://${IP}/datos`); const d = await r.json(); // Update metric cards document.getElementById('impactos').textContent = d.impactos; document.getElementById('activo').textContent = Math.floor(d.activo / 60); document.getElementById('reposo').textContent = Math.floor(d.reposo / 60); document.getElementById('postura').textContent = parseFloat(d.posturaAvg).toFixed(1); // Update orientation bars barra('bPitch', 'vPitch', d.pitch, -90, 90, '°'); barra('bRoll', 'vRoll', d.roll, -90, 90, '°'); barra('bAccel', 'vAccel', d.accel, 0, 4, ' g'); // Push to history chart hist.push(parseFloat(d.accel)); if (hist.length > MAX) hist.shift(); dibujar(); } catch(e) { document.getElementById('connStatus').textContent = '✗ No connection'; } } setInterval(obtener, 1000);

App features

The finished app has four screens: a splash screen, a login/registration screen where the owner enters their details and the pet's profile (name, breed, age, weight, and photo), the main dashboard with live metrics, and a PDF report generator. User data is saved in localStorage so the app remembers the session and skips directly to the dashboard on subsequent visits.

The PDF report is generated client-side using jsPDF — no server needed. It includes the pet profile, daily metrics, a clinical analysis section with color-coded alerts, and a footer with the Fab Academy project credit.

01b. Full Web App — index.html

The complete web application lives in a single index.html file. It contains the HTML structure, all CSS styling, and the JavaScript logic for fetching sensor data, updating the UI, and generating the PDF report. The Krypto logo is embedded as a base64 PNG so the file is fully self-contained — no external assets needed.

Splash screen
Login / Register
Live dashboard
PDF report generator
localStorage session
PWA installable
index.html — structure overview

The app is organized into three screens managed by a show/hide system. Only one screen is visible at a time. The goTo(id) function handles all transitions.

HTML <!-- Screen 1: Splash --> <div id="splash" class="screen active"> <img class="logo-img" src="data:image/png;base64,..."/> <div class="app-name">Krypto<br>Support</div> <button onclick="goTo('loginreg')">LOG IN</button> </div> <!-- Screen 2: Login / Register --> <div id="loginreg" class="screen"> <!-- Two tabs: login + registration --> <!-- Registration collects: name, email, password --> <!-- Pet profile: name, breed, age, weight, photo --> </div> <!-- Screen 3: Dashboard --> <div id="dash" class="screen"> <!-- Blue header with dog name and connection status --> <!-- IP input field to connect to ESP32 --> <!-- 4 metric cards: impacts, active, rest, posture --> <!-- Orientation bars: pitch, roll, acceleration --> <!-- 60s acceleration chart (Canvas API) --> <!-- Actions: reset day + generate PDF report --> </div>
JavaScript — screen navigation & session management
JS // Navigate between screens function goTo(id) { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById(id).classList.add('active'); if (id === 'dash') setTimeout(initGrafica, 100); } // Save user + pet profile to localStorage on register function registro() { const user = { nombre: document.getElementById('r-nombre').value, email: document.getElementById('r-email').value, pass: document.getElementById('r-pass').value, dogname: document.getElementById('r-dogname').value, raza: document.getElementById('r-raza').value, edad: document.getElementById('r-edad').value, peso: document.getElementById('r-peso').value, foto: document.getElementById('fotoPreview').src }; localStorage.setItem('ks-user', JSON.stringify(user)); cargarPerfil(user); goTo('dash'); } // Auto-login: skip to dashboard if session exists window.addEventListener('load', () => { const user = JSON.parse(localStorage.getItem('ks-user') || 'null'); if (user) { cargarPerfil(user); goTo('dash'); } });
JavaScript — real-time chart with Canvas API

The acceleration history chart is drawn frame by frame using the HTML Canvas API. Impact events above 2.5g are highlighted as yellow dots with a blue outline.

JS function dibujar() { if (!canvas) return; canvas.width = canvas.offsetWidth; ctx.clearRect(0, 0, canvas.width, canvas.height); const w = canvas.width / MAX; // Reference line at 1g (stationary) const y1 = canvas.height - (1 / 4) * canvas.height; ctx.strokeStyle = '#e0dcc8'; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.moveTo(0, y1); ctx.lineTo(canvas.width, y1); ctx.stroke(); ctx.setLineDash([]); // Acceleration line ctx.strokeStyle = '#0d31e2'; ctx.lineWidth = 2; ctx.beginPath(); hist.forEach((v, i) => { const x = i * w; const y = canvas.height - (Math.min(v, 4) / 4) * canvas.height; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }); ctx.stroke(); // Impact dots — yellow fill, blue outline hist.forEach((v, i) => { if (v > 2.5) { ctx.fillStyle = '#ffde59'; ctx.beginPath(); ctx.arc(i * w, canvas.height - (Math.min(v, 4) / 4) * canvas.height, 4, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#0d31e2'; ctx.stroke(); } }); }
JavaScript — PDF report with jsPDF

The PDF is generated entirely client-side using the jsPDF library. It includes the pet profile pulled from localStorage, the day's metrics, and a color-coded clinical analysis section.

JS function descargarPDF() { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); const user = JSON.parse(localStorage.getItem('ks-user') || '{}'); // Blue header doc.setFillColor(13, 49, 226); doc.rect(0, 0, 210, 40, 'F'); doc.setTextColor(255, 255, 255); doc.setFontSize(22); doc.setFont('helvetica', 'bold'); doc.text('KRYPTO SUPPORT', 18, 17); // Pet profile card doc.setFillColor(246, 243, 226); doc.roundedRect(18, 48, 174, 24, 3, 3, 'F'); doc.setTextColor(13, 49, 226); doc.text(user.dogname || 'Krypto', 22, 62); // Save as dated file doc.save('Krypto_Reporte_' + new Date().toISOString().slice(0, 10) + '.pdf'); }

02. The Interface

The dashboard is designed for mobile-first use — the dog's owner checks it from their phone while Krypto is wearing the sensor. The layout follows the brand identity defined in Canva: cream background, blue header bar, yellow accents, and bold uppercase typography.

colorpalette

Login / Registration

Two-tab screen: existing users log in with email and password, new users register their profile and their pet's details including a photo.

colorpalette

Live dashboard

Four metric cards showing impacts, active time, rest time, and average posture angle. Orientation bars update in real time from the IMU data.

colorpalette

PDF report

One-tap report generation with the pet profile, daily metrics, and a clinical analysis section with automatic alerts for elevated impacts or abnormal posture.

Metric Source Clinical relevance for IVDD
Impacts (>2.5g) Accelerometer magnitude spike High-impact events stress the intervertebral discs directly
Active time Movement threshold on accel mag Tracks whether the dog is respecting prescribed rest periods
Rest time Near-zero acceleration Confirms recovery time after episodes or surgery
Posture angle Pitch average (complementary filter) Sustained abnormal angles may indicate postural compensation due to pain

03. Publishing — GitLab Pages

Once the interface was complete and tested locally with Live Server, I published it through GitLab Pages so it can be accessed from any device — not just the development computer. The repository already existed as part of the Fab Academy GitLab instance, so publishing only required adding a pipeline configuration file.

  • 1Pushed index.html to the krypto-support repository on GitLab
  • 2Added a .gitlab-ci.yml pipeline file via the Pipeline Editor
  • 3The pipeline ran automatically, copying the file into a public/ folder and deploying it
  • 4Opened the published URL on iPhone Safari and tested the connection to the ESP32
  • 5Added to iPhone home screen as a PWA — it installs like a native app with no App Store
YAML # .gitlab-ci.yml — GitLab Pages deploy pages: stage: deploy script: - mkdir public - cp index.html public/index.html artifacts: paths: - public only: - main
colorpalette

GitLab pipeline — green

The CI/CD pipeline completed successfully. The green checkmark confirms the site was deployed and is live at the GitLab Pages URL.

colorpalette

Connected on iPhone

The app open in Safari on iPhone, connected to the ESP32 on the same WiFi network. The dashboard shows live data from the MPU-6050 sensor.

PWA — installing as a native app

The app includes the necessary meta tags for iOS to treat it as a Progressive Web App. From Safari, tapping Share → Add to Home Screen installs it with the Krypto logo as the icon and opens it without the browser chrome — it looks and feels like a native app. No App Store, no Xcode, no developer account required.

The one constraint is that the ESP32 connection only works when the phone is on the same WiFi network as the sensor. The login, profile, and PDF report features work from anywhere since they run entirely in the browser using localStorage.

04. Demo — App in action

The following video shows the full workflow: the ESP32 connected to the MPU-6050, the dashboard loading on iPhone, the live metrics updating as the sensor moves, and the PDF report being generated and downloaded.

🎬 demo-krypto.mp4 Reemplaza este bloque con tu video.
Ver comentarios en el HTML para las opciones.
colorpalette

ESP32 sending data

Serial Monitor showing the IP address assigned to the ESP32 after connecting to WiFi. This IP is entered in the dashboard to start the live feed.

colorpalette

Live data on iPhone

The dashboard open on iPhone Safari, connected to the ESP32. The metric cards and orientation bars update every second as the sensor moves.

05. Files

Source files and links for this week's work.


↓ index.html ↗ GitLab repository ↗ Live app