Fab Academy 2026 · Final Project · Fab Lab Puebla

Somnia

Interactive Ambient Holographic Lamp

AuthorGreayshell Cielo Gómez
MCUSeeed XIAO ESP32-C6
DisplayGC9A01 1.28" TFT
LightingWS2812B NeoPixel
LabFab Lab Puebla
scroll
Final Project · Video & Slide
Moodboard
01 · Concept & Motivation

✦ How It Started?

For many children, nighttime can feel unfamiliar, vulnerable and sometimes even frightening. When the lights go out, ordinary objects can seem different, shadows become larger and the comfort of daytime disappears. This idea emerged from a personal experience with my younger sister, who often felt uneasy when the lights were turned off.

Watching those moments made me wonder whether a lamp could become more than a source of illumination. Could it create a sense of presence? Could light itself become a companion during those quiet moments before sleep?

✦ Project Proposal

Somnia is an interactive ambient lamp designed to provide companionship during nighttime moments through light, holographic visualization and intuitive interaction. By combining digital fabrication, embedded electronics and programming, the project creates a calming environment where light becomes both functional and experiential.

✦ Inspiration & References

The project was inspired by kinetic product design, atmospheric lighting installations, calm technology concepts and Pepper's Ghost holographic systems. Existing references often separate movement from emotional interaction, so this project aims to integrate both into a single cohesive object.

Moodboard
02 · Initial Sketches & Form Development

✦ Initial Sketches & Form Development

Multiple sketches and iterations were developed to explore the relationship between the holographic system and the overall emotional language of the lamp. The final direction evolved toward a soft mushroom-like silhouette with illuminated floating rings and a holographic central structure.

Initial sketches
03 · What Will You Design?

✦ What will you design?

All physical and electronic components are designed from scratch. The following elements were developed during the project:

Enclosure
  • Lamp outer structure
  • Mushroom-inspired form
  • 3D printed parts
Holographic Chamber
  • Acrylic reflector 45°
  • Display housing
  • Laser cut base
⚡︎
Electronics
  • Custom PCB
  • Touch interface
  • WiFi web interface
✴︎
Lighting
  • NeoPixel system
  • Color modes
  • Diffuser dome
04 · Interaction Logic

✦ Interaction Logic

The interaction system was designed to provide a simple and intuitive experience. Through touch and WiFi connectivity, users can control the lamp's lighting and holographic effects, allowing the different visual subsystems to work together as a unified ambient experience.

● OFF ✦ SINGLE TOUCH TTP223 touch sensor ● ON ✦ NEOPIXELS WS2812B ring · pin D6 → Activates on power on → Default: warm white → Ambient ring lighting ✦ Single tap cycles mode cycles color mode ● 1. White light ● 2. Warm light: yellow→orange ● 3. Cool light blue→purple ✦ HOLOGRAM GC9A01 + acrylic mirror → Always active when ON → Pepper's Ghost visual effect → Image in display → Image floats in mirror ✦ WIFI CONTROL Web interface → Change LED color & mode → Change static image → Open from phone browser ○ TURN OFF Single touch while ON → LEDs turn off → Display turns off → Returns to OFF state ● OFF
✦ Final Project

Interaction Logic

The lamp operates through two states: OFF and ON. A touch on the TTP223 sensor activates the NeoPixel lighting and holographic display. Additional touches cycle through three lighting modes, while the GC9A01 display generates static images for the Pepper's Ghost holographic effect through the web interface.

05 · Materials & Components

✦ What materials and components will be used?

