Mirror, mirror, on the wall...

For my final project, I made the magic mirror from Snow White!

A sneak peek...

Background

I initially wanted this mirror to be able to:

With the limits of this class (and my sanity), this list was triaged down to:

While I was able to implement some of the speech functions in previous weeks (see Interface and Application Programming week), I was not able to get to the text-to-speech component and decided to prioritize executing well on the fabrication methods I learned this semester rather than programming, which is closer to my prior background.

In my initial browsing and research, I found some visual replicas of the mirror that were selling for much higher than I had expected...

None of these had any electronics, though I had seen some examples of smart mirrors, with LED screens. These, shockingly, were selling for less than the Magic mirror replicas that I had seen!

With that, I had a useful visual reference for designing the mirror but also had a lot of creative freedom on how I wanted the "alive-ness" to come through.

I decided I wanted to use one-way reflective film so that light could shine through the mirror, but decided against using an LED screen (primarily because I didn't want to spend hundreds on one big enough). Instead, I decided to line the inside of the mirror with a LED strip, and have the lights shine onto some object inside. I found a privacy film for 6 dollars that would be reflective unless the light shining from the back was stronger than from the front, which should mean that with the LED strip on, whatever inside the mirror would be illuminated, but otherwise the viewer would just see a reflection of themselves.

Other than this film and the LED strip, I planned on finding everything I needed in the EECS shop, which proved to be more difficult than I had expected.

Frame

I have never suffered from Fusion as much as I did while designing the frame for this project. Initially, I had planned on 3D printing parts of the frame and then assembling.

However, I had the thought that it would be faster to CNC it, and so decided to poke around the EECS stockroom. I found this mysterious foam and inquired about it, which Anthony let me know was very expensive and called Renshape. However, since none of the classes used Renshape anymore, Anthony very kindly let me use it for my project!

I modified my CAD slightly, since I realized that Renshape was hand-carveable, and got to CNC-ing.

The CNC job took around an 1h30m, and turned out quite well! We used 3 drills: a 3/8th in and 1/4th in flat-endmill and 1/8th in ball-endmill for the details. The 1/8th wasn't able to get the details from the CAD, but it cleaned the frame beautifully! I got to work carving out the smaller details by hand.

Hand carving took quite some time, but I was really happy with the finish of the mirror. Once I finished carving it, I headed over to the CBA shop to paint it gold.

Body

The "Body" can be roughly split into 4 parts: the mirror, the mask, the pcb casing, and the mirror backing,.

Firstly, the mirror. Luckily, this process was relatively quick to complete. I found a slightly darkened, but mostly clear acrylic, and laser-cut it in the right dimensions. Then, taking the film I purchased, I applied it and smoothed out the air bubbles. It actually took me around 5 tries to apply the film, because it was really difficult to get the air bubbles out without crimping the film. I ended up being limited both by willpower and amount of film bought and came to peace with the slightly bubbled mirror surface.

Next, the mask. The bulk of the design and creation of this mask can be found in Wildcard week. For the final project, the mask was trimmed, sanded, then painted. For the assembly, I used epoxy glue to attach it to the acrylic backing.

The pcb casing was designed in Fusion then 3D printed. I included several holes on the sides of the casing for the wires, since at this point, I wasn't sure which electronics I would be able to complete in time. I was aiming to have the distance sensor, mic, LEDs, and speaker, but as you'll see in the 4th component of the body, a major hiccup in the mirror backing caused severe delays.

It ended up on the emptier side with fewer connected wires than anticipated, but here it is painted as well as holding its electronics.

And the bane of my existence, the mirror backing.

First, the CAD. The backing needed to go around the mirror, potentially hold the speakers, and also have a space for the wires.

The backing was way too big to be printed in one go, so it was split into quarters. I headed over to the CBA shop to print since I knew I would have a long print job, and thus began my misery. Out of the Prusas in the Mars lab, some had finnicky nozzles that would cause failed prints ~20% of the way in. However, the biggest issue I had (which I didn't realize until far too late) was that many of the filament rolls had small tangles, which would cause the filament to be pulled through irregularly at times, causing failed or bumpy prints.

I noticed some of these on the pcb case print, but the issue was so small I didn't have to worry too much about it. I successfully printed the backing after ~6 hours of printing, just to realize that the backing was actually too short. I then adjusted my CAD file accordingly and decided to try printing on the Bambu, which should have been more reliable. Kyle Horn helped me out a lot with loading in the print and troubleshooting filament issues, but unfortunately luck was not on my side and my 9 hour print job failed not once but twice. At this point, I was about 1.5 days behind when I thought I would have the frame printed, which made planning assembly significantly harder, and it was time to triage.

I eventually was able to print on the Bambu (9 hour print job). You can see the color change here because I had to switch out the filament in the middle since the brown PLA was finnicky.

I connected the four parts into the frame backing with fast-curing Epoxy and spray-painted it gold in the CBA lab, completing the backing body of the mirror.

Electronics

I used the Polulu VL53L1x breakout board for distance sensing and a Neopixel LED strip for lights. I chose the XIAO-ESP32S3 for my microcontroller, initially because I was planning on using its WiFi and I2S capabilities. For distance sensing and controlling LEDs, I realize it is definitely overkill, but I really enjoyed getting to figure out the WiFi and sound setup in networking week.

For my PCB, I decided to create a specialized breakout board for the XIAO-ESP32S3. I routed the pins for each device (LEDs, mic, distance sensor, speaker amplifier) to separate connectors, which allowed me to modularize easier.

I'm happy to report that after the trauma of milling failures during input devices, I cheerfully milled on the Roland, deburred, soldered, and was ready to move on.

I found this helpful tutorial on using the VL53L1x distance sensor. For the LEDs, I decided to connect a separate power supply. This was because the Neopixel library wasn't letting me control more than 60 LEDs, which I thought meant the power supply wasn't enough, but found out that it was an issue with the library instead. Once I switched to FastLED, I was able to program 150 LEDS.

I discovered that because I had designed the mirror backing with the speakers in mind, I actually couldn't lay the LEDS flat against the rim. I cut and soldered wires to the strip to have a bit more allowance with where I could stick the light strip.

A quick test that the LEDs still worked and that brightness was not affected!

#include "VL53L1X.h"
#include 

// VL53L1X sensor object
VL53L1X sensor;

// Pin and FastLED configuration
#define LED_PIN D10
#define NUMPIXELS 150
#define COLOR_ORDER GRB
#define LED_TYPE WS2812

CRGB leds[NUMPIXELS];

// State variables
bool mirror_on = false;

// Global variables for non-blocking fade
float brightness[NUMPIXELS]; // Current brightness level for each LED
float fadeStep = 0.1;        // Increment or decrement for fade (adjust as needed)
unsigned long lastFadeTime = 0; // Tracks time for non-blocking fades
bool fadingIn = true;         // State variable for direction of fade

// Sensor initialization function
void sensor_init(VL53L1X::DistanceMode range_mode, bool high_speed) {
    Wire.begin();
    sensor.setTimeout(500);
    if (!sensor.init()) {
    Serial.println("Failed to detect VL53L1X sensor!");
    while (1); // Halt the program if the sensor is not detected
    }
    sensor.setDistanceMode(range_mode);
    int budget = high_speed ? 33000 : 140000;
    sensor.setMeasurementTimingBudget(budget);
}

void setup() {
    Serial.begin(9600);
    
    // Initialize the VL53L1X sensor
    sensor_init(VL53L1X::Medium, false);

    // Initialize the FastLED strip
    FastLED.addLeds(leds, NUMPIXELS);
    FastLED.clear();

    // Initialize brightness array
    for (int i = 0; i < NUMPIXELS; i++) {
    brightness[i] = 0.0;
    }
}

void loop() {
    // Perform a single distance measurement
    int dist = sensor.readRangeSingleMillimeters();
    if (sensor.timeoutOccurred()) {
    Serial.println("Sensor timeout occurred!");
    return;
    }
    Serial.println(dist);

    if (dist > 90 && !mirror_on) {
    mirror_on = true;
    fadingIn = true; // Set fade direction to fade in
    } else if (dist < 90 && mirror_on) {
    mirror_on = false;
/    fadingIn = false; // Set fade direction to fade out
    }

    // Continuously fade in or out based on state
    smoothFade(fadingIn);
}

// Non-blocking smooth fade-in or fade-out
void smoothFade(bool fadeIn) {
    unsigned long currentTime = millis();

    if (currentTime - lastFadeTime >= 10) { // Adjust fade speed
    lastFadeTime = currentTime;

    // Adjust brightness incrementally for fade effect
    if (fadeIn) {
        for (int i = 0; i < NUMPIXELS; i++) {
        brightness[i] += fadeStep;
        if (brightness[i] >= 1.0) brightness[i] = 1.0; // Cap at max brightness
        }
    } else {
        for (int i = 0; i < NUMPIXELS; i++) {
        brightness[i] -= fadeStep;
        if (brightness[i] <= 0.0) brightness[i] = 0.0; // Cap at minimum brightness
        }
    }

    // Set colors with alternating sections of 5 LEDs
    for (int i = 0; i < NUMPIXELS; i++) {
        // Alternate every 5 LEDs (green for 5, purple for next 5, and so on)
        CRGB baseColor = ((i / 5) % 2 == 0) ? CRGB(0, 255, 0) : CRGB(128, 0, 128);  // Alternates every 5 LEDs
        leds[i] = baseColor.fadeToBlackBy(255 - (brightness[i] * 255)); // Apply fade to the color
    }

    FastLED.show();
    }
}
    

Assembly

Even thinking a lot about supply-side time management, I was struck by how, since I wasn't the most familiar with each fabrication process, I didn't accurate estimate how much time everything would take, so I ended up having less time for Assembly that I had assumed.

At this point, I had many different components, and it was time to put them all together. I started with the frame, mirror, and body. I had planned on drilling into the Renshape, but the acrylic mirror was heavier than I thought, so I decided to use epoxy to connect the body and mirror to the frame front.

I then attached the LEDs and distance sensor. I printed a couple of fasteners as well, which I also attached using epoxy. These were to position the distance sensor, and also do a bit of wire management.

I attached the backing with epoxy, and slotted wires through the pcb casing, which was attached with screws. You can see a couple more fasteners to direct the wires! I attached the USB-C cable within the casing, so that to turn on the mirror, you would just have to plug the LED power supply into a socket and the USB-A side of the cable into a laptop.

After a bit of debugging and checking connections, the distance sensor and lights were functioning properly, and when not engaged, the mirror showed all the fabrication processes we learned in class! Overall, I was very satisifed with this mirror as a final project. I would love to integrate speech functions, which would mean connecting the mic and speakers to the PCB and utilizing the WiFi functions of the ESP32S3. Definitely a future project, now that I feel much more comfortable with fabrication and electronics.