This document is written as preparation for an oral project defense. It explains not only what the project does, but also how the code works, why each file exists, and how to explain the most important functions.
This project is an alarm clock built with Arduino UNO. It uses a 16x2 I2C LCD display to show information, a DS1302 RTC module for date and time handling, a DHT11 sensor for temperature and humidity, four buttons for user input, a buzzer for sound output, and an LED for alarm status indication.
Main features:
- display current time;
- display current date;
- display temperature and humidity;
- set current time using buttons;
- set current date using buttons;
- set alarm time;
- save alarm settings in EEPROM;
- play alarm sound using a buzzer;
- show alarm status using an LED;
- snooze alarm for 5 minutes;
- stopwatch;
- countdown timer;
- factory reset.
The project is implemented as a state machine. This means that the program has a set of states, also called screens, and only one screen is active at a time: clock, date, environment, set time, set alarm, timer, and so on.
| Component | Purpose |
|---|---|
| Arduino UNO | Main microcontroller that runs the program |
| 16x2 I2C LCD | Displays time, date, sensor data, menus, and messages |
| DS1302 RTC | Real-time clock module |
| DHT11 | Temperature and humidity sensor |
| 4 buttons | User control and menu navigation |
| Buzzer | Alarm and timer sound output |
| LED | Alarm enabled indicator and ringing indicator |
| Breadboard | Circuit assembly without soldering |
All pin definitions are stored in include/config.h. If asked where hardware pins are configured, the answer is: "All hardware pin definitions are centralized in config.h, so they do not need to be searched across the whole codebase."
| Device | Device Pin | Arduino Pin |
|---|---|---|
| LCD I2C | SDA | A4 |
| LCD I2C | SCL | A5 |
| LCD I2C | VCC | 5V |
| LCD I2C | GND | GND |
| DS1302 RTC | DAT | D7 |
| DS1302 RTC | CLK | D6 |
| DS1302 RTC | RST | D8 |
| DS1302 RTC | VCC | 5V |
| DS1302 RTC | GND | GND |
| DHT11 | DATA / S | D5 |
| DHT11 | VCC / + | 5V |
| DHT11 | GND / - | GND |
| MODE button | signal | D2 |
| SELECT button | signal | D3 |
| UP button | signal | D4 |
| DOWN button | signal | D9 |
| Buzzer | signal / + | D10 |
| Buzzer | GND / - | GND |
| LED | through resistor | D11 |
The buttons use INPUT_PULLUP.
This means:
- button not pressed - Arduino reads
HIGH; - button pressed - Arduino reads
LOW.
This is important because the code detects a button press as a transition from HIGH to LOW.
The libraries are configured in platformio.ini.
| Library | Purpose |
|---|---|
makuna/RTC |
DS1302 RTC support |
adafruit/DHT sensor library |
DHT11 sensor support |
adafruit/Adafruit Unified Sensor |
Dependency for the DHT library |
robtillaart/I2C_LCD |
Included in the skeleton project |
marcoschwartz/LiquidCrystal_I2C |
Actually used for the 16x2 I2C LCD |
EEPROM |
Built-in Arduino library for persistent settings |
Wire |
I2C communication for the LCD |
include/
config.h
context.h
lcd_wrapper.h
rtc_wrapper.h
screens.h
sensors.h
src/
main.cpp
lcd_wrapper.cpp
rtc_wrapper.cpp
sensors.cpp
screens/
init.cpp
app.cpp
lib/
helpers/
platformio.ini
READMEMain structure idea:
main.cpponly runs the state machine;context.hstores the program state;screens.hdeclares all screens;src/screens/init.cppinitializes the system;src/screens/app.cppcontains the menu, buttons, alarm, timer, and stopwatch logic;lcd_wrapper.cppisolates LCD operations;rtc_wrapper.cppisolates time, RTC, and EEPROM snapshot logic;sensors.cppisolates DHT11 operations.
| Button | Main Action |
|---|---|
| MODE | Move to the next screen |
| SELECT | Enter edit mode, confirm, start, pause |
| UP | Increase value or reset stopwatch |
| DOWN | Decrease value |
The exact behavior depends on the active screen.
During alarm ringing:
SELECTactivates snooze;MODE,UP, orDOWNstops the alarm.
On the stopwatch screen:
SELECTstarts or pauses the stopwatch;UPresets it.
On the timer screen:
UPandDOWNchange minutes or seconds;SELECTmoves from minutes to seconds and then starts the timer;- while running,
SELECTpauses or resumes the timer.
Screens are declared in include/screens.h using the screen enum.
| Screen | Purpose |
|---|---|
INIT_SCR |
Initializes the program |
CLOCK_SCR |
Shows current time |
SHOW_DATE_SCR |
Shows current date |
SHOW_ENV_SCR |
Shows temperature and humidity |
SET_TIME_SCR |
Sets current time |
SET_DATE_SCR |
Sets current date |
SET_ALARM_SCR |
Sets alarm |
STOPWATCH_SCR |
Stopwatch |
TIMER_SCR |
Countdown timer |
FACTORY_RESET_SCR |
Resets settings |
ALARM_SCR |
Active alarm ringing screen |
This file contains all pin definitions and basic configuration values.
#define BAUD_RATE 9600Serial communication speed. Serial is initialized, although the project mainly uses the LCD.
#define RTC_DAT_PIN 7
#define RTC_CLK_PIN 6
#define RTC_RST_PIN 8Pins for the DS1302 RTC module. DS1302 does not use I2C. It uses separate DAT, CLK, and RST lines.
#define BUZZER_PIN 10Pin used by the buzzer. The code uses tone() to generate sound on this pin.
#define DHT_PIN 5Data pin for the DHT11 sensor.
#define BTN1_PIN 2
#define BTN2_PIN 3
#define BTN3_PIN 4
#define BTN4_PIN 9In the code these are mapped to:
BTN_MODE;BTN_SELECT;BTN_UP;BTN_DOWN.
#define STATUS_LED_PIN 11Pin used by the status LED. The LED is on when the alarm is enabled and blinks while the alarm or timer is ringing.
#define LCD_I2C_ADDRESS 0x27
#define LCD_ROWS 2
#define LCD_COLS 16The LCD I2C address was found using an I2C scanner. The display has 2 rows and 16 columns.
This is one of the most important files. It defines the structures that store the program state.
struct button_state {
byte pin;
bool last_state;
unsigned long last_change;
};This structure stores the state of one button.
Fields:
pin- Arduino pin number;last_state- previous button state,HIGHorLOW;last_change- time of the last accepted state change, used for debounce.
Debounce is necessary because a mechanical button can produce several quick signal changes during one press. Without debounce, one physical press could be detected as multiple presses.
context is the main program state structure. It is passed to all screen functions.
The main idea is to keep program state in one object instead of using many unrelated global variables.
Important field groups:
byte current_screen;Stores the currently active screen.
button_state mode_button;
button_state select_button;
button_state up_button;
button_state down_button;Stores button states for debounce and press detection.
float temperature;
int humidity;
unsigned long last_sensor_read;Stores the latest valid sensor values and the time of the last DHT11 read.
bool setting_time;
byte time_field;
byte set_hour;
byte set_minute;setting_timetells whether the user is editing time;time_fieldtells which field is selected: hour or minute;set_hour,set_minuteare temporary values before saving.
bool setting_date;
byte date_field;
byte set_day;
byte set_month;
int set_year;Similar to time editing, but for day, month, and year.
bool setting_alarm;
byte alarm_field;
byte alarm_hour;
byte alarm_minute;
bool alarm_enabled;
bool alarm_ringing;
int last_alarm_key;alarm_hour,alarm_minutestore the alarm time;alarm_enabledtells whether the alarm is active;alarm_ringingtells whether the alarm is currently ringing;last_alarm_keyprevents repeated alarm starts during the same minute.
bool snooze_active;
byte snooze_hour;
byte snooze_minute;Stores whether snooze is active and the next snooze time.
bool stopwatch_running;
unsigned long stopwatch_started_at;
unsigned long stopwatch_elapsed_before_start;Used for stopwatch timing.
bool timer_setting;
byte timer_field;
byte timer_minutes;
byte timer_seconds;
bool timer_running;
bool timer_done;
unsigned long timer_ends_at;
unsigned long timer_remaining_ms;Stores countdown timer state, values, and timing data.
This file declares the screen enum and screen function prototypes.
The enum lists all states of the program. Each state represents one screen or mode.
Using an enum makes the code more readable. Instead of numbers like 0, 1, or 2, the code uses names like CLOCK_SCR, SET_ALARM_SCR, and TIMER_SCR.
Example:
enum screen clock_screen(struct context *ctx);
enum screen set_alarm_screen(struct context *ctx);Each screen function:
- receives a pointer to
context; - runs the logic for that screen;
- returns the next active screen.
This file is intentionally short.
int main() {
struct context context;
enum screen screen = INIT_SCR;
for (;;) {
switch (screen) {
...
}
}
}What happens:
- A
contextstructure is created. - Initial screen is set to
INIT_SCR. - An infinite loop starts.
- The
switchstatement calls the function for the active screen. - Each screen function returns the next screen.
This is the main state machine dispatcher.
Defense explanation:
"main.cpp does not contain business logic. It only dispatches states. All real behavior is implemented in screen functions and wrapper modules."
This file handles system initialization.
This function fills the context structure with initial values.
It:
- sets the initial screen to
CLOCK_SCR; - initializes all four button states;
- clears temperature and humidity values;
- disables time, date, and alarm editing modes;
- sets default alarm to
07:00 OFF; - disables snooze;
- resets the stopwatch;
- sets the timer to
01:00.
This is important because uninitialized variables can contain random memory values.
This is the first screen called from main.cpp.
Important operations:
init();
context_init(ctx);
Wire.begin();
Serial.begin(BAUD_RATE);init() is the Arduino core initialization function. It is needed because this project uses a custom main() instead of the usual setup() and loop().
Pins are configured:
pinMode(BTN_MODE, INPUT_PULLUP);
pinMode(BTN_SELECT, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(STATUS_LED_PIN, OUTPUT);Then hardware modules are initialized:
lcd_init();
lcd_backlight(true);
clock_init();
sensors_init();
load_alarm_settings(ctx);Finally, the LCD shows:
Alarm Clock
ReadyThe function returns CLOCK_SCR, so the program moves to the clock screen.
This file hides direct use of the LiquidCrystal_I2C library.
The LCD object is created inside the file:
static LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_COLS, LCD_ROWS);The static keyword means this object is private to lcd_wrapper.cpp.
Initializes the LCD:
lcd.init();Clears the full display.
Sets the cursor position.
The wrapper uses parameters as row, column, while the library call uses column, row.
Prints text at the current cursor position.
Sets the cursor and prints text.
Prints a full 16-character padded row:
snprintf(line, sizeof(line), "%-16s", text);This prevents old characters from remaining on the LCD when a shorter string replaces a longer one.
Clears one LCD row.
Turns the LCD backlight on or off.
This file handles DHT11 sensor operations.
Creates the DHT11 sensor object.
Starts the DHT sensor:
dht.begin();Returns temperature in degrees Celsius.
If the sensor read fails, the DHT library can return NaN. The validation is done in read_sensor() inside app.cpp.
Reads humidity.
If humidity is invalid, it returns -1.
The app checks:
if (!isnan(new_temperature) && new_humidity >= 0)So only valid sensor readings are stored in context.
This file handles time, date, the DS1302 RTC, and EEPROM time snapshot.
The project uses a DS1302 RTC module, but it also stores the last set date and time in EEPROM.
When the user saves time or date:
- the internal software clock is updated;
- the value is saved to EEPROM;
- the value is written to DS1302.
When Arduino starts:
- it first tries to load the EEPROM snapshot;
- if the snapshot is valid, it starts from that value;
- if there is no valid snapshot, it tries the RTC;
- if RTC is invalid, it uses a fallback default time.
This was done to reliably restore the last configured time after restart.
#define EEPROM_TIME_MAGIC_ADDR 10
#define EEPROM_TIME_YEAR_ADDR 11
#define EEPROM_TIME_MONTH_ADDR 12
#define EEPROM_TIME_DAY_ADDR 13
#define EEPROM_TIME_HOUR_ADDR 14
#define EEPROM_TIME_MINUTE_ADDR 15
#define EEPROM_TIME_SECOND_ADDR 16
#define EEPROM_TIME_MAGIC_VALUE 0x5CEEPROM stores:
- year;
- month;
- day;
- hour;
- minute;
- second;
- magic byte.
The magic byte is used to check whether EEPROM contains valid saved data.
DS1302 is controlled through ThreeWire:
static ThreeWire rtc_wire(RTC_DAT_PIN, RTC_CLK_PIN, RTC_RST_PIN);Parameter order:
DAT, CLK, RSTCreates the RTC object.
Checks whether date and time are valid.
It checks:
- year from 2024 to 2099;
- month from 1 to 12;
- day from 1 to 31;
- hour from 0 to 23;
- minute from 0 to 59;
- second from 0 to 59;
- internal
date_time.IsValid().
This prevents invalid RTC data from breaking the program.
Returns the default date and time:
22.05.2026 00:00:00This is used when neither EEPROM nor RTC contains valid data.
Starts the internal software clock.
It stores:
- base time;
- the
millis()value when the base time was set; - a validity flag.
Then safe_now() can calculate current time using millis().
Saves date and time to EEPROM.
It uses EEPROM.update() instead of EEPROM.write().
Difference:
write()always writes;update()writes only if the value changed.
This is better because EEPROM has a limited number of write cycles.
Loads date and time from EEPROM.
Algorithm:
- Check magic byte.
- If magic byte is wrong, return
false. - Read year, month, day, hour, minute, and second.
- Build
RtcDateTime. - Validate it.
- If valid, write result into the output parameter and return
true.
This is the central safe time getter.
Algorithm:
- If internal software clock is valid, return:
app_clock_base + elapsed_seconds- Otherwise, read RTC.
- If RTC data is valid, use RTC.
- If RTC is invalid, use fallback time.
Converts the library type RtcDateTime into the project type dt.
This makes the rest of the program independent from the RTC library type.
Initializes RTC and initial time.
Algorithm:
- Start RTC using
rtc.Begin(). - Disable write protection.
- Start RTC.
- Try to load time from EEPROM.
- If EEPROM is valid, use it.
- Otherwise, try RTC.
- If RTC is invalid, use fallback time.
Changes only the date and keeps the current time.
Changes only the time and keeps the current date.
Main function for saving date and time.
It:
- creates
RtcDateTime; - updates the software clock;
- saves EEPROM snapshot;
- writes time to RTC;
- starts RTC.
Return separate date parts.
Return separate time parts.
Returns the full current date and time as struct dt.
This function is used by screens, alarm logic, date logic, time logic, and snooze.
Clears the EEPROM magic byte for the saved time snapshot.
It is used by Factory Reset.
This is the largest file. It contains the main application behavior.
#define EEPROM_MAGIC_ADDR 0
#define EEPROM_ALARM_HOUR_ADDR 1
#define EEPROM_ALARM_MINUTE_ADDR 2
#define EEPROM_ALARM_ENABLED_ADDR 3
#define EEPROM_MAGIC_VALUE 0xA6EEPROM stores:
- alarm hour;
- alarm minute;
- alarm enabled flag;
- magic byte.
Returns the number of days in a month.
It handles:
- February;
- leap years;
- 30-day months;
- 31-day months.
This prevents invalid dates like April 31.
Reads a button with debounce.
Algorithm:
- Read current pin state.
- If the state changed and more than 60 ms passed, accept the change.
- If the new state is
LOW, this is a button press. - Return
trueonly once per press.
Saves alarm settings to EEPROM:
- magic byte;
- alarm hour;
- alarm minute;
- enabled state.
Loads alarm settings from EEPROM.
If magic byte is missing, it sets default values:
07:00 OFFIt also validates hour and minute.
Moves to the next screen.
If the current screen is the alarm screen or the last menu screen, it returns to CLOCK_SCR.
It also exits edit modes:
ctx->setting_time = false;
ctx->setting_date = false;
ctx->setting_alarm = false;Reads DHT11 every 2 seconds.
This is needed because DHT11 should not be read too frequently.
If values are valid, it stores them in context.
Starts time editing.
It reads current time using now() and copies hour and minute into temporary fields.
Saves edited time.
It keeps the current date and saves the new hour and minute through set_datetime().
Then it shows:
Time savedChanges hour or minute.
direction is:
1for UP;-1for DOWN.
Hours wrap between 0 and 23. Minutes wrap between 0 and 59.
Starts date editing by copying current date into temporary fields.
Saves edited date while keeping the current time.
Changes day, month, or year.
It validates the day when month or year changes.
Starts alarm editing and selects the hour field.
Saves alarm settings to EEPROM and shows:
Alarm savedChanges:
- alarm hour;
- alarm minute;
- alarm ON/OFF.
Starts alarm ringing state.
It:
- sets
alarm_ringing = true; - switches screen to
ALARM_SCR; - clears LCD.
The buzzer is handled separately in update_buzzer_and_led().
Stops the alarm sound and returns to the clock screen.
Postpones the alarm by 5 minutes.
It:
- reads current time;
- adds 5 minutes;
- stores snooze hour and minute;
- enables snooze;
- stops current alarm sound;
- returns to clock screen.
Checks whether the alarm should start.
It:
- ignores the check if alarm is already ringing;
- reads current time;
- checks snooze time;
- checks regular alarm time if alarm is enabled;
- starts alarm if hour and minute match.
last_alarm_key prevents repeated alarm starts in the same minute.
Calculates elapsed stopwatch time.
If paused, it returns the saved elapsed time. If running, it adds time passed since the last start.
Starts or pauses the stopwatch.
Resets stopwatch to zero.
Starts the countdown timer.
It calculates duration:
duration = (minutes * 60 + seconds) * 1000Then it stores:
timer_ends_at = millis() + timer_remaining_ms;Pauses the timer and stores remaining milliseconds.
Resumes the paused timer.
Resets the timer into setting mode and turns off the buzzer.
Changes timer minutes or seconds.
Minutes wrap from 0 to 99. Seconds wrap from 0 to 59.
Checks if the timer finished.
If finished:
timer_running = false;timer_done = true;- remaining time becomes 0;
- screen switches to
TIMER_SCR; - LCD is cleared.
Resets:
- alarm to
07:00 OFF; - snooze;
- saved time snapshot;
- stopwatch;
- timer to
01:00.
It then shows:
Factory reset
DoneMain button handling function.
It:
- reads all four buttons;
- handles alarm ringing controls;
- handles timer-done controls;
- handles MODE screen switching;
- handles SELECT actions depending on active screen;
- handles UP and DOWN depending on active screen.
This is one of the central functions of the program.
Controls buzzer and LED.
If alarm or timer is ringing:
- buzzer toggles every 250 ms;
- LED blinks together with the buzzer.
If nothing is ringing:
- buzzer is off;
- LED is on only when alarm is enabled.
Shows current time.
First row:
Timeif alarm is disabled;Time ALif alarm is enabled.
Second row:
- time in
HH:MM:SSformat; SNZif snooze is active.
Shows date in:
DD.MM.YYYYShows temperature and humidity.
Temperature is rounded to integer value.
Shows the time setting screen.
Before editing:
Set Time
SELECT to editDuring editing:
Edit hour;- or
Edit minute.
Shows date setting screen for day, month, and year.
Shows alarm setting screen.
Format:
HH:MM ONor
HH:MM OFFShows stopwatch in:
MM:SS.HHwhere HH means hundredths of a second.
Shows timer state:
- setting minutes;
- setting seconds;
- running;
- paused;
- finished.
Shows:
Factory Reset
SELECT to resetShows:
Wake up! HH:MM
SEL snooze M offMeaning:
SELECT- snooze;MODE- turn alarm off.
Calls the correct display function depending on current_screen.
Main common logic for all screens.
It:
- sets active screen;
- handles buttons;
- reads sensor;
- updates timer;
- checks alarm;
- updates buzzer and LED;
- redraws the current screen;
- delays for 40 ms;
- returns current screen.
This avoids duplicating the same logic in each screen function.
Functions like:
enum screen clock_screen(struct context *ctx)simply call run_screen() with their own screen value.
This matches the expected project skeleton structure.
main()starts withINIT_SCR.init_screen()initializes Arduino, LCD, RTC, DHT11, buttons, buzzer, LED, and EEPROM settings.- Program moves to
CLOCK_SCR. - The active screen function is called repeatedly.
- Screen function calls
run_screen(). run_screen()handles buttons, sensor, alarm, timer, LED, buzzer, and LCD.- If MODE is pressed, screen changes.
- If alarm time matches current time, program switches to
ALARM_SCR.
- User goes to
Set Time. - User presses
SELECT. - Hour editing starts.
UP/DOWNchanges hour.SELECTmoves to minute editing.UP/DOWNchanges minutes.SELECTsaves time.
Saving is performed through:
set_datetime(...)This stores time in the software clock, EEPROM snapshot, and RTC.
- User goes to
Set Alarm. SELECTstarts hour editing.UP/DOWNchanges hour.SELECTmoves to minute editing.UP/DOWNchanges minute.SELECTmoves to ON/OFF.UP/DOWNtoggles ON/OFF.SELECTsaves alarm.
Alarm data is stored in EEPROM.
check_alarm() checks current time continuously.
If:
current.hours == alarm_hour
current.minutes == alarm_minute
alarm_enabled == truethen start_alarm_ring() is called.
After that:
- screen becomes
ALARM_SCR; update_buzzer_and_led()starts the buzzer;- LED blinks.
When alarm is ringing, pressing SELECT calls snooze_alarm().
The function adds 5 minutes to current time and stores it in:
snooze_hour
snooze_minuteThen the alarm stops.
When snooze time arrives, check_alarm() starts the alarm again.
Stopwatch uses millis(), not RTC.
When started, it stores:
stopwatch_started_at = millis();When paused, elapsed time is accumulated.
When reset, elapsed time becomes zero.
Timer also uses millis().
When started:
timer_ends_at = millis() + timer_remaining_ms;update_timer() checks whether current millis() reached timer_ends_at.
If yes:
- timer finishes;
timer_done = true;- buzzer starts.
EEPROM is used for two things:
- Saving alarm settings.
- Saving the last configured date and time.
The code uses EEPROM.update() to reduce unnecessary EEPROM writes.
Magic bytes are used to check whether saved data is valid.
The project uses DS1302 RTC.
It is connected using:
DAT
CLK
RSTnot I2C.
RTC is initialized in clock_init().
When time is set, set_datetime() writes it to RTC using:
rtc.SetDateTime(updated);The code also validates RTC data to avoid invalid values.
The program is implemented as a state machine.
The list of states is declared in screens.h.
main.cpp contains an infinite loop and a switch statement that calls the active state function.
Each screen function returns the next state.
Advantages:
- easy to add new screens;
- easier to read;
- each mode is separated;
- avoids one huge
loop()function.
The project includes the following additional features:
Postpones the alarm for 5 minutes.
Stopwatch with start, pause, and reset.
Timer that rings when it reaches zero.
Resets alarm, timer, stopwatch, and saved time.
If exactly three additional features are required, use:
- Snooze.
- Stopwatch.
- Countdown timer.
Factory reset can be mentioned as an extra helper feature.
Because Arduino has internal pull-up resistors. This avoids external pull-down resistors. A released button reads HIGH, and a pressed button reads LOW.
Mechanical buttons can bounce electrically. Debounce prevents one physical press from being detected multiple times.
It stores the whole program state in one structure and passes it between screen functions.
Because the project is modular. main.cpp only dispatches states, while logic is implemented in separate modules.
So screen logic does not depend directly on the LCD library. If the LCD library changes, only the wrapper needs to be updated.
DHT11 should not be read too frequently. The program reads it about every 2 seconds.
It uses last_alarm_key, which stores the day and minute of the last alarm trigger.
If alarm is enabled, LED is on. If alarm or timer is ringing, LED blinks together with buzzer.
millis() allows non-blocking time measurement. The program can still read buttons and update the display.
Before defense, test:
- LCD shows startup message;
MODEcycles through all screens;Timeshows time;Dateshows date;Temp/Humidityshows DHT11 values;Set Timechanges hour and minute;Set Datechanges day, month, and year;- after restart, last configured time is restored;
Set Alarmsets alarm time;- LED turns on when alarm is enabled;
- alarm rings at configured time;
SELECTduring ringing activates snooze;MODEduring ringing stops the alarm;- stopwatch starts, pauses, and resets;
- timer can be configured, started, paused, and rings at zero;
- factory reset resets settings.
This is an Arduino UNO alarm clock. It uses an LCD display, DS1302 RTC, DHT11 sensor, buttons, buzzer, and LED. The program is implemented as a state machine where every mode is a separate screen. main.cpp only dispatches screens, while the main logic is in screens/app.cpp. Program state is stored in the context structure. Time and date are handled by rtc_wrapper, LCD output by lcd_wrapper, and DHT11 by sensors. The user can set time, date, and alarm using buttons. Alarm settings are stored in EEPROM, and when the alarm rings, the buzzer sounds and the LED blinks. Additional features are snooze, stopwatch, and countdown timer.