ComponentTypeApplicationMaterial
Seeed XIAO ESP32-C6ElectronicsMCU: lighting, G09A1, touch, WiFiPCB module
Custom PCBElectronicsConnects and distributes all subsystemsCopper-coated fiberglass
NeoPixel Ring WS2812BElectronics RGB lighting: multiple color modes16-LED PCB ring
Round Display GC9A01ElectronicsHolographic static image content1.28" TFT display
TTP223 Touch SensorElectronicsCapacitive touch: power and modesTTP223 touch sensor
Acrylic Reflector 45°Laser CuttingCreates Pepper's Ghost holographic effectTransparent acrylic 2mm
BaseLaser CuttingStructural base for the lampMDF 3mm
Dome Diffuser3D PrintingDiffuses NeoPixel lightingPETG filament
Structural Rings3D PrintingMain visual + superior lamp bodyPLA filament
Internal Supports3D PrintingHold display and electronics in placePLA filament
Wooden SticksStructureConnect and align structural ringsWood, 9 mm ø
Glass vaseStructureEnclosure for the Pepper's Ghost system90 mm ø
06 · Estimated Cost

✦ Estimated Components Cost

ComponentEstimated Cost (USD)
Seeed XIAO ESP32-C6$8–12
GC9A01 Round Display$5–8
NeoPixel Ring WS2812B$5–10
TTP223 Touch Sensor$1–3
Acrylic Sheets$10–20
3D Printing Filament (PLA+PETG)$10–15
Glass Vase$5–8
MDF + Wooden Dowels$5–8
PCB Materials$10–15
Misc (wires, connectors)$5–8
✦ Estimated Total$64–107 USD
07 · Project Timeline
TaskDescriptionTimeline
Protoboard integrationConnect all components and test full system before PCB manufacturingMay 18
Hologram testTest GC9A01 display with 45° reflector and evaluate holographic visibilityMay 24
WiFi interfaceDeploy web server on ESP32-C6 and test remote control from phone browserMay 20–22
Enclosure modelingContinue 3D modeling of dome, structural rings and internal supportsMay 21–27
PCB design in KiCadDesign custom PCB routing all connections into a compact boardMay 24–29
PCB fabricationMill and solder the custom PCB at Fab Lab PueblaJune 1-2
Final assemblyAssemble all 3D printed, laser cut and electronic components into the final enclosureJune 5–6
Final documentationComplete all documentation pages and record final project videoJune 4–8
08 · 3D Design & Enclosure

✦ Modeling the Enclosure

The enclosure was designed in OnShape. The mushroom-inspired form references soft organic shapes while maintaining structural rigidity. The lamp is composed of three stacked sections: the upper translucent dome (diffuser), the central holographic chamber, and the lower base (electronics compartment). For additional Onshape details go to my Week 2 .

Dome

Translucent Dome

Printed with PETG filament and 1 mm thickness for optimal light diffusion.

Chamber

Holographic Chamber

Cylindrical cavity to eliminate the entry of light. The GC9A01 is at the compartment of the base.

Base

Electronics Base

Base for mounting the PCB and the orderly installation of components.

✦ 3D Print Settings

Printer: Bambu Lab P1S — PLA / PETG filament

Layer height: 0.2mm standard · 0.12mm detail (dome)

Infill: Gyroid & Grid

Supports: Yes

Moodboard

✦ Design Reflection

The first dome print came out opaque since it was too thick. Also, it was sent to print without supports and this resulted in a poor finish. That's why I changed from 2mm to 1mm wall thickness, and that gave the right diffusion without losing structural integrity. Also, to easily remove the supports, I set Top Z distance to 0.3 and Top interface layer to 3. For detailed 3D printing parameters, see my Week 5.

✦ Iterative Design Process

Throughout the design process, several prototypes were produced to test different tolerances for the round display and touch sensor. These iterations helped me to optimize the internal layout and ensure reliable integration of all components within the enclosure.

Moodboard
09 · Laser Cutting

✦ Cutting the Reflector & Base

Two components were produced on the laser cutter: the acrylic 45° reflector that creates the Pepper's Ghost effect and the MDF structural base ring. Both were designed in Onshape and exported as DXF. For detailed laser cutting documentation, go to my Week 3.

