3/4 view of the sunset receipt printer turned on, connected to power.
This project visualizes the passing of the day with an LED strip that shifts color depending on the hour. The printer prints a receipt with the sunset time an hour before sunset to remind me to take a daily walk. This box will sit on my studio desk, where I spend most of my day.
Top view with the lid off, displaying the electronic configurations inside the box. The thermal printer occupies most of the space at the bottom of the box. The Arduino and Real-Time Clock sit on top of the thermal printer. The connections are soldered via a small protoboard placed sideways on the left of the printer. An LED strip is wrapped around the bottom of the thermal printer.
Back view of the box with a print button and a latching button that puts the LED on demo mode (displaying a full range of color gradient throughout the day in 30 seconds).
A 3/4 view of the project in action: printing a sunset receipt and LEDs that light up corresponding to time of day. The Arduino takes Pittsburgh's longitude and latitude and a Real-TIme Clock to keep track and calculate the sunset time of everyday. The exterior of the box is made of 6mm plywood and white acrylic that is semi-transparent to show the light from the LED strips.
The printed receipt from the box. This receipt prints automatically everyday an hour before sunset, or can be manually triggered through pressing the print button on the back of the box.
In this video, I'm showing the system in demo mode that condenses what is supposed to happen in a day to 30 seconds: the LED strip displays a range of colors corresponding to the time of day. I manually pressed the "print" button on the back panel of the box to print an additional sunset receipt. At the end, I ripped the receipt from the box opening to keep.
The receipt is printed from an opening on the front panel of the box that is cut to the width of the receipt paper.
Unsuccessful experimentation with implementing a manual print option by adding tactile buttons. The button did not work properly due to connection issues, possibly because I did not insert it into the breadboard correctly.
First iteration of electronic layouts with a printer (not featured in the picture), breadboard for connection, tactile button, real-time clock, and an arduino connected to laptop for power.
The process of assembling the exterior of the box while figuring out the placement of electronics. The box is glued together with Elmer's Glue as I found that it worked better than wood glue.
I taped together the electronics in a way that I would place them into the exterior. After experimenting with different configurations, I found that the breadboard takes up too much space, and wires often come loose, so I decided to solder the wires for the final version.
A process image of me gluing together the box. Because the box contains both acrylic and plywood pieces, I painted on gel super glue to attach the various parts.
Overall, the hardest part was figuring out how to place the hardware in a way that would fit nicely and snugly in the box container, and also tailoring the container to have the necessary openings for the push buttons and external power supplies. I did two iterations of the laser-cut box because I failed to consider the thickness of the LED strips and the back parts of the buttons, so the first iteration left no space for the components. These are problems that I did not think I would encounter before I started putting together the physical parts, so I think that it is a nice experience for me to learn the process of navigating through hardware and software and speed up my workflow in the future.
Learning from the previous projects, the coding aspect of this project was much simpler to debug and reiterate because I learned how to code one step at a time to test that each component works individually before putting everything together.
3. Discussion
(instructions are embedded below)
From ideation to in-class crits to implementation, I learned a lot throughout each step of the process. I found the in-class crits surprisingly helpful because they helped me consider important aspects that I would've otherwise forgotten. For example, Zar mentioned in the 10/29/25 peer feedback session, "You could maybe add a manual override print button that will print out the sunset time even if it’s more than an hour before, so that if you happen to not be near the printer an hour before, you can still check." This comment was extremely helpful because I did not consider the big picture of my day-to-day life while interacting with the system, of when I might have times that I would want a sunset receipt, either to show another friend or have a physical reminder to take with me if I will not be sitting at the studio desk. Another comment that I vividly remembered from the feedback session was when Helios proposed the idea of incorporating "a display to make it a real-time clock while it wasn’t printing." I did not end up taking his suggestion because I believe that adding this feature would take away the tangible and whimsical aspect of the sunset receipt printer, which gives a surprising physical souvenir every day to remind you to be present in the world around us. I feel that incorporating a digital display would not match my original idea and would make it feel more like an existing phone/ digital clock.
Reflecting on the project, I'm generally happy with how it turned out, especially because it functions without any bugs. It satisfied my primary goal of creating a tangible interaction that encourages me to step outside and connect to the natural cycle of the sun. However, I do feel that I can work more on the fabrication of the container to make it seem more refined. At this stage, I felt that the figure joints felt too raw and not as complete as I would like them to look. If I had more time, I would increase the distance between the LED strips and the acrylic to better diffuse the lights and use a frosted acrylic for a smoother look. If possible, I would also like to experiment with woodworking instead of laser cutting to further customize the look of the box.
Throughout this process, I learned that my workflow can sometimes constrain my creative ideas and often compromises the interaction and fabrication design for technical feasibility. For example, while making a physical container for the electronics, I realized that it takes much longer than I anticipated, especially building the box in fusion, and making consideration of the spacing for each individual component. It is also much more time-consuming because there are physical and timing constraints on when I can use the laser cutter, and going back and forth to reiterate the box dimensions and form. If I could advise my past self, I would consider all aspects of building the project from software to hardware to fabrication equally, and scope it to the limitations while maximizing the creative output.
For next steps, I plan on iterating on this project by adding more features to the box to make it feel more interactive, rather than a passive printing machine. I would also hope to work more on creating a better container for the system to make it feel like a polished table add-on that would add to the aesthetic appearance of my studio table.
/*
Project Title: Sunset Receipt + LED Time Lamp
Author: Yutong Zhen
This project uses a DS3231 real-time clock, a thermal printer,
and a NeoPixel LED strip to create a physical "sunset reminder" device.
Each day, one hour before local sunset in Pittsburgh, PA, the system
prints a receipt containing the sunset time. A manual print button and an LED demo mode button are also included.
The LED strip visualizes the time of day through a smooth color
transition:
- 6AM → 12PM: Blue → Yellow
- 12PM → 6PM: Yellow → Red
- 6PM → 9PM: Orange → Purple
- Night: Off
A demo mode switch allows the full 6AM–9PM cycle to play back
over 1 minute for quick presentation.
pin mapping:
Arduino pin | role | description
___________________________________________________________________
2 input attached button for printing
3 input attached to button for LED Demo mode
4 output attached to LED strip
5 output attached to RX of thermal printer
6 output attached to TX of thermal printer
SDA, SCL input attached to RTC: I2C
Code written by ChatGPT on 11/2/2025
Edited by Yutong Zhen, yzhen2@andrew.cmu.edu
*/
#include <Wire.h>
#include "RTClib.h"
#include "Adafruit_Thermal.h"
#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>
#include <math.h>
// -------------------- Hardware Setup --------------------
RTC_DS3231 rtc;
SoftwareSerial mySerial(5, 6); // RX, TX for Thermal Printer
Adafruit_Thermal printer(&mySerial);
#define BUTTON_PIN 2 // Momentary button for printing
#define DEMO_SWITCH_PIN 3 // Latching switch for demo mode
#define LED_PIN 4
#define NUM_LEDS 20
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// -------------------- Pittsburgh Location --------------------
#define LATITUDE 40.44
#define LONGITUDE -79.99
#define TIMEZONE -5.0 // EDT (-5 for EST)
// -------------------- Global Variables --------------------
bool printedToday = false;
int lastDay = -1;
bool lastButtonState = HIGH;
bool demoMode = false;
// --- Demo mode patch variables ---
bool lastDemoMode = false;
unsigned long demoStartMillis = 0;
// -------------------- Date Helpers --------------------
int dayOfYear(int year, int month, int day) {
static const int monthDays[12] =
{31,28,31,30,31,30,31,31,30,31,30,31};
int n = day;
for (int i = 0; i < month - 1; i++) n += monthDays[i];
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
if (month > 2) n++;
return n;
}
// -------------------- Sunset Calculation --------------------
double computeSunsetTime(int n, double lat, double lon, double tz) {
double rad = M_PI / 180.0;
double decl = 23.45 * sin(rad * ((360.0/365.0)*(284+n)));
double B = (360.0/365.0) * (n - 81);
double EoT = 9.87 * sin(2*rad*B) - 7.53 * cos(rad*B) - 1.5 * sin(rad*B);
double H = acos(-tan(rad*lat) * tan(rad*decl)) / rad;
double sunsetSolar = 12.0 + (H / 15.0);
double lonHours = lon / 15.0;
return sunsetSolar - (EoT/60.0) - lonHours + tz;
}
// -------------------- Receipt Printing --------------------
void printSunsetReceipt(DateTime now, int sunsetH, int sunsetM) {
printer.wake();
printer.justify('C');
printer.setSize('L');
printer.println(F("SUNSET"));
printer.setSize('L');
printer.print(sunsetH);
printer.print(":");
if (sunsetM < 10) printer.print("0");
printer.print(sunsetM);
printer.println(" PM");
printer.println();
printer.setSize('S');
printer.println(F("Pittsburgh, PA"));
printer.print(F("Date: "));
printer.print(now.month());
printer.print("/");
printer.print(now.day());
printer.print("/");
printer.println(now.year());
printer.println();
printer.println(F("Go take a walk!"));
printer.println();
printer.println();
printer.println();
printer.println();
printer.feed(3);
printer.sleep();
Serial.println("✅ Sunset receipt printed.");
Serial.print(now.month());
Serial.print("/");
Serial.print(now.day());
Serial.print("/");
Serial.print(now.year());
Serial.print(" ");
Serial.print(now.hour());
Serial.print(":");
if (now.minute() < 10) Serial.print("0");
Serial.print(now.minute());
Serial.print(":");
if (now.second() < 10) Serial.print("0");
Serial.println(now.second());
}
// -------------------- LED Color Function --------------------
uint32_t timeToColorMinutes(int t) {
int r, g, b;
// 6AM → 12PM: Blue → Yellow (360–720)
if (t >= 360 && t < 720) {
float p = float(t - 360) / 360.0;
r = int(255 * p);
g = int(255 * p);
b = int(255 * (1 - p));
// 12PM → 6PM: Yellow → red (720–1080)
} else if (t >= 720 && t < 1080) {
float p = float(t - 720) / 360.0;
r = 255;
g = 255 - int(p * (255)); // 255 → 120
b = 0;
// 6PM → 9PM: Orange → Purple (1080–1260)
} else if (t >= 1080 && t < 1260) {
float p = float(t - 1080) / 180.0;
r = 255 - int(p * (255 - 100)); // 255 → 100
g = 120 - int(p * 120); // 120 → 0
b = int(p * 255); // 0 → 255
// Night: LEDs off
} else {
r = g = b = 0;
}
return strip.Color(r, g, b);
}
// -------------------- Setup --------------------
void setup() {
Wire.begin();
rtc.begin();
mySerial.begin(19200);
delay(500);
printer.begin();
printer.setDefault();
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(DEMO_SWITCH_PIN, INPUT_PULLUP);
strip.begin();
strip.show();
Serial.println("System initialized with Demo Mode support.");
}
// -------------------- Main Loop --------------------
void loop() {
DateTime now = rtc.now();
// -------- Read demo switch --------
demoMode = (digitalRead(DEMO_SWITCH_PIN) == HIGH);
// Detect transition: OFF → ON, reset demo timer
if (demoMode && !lastDemoMode) {
demoStartMillis = millis();
}
// -------- Compute minutes (demo or normal) --------
int minutesNow;
if (!demoMode) {
// Normal mode = real time
minutesNow = now.hour() * 60 + now.minute();
} else {
// Demo mode = compress 6AM–9PM into 1 minute, starting fresh every time
unsigned long ms = (millis() - demoStartMillis) % 60000; // 0–59999
float p = ms / 30000.0; // 0–1 progress
minutesNow = 360 + int(p * (1260 - 360)); // map 6AM–9PM
}
// Track last state
lastDemoMode = demoMode;
// -------- Sunset Calculations --------
int n = dayOfYear(now.year(), now.month(), now.day());
double sunsetHour = computeSunsetTime(n, LATITUDE, LONGITUDE, TIMEZONE);
int sunsetH = int(sunsetHour);
int sunsetM = int((sunsetHour - sunsetH) * 60);
double printHour = sunsetHour - 1;
if (printHour < 0) printHour += 24;
int printH = int(printHour);
int printM = int((printHour - printH) * 60);
// -------- Automatic print one hour before sunset --------
if (!printedToday && now.hour() == printH && now.minute() == printM) {
printSunsetReceipt(now, sunsetH, sunsetM);
printedToday = true;
lastDay = now.day();
}
// -------- Manual print --------
bool buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW && lastButtonState == HIGH) {
delay(50);
if (digitalRead(BUTTON_PIN) == LOW) {
printSunsetReceipt(now, sunsetH, sunsetM);
}
}
lastButtonState = buttonState;
// Reset for new day
if (now.day() != lastDay) printedToday = false;
// -------- Update LEDs --------
uint32_t color = timeToColorMinutes(minutesNow);
for (int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, color);
}
strip.show();
delay(1000);
}