acrylic sheet

Acrylic Reflector

0.2 mm clear acrylic for insertion into a glass (Pepper's Ghost Holographic System)

finished pieces: reflector + MDF base

MDF Base

3mm MDF. Base with wooden sticks for the lamp. Covered with two strips of wood veneer.

✦ Process Reflection

Getting the acrylic reflector angle right took three test cuts. The 45° angle needs to be exact, even 2° off visibly shifts the projected image out of alignment with the chamber center.

10 · Electronics & Custom PCB

✦ Designing the PCB

The custom PCB was designed in KiCad and milled on the Roland SRM-20 at Fab Lab Puebla. The board routes all connections from the XIAO ESP32-C6 to the four subsystems: GC9A01 display, NeoPixel ring, TTP223 touch sensor and 5V 2A power supply. For detailed PCB design and fabrication documentation, go see my Week 6 and Week 8.

✦ Schematic in KiCad

The custom PCB is built around a Seeed XIAO ESP32-C6 and connects the lamp’s main subsystems: the TTP223 touch sensor, the NeoPixel ring and the GC9A01 holographic display.

Moodboard
◆ What I will be using?
  • 1 Seed XIAO ESP32-C6
  • 1 Resistor SMD (330Ω)
  • 13 Male Header Pins
  • 14 Female Header Pins

✦ PCB Layout

The PCB was designed in a custom star-shaped form with the Seeed XIAO ESP32-C6 located at its center, with the components arpund it, while 0.8 mm traces provide reliable routing and fabrication. I also added the project's name on it.

Moodboard

✦ Pin Mapping — XIAO ESP32-C6

Component Signal XIAO Pin GPIO Notes
GC9A01 DisplaySCL / SCKD8GPIO19SPI clock
GC9A01 DisplaySDA / MOSID10GPIO18SPI data
GC9A01 DisplayCSD2GPIO2Chip select
GC9A01 DisplayDCD3GPIO21Data / command
GC9A01 DisplayRSTD1GPIO1Power
GC9A01 DisplayVCC3.3V-Reset
NeoPixel RingDIND0GPIO0WS2812B data in
TTP223 TouchSIGD4GPIO22Digital input
NeoPixel RingVCC5V-Power
GC9A01 / TTP223VCC3.3V-Power
PCB MILLING — Roland SRM-20 FINISHED PCB

✦ PCB Reflection

The PCB was designed using Gerber2png and modsproject, then printed on a Roland SMR-20 and finally soldered with the required components. For more information, see my Week 8.

11 · Embedded Programming

✦ Display Selection

I selected a 1.28" GC9A01 circular display to generate the visuals for the Pepper's Ghost system. Although I initially referred to it as an OLED, I later discovered it is actually a TFT LCD display that communicates via SPI (MOSI and SCK signals) and operates at 3.3V, making it directly compatible with the XIAO ESP32-C6.

✦ First Hardware Issue

The display was first connected on a breadboard (just as a test) using the hardware SPI pins of the XIAO ESP32-C6. During the first power-up test, I accidentally created a short circuit that caused both the jumper wires and the display to heat up. Fortunately, the short circuit was only momentary and the display didn't suffered permanent damage.

Moodboard
✦ GC9A01 CIRCULAR DISPLAY

Library Selection

I first tried using TFT_eSPI, but it wasn't compatible with the MCU and generated compilation errors. I then switched to the Arduino GFX Library, which supports the GC9A01 and displayed graphics on the screen.

✦ Preparing Graphics for the Display

To create the holographic content, I designed three graphics in Procreate using a completely black background. This was important because only the bright elements should remain visible when reflected by the Pepper's Ghost system.

Dome

✦ Dream Star

A playful star that accompanies children during quiet nighttime moments.

Chamber

✦ Dream Moon

A gentle moon that helps create a relaxing and comforting atmosphere.

Base

✦ Dream Sun

A warm sun that brings a feeling of happiness and safety to the experience.

✦ Memory Optimization After displaying images, I encountered another challenge related to memory usage. My original graphics were exported at 240 × 240 pixels, matching the resolution of the display. However, storing multiple images at this size exceeded the flash memory of the ESP32-C6. To solve this issue, I compared different image resolutions:
Resolution
Result
240 × 240
Too large
160 × 160
Fits, but appears small
200 × 200
Selected solution

✦ Final Resolution

The final graphics were resized to 200 × 200 pixels, which preserved visual quality while keeping memory usage within the limits of the microcontroller.

✦ Image Conversion Workflow

The GC9A01 display cannot directly read PNG or JPG images, so every graphic needed to be converted into RGB565 arrays stored inside the Arduino program (file.h).

I experimented with different online conversion tools, including LVGL Image Converter and FileToCArray. While both tools generated usable files, they still required additional manual modifications.

✦ Arduino Code C++

This was the code used to program the screen to project the example image.

    
    
#include <Arduino_GFX_Library.h>   // Library for the GC9A01 display
#include "flower_orange.h"         // Converted image stored as RGB565 array

#define TFT_CS   D2                // Chip Select pin
#define TFT_DC   D3                // Data/Command pin
#define TFT_RST  D1                // Reset pin

// Create the SPI communication bus
Arduino_DataBus *bus = new Arduino_HWSPI(TFT_DC, TFT_CS);

// Initialize the GC9A01 display
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RST, 0, true);

void setup() {
  gfx->begin();                    // Start the display
  gfx->fillScreen(0x0000);         // Clear screen with black background

  // Draw the image at position (0,0)
  gfx->draw16bitRGBBitmap(
    0,
    0,
    (uint16_t*)flower_orange,
    240,
    240
  );
}

void loop() {
  // No repeated actions required
}
✦ Note: It's important that the .h file is in the same Arduino IDE folder as the sketch with the code that the display will receive.
Command
Description
#include <Arduino_GFX_Library.h>
Imports the graphics library used to control the display.
#include "flower_orange.h"
Loads the image data stored as an RGB565 array.
Arduino_HWSPI()
Creates the SPI communication bus.
Arduino_GC9A01()
Initializes the GC9A01 display driver.
gfx->begin()
Starts communication with the display.
gfx->fillScreen()
Fills the screen with a selected color.
gfx->draw16bitRGBBitmap()
Draws a bitmap image stored in RGB565 format.
setup()
Runs once when the microcontroller starts.
loop()
Repeats continuously after setup is completed.

✦ Conversion Script

This custom Python tool converts images into RGB565 format and generates Arduino-ready .h files, making it easier to display custom graphics on the GC9A01 screen.

    
    

import tkinter as tk                     # Create the graphical interface
from tkinter import filedialog, messagebox
from PIL import Image                    # Open and edit images
import os                                # Handle file names and paths

def convert_image():

    # Select an image file
    image_path = filedialog.askopenfilename(
        title="Select an image",
        filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")]
    )

    if not image_path:
        return

    # Open image and convert it to RGB
    img = Image.open(image_path).convert("RGB")

    # Resize image to fit the display resolution
    img = img.resize((240, 240), Image.LANCZOS)

    # Generate a valid variable name
    file_name = os.path.splitext(os.path.basename(image_path))[0]
    variable_name = file_name.replace(" ", "_").replace("-", "_")

    # Choose where to save the output file
    output_path = filedialog.asksaveasfilename(
        title="Save .h file",
        defaultextension=".h",
        initialfile=variable_name + ".h",
        filetypes=[("Header file", "*.h")]
    )

    if not output_path:
        return

    # Create the header file
    with open(output_path, "w") as f:

        # Write file header information
        f.write("#pragma once\n")
        f.write("#include <pgmspace.h>\n\n")

        f.write(f"#define {variable_name.upper()}_WIDTH 240\n")
        f.write(f"#define {variable_name.upper()}_HEIGHT 240\n\n")

        # Create RGB565 image array
        f.write(f"static const uint16_t {variable_name}[] PROGMEM = {{\n")

        values = []

        # Convert every pixel to RGB565 format
        for y in range(240):
            for x in range(240):

                r, g, b = img.getpixel((x, y))

                rgb565 = (
                    ((r & 0xF8) << 8)
                    | ((g & 0xFC) << 3)
                    | (b >> 3)
                )

                values.append(f"0x{rgb565:04X}")

        # Write pixel values into the array
        for i in range(0, len(values), 16):
            f.write("  " + ", ".join(values[i:i+16]) + ",\n")

        f.write("};\n")

    # Show confirmation message
    messagebox.showinfo(
        "Done!",
        f"File saved as:\n{output_path}\n\nArray name: {variable_name}"
    )

# Create application window
window = tk.Tk()

window.title("GC9A01 Image Converter")
window.geometry("400x200")
window.resizable(False, False)

# Title label
tk.Label(
    window,
    text="Image to .h Converter for GC9A01",
    font=("Arial", 12, "bold"),
    pady=20
).pack()

# Description label
tk.Label(
    window,
    text="Convert PNG or JPG images into RGB565 format\nfor Arduino and the GC9A01 display.",
    font=("Arial", 10),
    justify="center"
).pack()

# Conversion button
tk.Button(
    window,
    text="Select Image and Convert",
    command=convert_image,
    font=("Arial", 12),
    bg="#4CAF50",
    fg="white",
    padx=20,
    pady=10
).pack(pady=20)

# Run the application
window.mainloop()
Command
Description
tkinter
Creates the graphic interface for the converter.
filedialog.askopenfilename()
Opens a window to select the input image.
Image.open()
Opens the selected image file.
.convert("RGB")
Converts the image to RGB color mode.
.resize()
Resizes the image to 240 × 240 px.
asksaveasfilename()
Lets the user choose where to save the .h file.
rgb565
Converts each pixel into RGB565 format.
PROGMEM
Stores the image data in the microcontroller flash memory.
messagebox.showinfo()
Shows a confirmation message when the file is created.
mainloop()
Keeps the interface running.

✦ Pepper's Ghost Effect Test

After generating the image, I made some quick prototypes to create the Pepper's Ghost effect using acetate and a small glass. In my first attempt, I placed the acetate at a 45-degree angle and above the rim of the glass, but this prevented the floating effect. I then decided to raise the acetate, and that's how I achieved the desired effect.

Moodboard

✦ Components Integration

Once the individual components were working independently, I connected the entire system on a breadboard to verify that all modules could operate together. This included the XIAO ESP32-C6, the GC9A01 display, the NeoPixel ring and the TTP223 touch sensor.

✦ Power Distribution

To simulate the final operating conditions, the system was powered using a 5V 2.4A wall adapter (Type C charger) connected directly to the XIAO ESP32-C6.

PCB MILLING — Roland SRM-20 FINISHED PCB

✦ PCB Integration

After validating the circuit on the breadboard, the components were transferred to the custom PCB. The lamp was programmed with the three designed images, plus three light modes in addition to white: yellow-orange, blue-purple, pink-white, which change with long touches on the sensor. Turning the whole system on/off is done with a subtle touch.

✦ Interface + MQTT Communication + GitLab

To enable communication between the lamp and the web interface, I implemented an MQTT-based system using the public HiveMQ broker. This architecture allows the ESP32-C6 and the dashboard to exchange information in real time through dedicated topics. For the initial interface concept and design reference, see my Week 11.

Topic
Function
somnia/control
Power (on/off) and control commands
somnia/color
Lighting color (white, sunset, aurora, pink)
somnia/pattern
Lighting effects (solid, respiration, wave)
somnia/screen
Hologram graphics (moon, star, sun, off)
sleep/brightness
Brightness control (0 to 100)
somnia/state
Current system status (ESP32-C6)
✦ Interface creation process: The following images show the process of creating the interface using MQTT communication, designing the interface and uploading the HTML to GitLab.

✦ Somnia's HTML + URL

This was the code used to program the screen to project the example image. Click here to go to the web page: somnia-interface.

Moodboard
✦ Timer System
  
  
<style>

/* Import custom fonts */
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=Inter:wght@300;400;500&display=swap');

/* Main color palette */
:root{
  --bg:#0d0d14;
  --card:#16161f;
  --text:#f0ede8;
  --muted:#6b6880;
  --accent:#B6E5ED;
}

/* General page styling */
body{
  background:var(--bg);
  color:var(--text);
  font-family:'Inter',sans-serif;
}

/* Somnia title */
.title{
  font-family:'Syne',sans-serif;
  font-size:60px;
  background:linear-gradient(135deg,#fff 30%,#B6E5ED 70%,#f4a7c3);
  -webkit-background-clip:text;
  -webkit-text-fill-color:transparent;
}

/* Dashboard cards */
.card{
  background:var(--card);
  border:1px solid #2a2a3a;
  border-radius:18px;
  padding:18px;
}

</style>
Command
Description
@import url()
Loads the fonts used in the interface.
:root
Defines the main color palette.
--accent
Stores the main accent color.
body
Sets the general background, text color, and font.
.title
Styles the Somnia title with a gradient effect.
.card
Creates the rounded control panels.
border-radius
Gives the interface a softer visual style.
✦ MQTT Connection
  
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>

var BROKER = 'broker.hivemq.com';
var PORT = 8884;

// Connect to the MQTT broker
function conectar(){

  // Generate a unique client ID
  var id = 'somnia' + Math.floor(Math.random()*9999);

  // Create MQTT client
  mqttClient = new Paho.MQTT.Client(BROKER, PORT, id);

  mqttClient.connect({

    // Execute when connection succeeds
    onSuccess:function(){

      // Subscribe to status topic
      mqttClient.subscribe('somnia/estado');

    },

    // Enable secure connection
    useSSL:true

  });

}
Command
Description
Paho.MQTT.Client()
Creates the MQTT client.
BROKER
Defines the MQTT broker address.
PORT
Defines the communication port.
connect()
Connects the dashboard to the broker.
subscribe()
Listens for messages from a topic.
useSSL:true
Enables secure communication.
✦ Publishing Commands
    
    function pub(topic,message){
  var msg = new Paho.MQTT.Message(String(message));
  msg.destinationName = topic;
  mqttClient.send(msg);
}
Command
Description
Paho.MQTT.Message()
Creates a new MQTT message.
destinationName
Selects the destination topic.
send()
Sends the message to the broker.
✦ Power Control
    
    function togglePower(){
  encendida=!encendida;
  pub('somnia/control',
      encendida ? 'sol' : 'off');
  }
Command
Description
togglePower()
Turns the lamp on or off.
encendida
Stores the current power state.
pub()
Sends the command through MQTT.
✦ Color Selection
  
  function setColor(c){
  pub('somnia/color',c);
}
Command
Description
setColor()
Selects a lighting color.
somnia/color
MQTT topic for color control.
pub()
Sends the selected color.
✦ Timer System
  
  function toggleTimer(){
  timerInterval=setInterval(function(){
    timerSegs--;
    if(timerSegs<=0){
      pub('somnia/control','off');
    }
  },1000);
}
Command
Description
setInterval()
Executes code repeatedly.
timerSegs--
Decreases the remaining time.
if(timerSegs<=0)
Checks if the timer ended.

✦ Final Result

The final interface successfully integrates MQTT communication, wireless control, and visual design into a single platform.

✦ Arduino IDE (C++) Finally, the code was uploaded to Arduino, integrating the rest of the components and functions designated for the lamp..
  
  /* SOMNIA - Main System Functions */

#include <WiFi.h>                  // WiFi connection
#include <PubSubClient.h>          // MQTT communication
#include <Adafruit_NeoPixel.h>     // NeoPixel LED control
#include <Arduino_GFX_Library.h>   // GC9A01 display control

#include "moon.h"                  // Moon graphic
#include "star.h"                  // Star graphic
#include "sun.h"                   // Sun graphic


/* MQTT Message Handling */

void callback(char* topic, byte* payload, unsigned int length) {

  String msg = "";

  // Convert received message into a string
  for (int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }

  String currentTopic = String(topic);

  // Update selected color
  if (currentTopic == "somnia/color") {
    currentColor = msg;
  }

  // Update selected lighting pattern
  if (currentTopic == "somnia/pattern") {
    currentPattern = msg;
  }

  // Update selected holographic graphic
  if (currentTopic == "somnia/display") {
    currentScreen = msg;
    previousScreen = "";
  }
}

/* MQTT Broker Configuration */

// Configure HiveMQ broker
client.setServer(broker, 1883);

// Assign callback function
client.setCallback(callback);


/* Display Graphics */

void showImage(String imageName) {

  // Clear display
  gfx->fillScreen(0x0000);

  // Show selected image
  if (imageName == "moon") {

    gfx->draw16bitRGBBitmap(
      20, 20,
      (uint16_t*)moon,
      200, 200
    );

  } else if (imageName == "star") {

    gfx->draw16bitRGBBitmap(
      20, 20,
      (uint16_t*)star,
      200, 200
    );

  } else if (imageName == "sun") {

    gfx->draw16bitRGBBitmap(
      20, 20,
      (uint16_t*)sun,
      200, 200
    );

  }
}


/* Touch Sensor Interaction */

void handleTouch() {

  bool touching = digitalRead(PIN_TOUCH);

  // Detect first touch
  if (touching && !touchDetected) {

    touchStartTime = millis();
    touchDetected = true;

  }

  // Detect touch release
  if (!touching && touchDetected) {

    unsigned long duration =
      millis() - touchStartTime;

    touchDetected = false;

    // Short touch → ON / OFF
    if (duration < 500) {

      lampOn = !lampOn;

    }

    // Long touch → Change color
    else {

      colorIndex =
        (colorIndex + 1) % 4;

      currentColor =
        colors[colorIndex];

    }

  }
}


/* Main Program Loop */

void loop() {

  // Reconnect if MQTT is disconnected
  if (!client.connected()) {
    reconnect();
  }

  // Process MQTT messages
  client.loop();

  // Read touch sensor
  handleTouch();

  // Update display when graphic changes
  if (
    screenEnabled &&
    currentScreen != previousScreen
  ) {

    showImage(currentScreen);

    previousScreen =
      currentScreen;

  }

  // Stop if lamp is OFF
  if (!lampOn) return;

  // Execute selected lighting pattern
  if (currentPattern == "solid") {

    solidPattern();

  }

  else if (
    currentPattern == "breathing"
  ) {

    breathingPattern();

  }

  else if (
    currentPattern == "wave"
  ) {

    wavePattern();

  }

}
Command
Description
WiFi.h
Connects the ESP32-C6 to WiFi.
PubSubClient.h
Enables MQTT communication.
callback()
Receives MQTT messages and updates the system.
client.setServer()
Defines the MQTT broker address and port.
broker
Stores the HiveMQ server address.
1883
MQTT communication port used by the ESP32.
client.setCallback()
Defines the function that processes incoming MQTT messages.
mostrarImagen()
Displays moon, star, or sun graphics.
manejarTouch()
Reads short and long touch gestures.
patronSolido(), patronRespiracion(), patronOla()
Displays stable, breathing, wave light effect

✦ Programming Reflection

The most challenging part of this process was converting the graphics into a format compatible with the display. I also encountered memory limitations on the ESP32-C6, which required reducing the image resolution to fit within the available storage. Although this prevented me from implementing animations, it helped me better understand the hardware constraints and optimize the system. Overall, I am satisfied with the final result and what I learned throughout the process

12 · System Integration

✦ Putting It All Together

For detailed system integration documentation, go see my Week 15.

Step 01

Base Assembly

PCB mounted on a lamp base. USB-C cable routed through the base. Touch sensor fixed to the front face.

Step 02

Display + Reflector Mounting

GC9A01 placed on its printed mount centered below the camera. Acrylic reflector bent at 45° inside a glass beaker. Neopixel cables run up behind the lamp body wall.

Step 03

NeoPixel Ring + Structural Rings

NeoPixel rings pass through the top rings and snap into the circular base.

Step 04

Dome + Final Closure

Translucent dome placed on top. Final functional test: touch to power on, cycle through modes, verify WiFi connectivity, confirm hologram projection alignment.

ASSEMBLY IN PROGRESS HOLOGRAM TEST
13 · Gallery

✦ Gallery

Below are images of the final result of somnia.

Subsystem 01

GC9A01 Hologram Visual

The display renders static image. *Text was added to indicate the image names.

GC9A01 IN ACTION

Subsystem 02

NeoPixel Color Modes

Three color modes cycle on each touch event besides white (default): warm yellow to orange, cool blue to purple and pink to white. It integrates a gentle breathing so the lamp always feels alive.

GC9A01 IN ACTION

Subsystem 03

WiFi Web Interface

The ESP32-C6 serves a web interface on its local network exposing light mode selector and hologram static image switcher.

GC9A01 IN ACTION
14 · Processes Used
ProcessApplicationTool / Software
2D DesignLaser cutting files, PCB layout, technical drawingsKiCad
3D DesignLamp enclosure, structural rings, internal supports, domeOnShape / Shapr3D
3D PrintingAll printed structural and enclosure componentsBambu Lab, PLA / PETG
Laser CuttingAcrylic reflector, structural base ringsEpilog / Trotec
Electronics ProductionCustom PCB milling and solderingRoland SRM-20
Embedded ProgrammingESP32-C6 firmware — NeoPixel, display, WiFi, touchArduino IDE / C++
System IntegrationAssembly, wiring and functional testingMultimeter
15 · Final Reflection

✦ What worked well

The Pepper’s Ghost effect worked successfully, creating a convincing floating visual. The integration between the lighting system, display, touch sensor and web interface also worked well, allowing the lamp to be controlled as a complete interactive system.

✦ What I'd do differently

I would explore a more powerful microcontroller to support short animations and better visual content. This would expand the storytelling possibilities while maintaining a good system performance.

✦ Future Directions

Future versions of Somnia could include a real-time clock module for a gentle sunset simulation before nighttime. I would also like to further explore the integration of servo motors to introduce meaningful movement and enhance the overall interaction experience.

✦ Thank you Fab Academy 2026

✦ Final Thoughts

Throughout this project, I learned that the best ideas evolve through iteration, problem solving, and adaptation. Although several aspects of Somnia changed during development, each challenge helped improve the final result. As someone with no previous experience working with electronics, I am especially proud of what I accomplished throughout the Fab Academy program. Learning how to design and manufacture a PCB, program a microcontroller, integrate different electronic systems and attend unexpected problems pushed me far beyond my comfort zone. While there is still much more to learn, this project represents an important first step into a field that was completely unfamiliar to me just a few months ago. More than the final result itself, I value the curiosity, persistence, and confidence that I gained along the way. Thank you, Fab Academy, for challenging me to keep experimenting, learning and discovering what I am capable of creating.

✦ Download Here!

In this section, you can find the downloadable source files developed during this project.

ZIP

SOMNIA'S FILES

Download Files