Clock
Source: src/clock.c · 258 lines · 16 functions.
Screen captures
Views of this part running on the Mini Arcade.

Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
configure_clock_timezone() · source line 36
static void configure_clock_timezone(void)
- Execute
static bool configured; - Decision
configuredTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
setenv("TZ", "CET-1CEST,M3.5.0/02,M10.5.0/03", 1); - Execute
tzset(); - Execute
configured = true;
Read original function source
static void configure_clock_timezone(void)
{
static bool configured;
if (configured) return;
setenv("TZ", "CET-1CEST,M3.5.0/02,M10.5.0/03", 1);
tzset();
configured = true;
}choose_section_layout() · source line 45
static void choose_section_layout(void)
- Execute
uint8_t previous_layout[CLOCK_ZONE_COUNT]; - Execute
memcpy(previous_layout, section_zone, sizeof(previous_layout)); - DO / WHILE loop
memcmp(previous_layout, section_zone, sizeof(section_zone)) == 0Run body, then test condition; repeat while true.
- FOR loop
uint8_t index = 0U; index < CLOCK_ZONE_COUNT; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
section_zone[index] = index;
- FOR loop
uint8_t index = CLOCK_ZONE_COUNT - 1U; index > 0U; --indexRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t swap = (uint8_t)(esp_random() % (index + 1U)); - Execute
const uint8_t temporary = section_zone[index]; - Execute
section_zone[index] = section_zone[swap]; - Execute
section_zone[swap] = temporary;
Read original function source
static void choose_section_layout(void)
{
uint8_t previous_layout[CLOCK_ZONE_COUNT];
memcpy(previous_layout, section_zone, sizeof(previous_layout));
do {
for (uint8_t index = 0U; index < CLOCK_ZONE_COUNT; ++index) section_zone[index] = index;
for (uint8_t index = CLOCK_ZONE_COUNT - 1U; index > 0U; --index) {
const uint8_t swap = (uint8_t)(esp_random() % (index + 1U));
const uint8_t temporary = section_zone[index];
section_zone[index] = section_zone[swap];
section_zone[swap] = temporary;
}
} while (memcmp(previous_layout, section_zone, sizeof(section_zone)) == 0);
}section_y() · source line 60
static int section_y(clock_section_t section)
- Exit this function
return zone_y[section_zone[section]];
Read original function source
static int section_y(clock_section_t section)
{
return zone_y[section_zone[section]];
}draw_unsynchronized_status() · source line 65
static void draw_unsynchronized_status(uint16_t *frame, int y)
Calls into this documentation:
- Execute
const wifi_connection_state_t connection = wifiServiceConnectionState(); - Execute
const time_sync_status_t sync_status = timeSyncStatus(); - Execute
const char *status_text = "CLOCK NOT SET"; - Execute
uint16_t status_colour = COLOR_YELLOW; - Decision
connection == WIFI_CONNECTION_CONNECTINGTrue- Execute
status_text = "WIFI CONNECTING...";
False- Decision
connection == WIFI_CONNECTION_FAILEDTrue- Execute
status_text = "WIFI CONNECTION FAILED"; - Execute
status_colour = COLOR_RED;
False- Decision
sync_status == TIME_SYNC_SYNCINGTrue- Execute
status_text = "TIME SYNCING...";
False- Decision
sync_status == TIME_SYNC_FAILEDTrue- Execute
status_text = "TIME SYNC FAILED"; - Execute
status_colour = COLOR_RED;
FalseContinue after this decision.
- Execute
draw_text_centred(frame, 160, y + 18, status_text, 1, status_colour);
Read original function source
static void draw_unsynchronized_status(uint16_t *frame, int y)
{
const wifi_connection_state_t connection = wifiServiceConnectionState();
const time_sync_status_t sync_status = timeSyncStatus();
const char *status_text = "CLOCK NOT SET";
uint16_t status_colour = COLOR_YELLOW;
if (connection == WIFI_CONNECTION_CONNECTING) status_text = "WIFI CONNECTING...";
else if (connection == WIFI_CONNECTION_FAILED) {
status_text = "WIFI CONNECTION FAILED";
status_colour = COLOR_RED;
} else if (sync_status == TIME_SYNC_SYNCING) status_text = "TIME SYNCING...";
else if (sync_status == TIME_SYNC_FAILED) {
status_text = "TIME SYNC FAILED";
status_colour = COLOR_RED;
}
draw_text_centred(frame, 160, y + 18, status_text, 1, status_colour);
}draw_time_section() · source line 84
static void draw_time_section(uint16_t *frame, int y, const char *text, uint16_t colour)
Calls into this documentation:
- Execute
fill_rect(frame, 0, y, LCD_WIDTH, 38, COLOR_BLACK); - Execute
draw_text_centred(frame, 160, y, text, 4, colour);
Read original function source
static void draw_time_section(uint16_t *frame, int y, const char *text, uint16_t colour)
{
fill_rect(frame, 0, y, LCD_WIDTH, 38, COLOR_BLACK);
draw_text_centred(frame, 160, y, text, 4, colour);
}draw_date_section() · source line 90
static void draw_date_section(uint16_t *frame, int y)
Calls into this documentation:
- Execute
fill_rect(frame, 0, y, LCD_WIDTH, 42, COLOR_BLACK); - Execute
draw_text_centred(frame, 160, y + 2, day_text, 2, COLOR_ORANGE); - Execute
draw_text_centred(frame, 160, y + 24, date_text, 1, COLOR_ORANGE);
Read original function source
static void draw_date_section(uint16_t *frame, int y)
{
fill_rect(frame, 0, y, LCD_WIDTH, 42, COLOR_BLACK);
draw_text_centred(frame, 160, y + 2, day_text, 2, COLOR_ORANGE);
draw_text_centred(frame, 160, y + 24, date_text, 1, COLOR_ORANGE);
}draw_countdown_section() · source line 97
static void draw_countdown_section(uint16_t *frame, int y)
Calls into this documentation:
- Execute
fill_rect(frame, 0, y, LCD_WIDTH, 43, COLOR_BLACK); - Execute
draw_text(frame, 8, y + 2, "DOOM", 1, COLOR_CRIMSON); - Execute
draw_text_centred(frame, 210, y, monday_countdown, 2, COLOR_CRIMSON); - Execute
draw_text(frame, 8, y + 25, "FREEDOM", 1, COLOR_OLIVE); - Execute
draw_text_centred(frame, 210, y + 23, friday_countdown, 2, COLOR_OLIVE);
Read original function source
static void draw_countdown_section(uint16_t *frame, int y)
{
fill_rect(frame, 0, y, LCD_WIDTH, 43, COLOR_BLACK);
draw_text(frame, 8, y + 2, "DOOM", 1, COLOR_CRIMSON);
draw_text_centred(frame, 210, y, monday_countdown, 2, COLOR_CRIMSON);
draw_text(frame, 8, y + 25, "FREEDOM", 1, COLOR_OLIVE);
draw_text_centred(frame, 210, y + 23, friday_countdown, 2, COLOR_OLIVE);
}format_countdown() · source line 106
static void format_countdown(char *text, size_t text_size, time_t now_time,
const struct tm *now, int target_weekday,
int target_hour, int target_minute)- Execute
struct tm target = *now; - Execute
int days_until_target = (target_weekday - target.tm_wday + 7) % 7; - Execute
target.tm_mday += days_until_target; - Execute
target.tm_hour = target_hour; - Execute
target.tm_min = target_minute; - Execute
target.tm_sec = 0; - Execute
target.tm_isdst = -1; - Execute
time_t target_time = mktime(&target); - Decision
difftime(target_time, now_time) <= 0.0True- Execute
target.tm_mday += 7; - Execute
target.tm_isdst = -1; - Execute
target_time = mktime(&target);
FalseContinue after this decision.
- Execute
long remaining = (long)difftime(target_time, now_time); - Execute
const long days = remaining / 86400L; - Execute
remaining %= 86400L; - Execute
const long hours = remaining / 3600L; - Execute
remaining %= 3600L; - Execute
const long minutes = remaining / 60L; - Execute
const long seconds = remaining % 60L; - Execute
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
Read original function source
static void format_countdown(char *text, size_t text_size, time_t now_time,
const struct tm *now, int target_weekday,
int target_hour, int target_minute)
{
struct tm target = *now;
int days_until_target = (target_weekday - target.tm_wday + 7) % 7;
target.tm_mday += days_until_target;
target.tm_hour = target_hour;
target.tm_min = target_minute;
target.tm_sec = 0;
target.tm_isdst = -1;
time_t target_time = mktime(&target);
if (difftime(target_time, now_time) <= 0.0) {
target.tm_mday += 7;
target.tm_isdst = -1;
target_time = mktime(&target);
}
long remaining = (long)difftime(target_time, now_time);
const long days = remaining / 86400L;
remaining %= 86400L;
const long hours = remaining / 3600L;
remaining %= 3600L;
const long minutes = remaining / 60L;
const long seconds = remaining % 60L;
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
}format_doomsday_countdown() · source line 135
static void format_doomsday_countdown(char *text, size_t text_size, time_t now_time)
- Execute
struct tm target = { .tm_year = 2027 - 1900, .tm_mon = 1, .tm_mday = 5, .tm_isdst = -1, }; - Execute
long remaining = (long)difftime(mktime(&target), now_time); - Decision
remaining < 0LTrue- Execute
remaining = 0L;
FalseContinue after this decision.
- Execute
const long days = remaining / 86400L; - Execute
remaining %= 86400L; - Execute
const long hours = remaining / 3600L; - Execute
remaining %= 3600L; - Execute
const long minutes = remaining / 60L; - Execute
const long seconds = remaining % 60L; - Execute
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
Read original function source
static void format_doomsday_countdown(char *text, size_t text_size, time_t now_time)
{
struct tm target = {
.tm_year = 2027 - 1900,
.tm_mon = 1,
.tm_mday = 5,
.tm_isdst = -1,
};
long remaining = (long)difftime(mktime(&target), now_time);
if (remaining < 0L) remaining = 0L;
const long days = remaining / 86400L;
remaining %= 86400L;
const long hours = remaining / 3600L;
remaining %= 3600L;
const long minutes = remaining / 60L;
const long seconds = remaining % 60L;
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
}update_time_strings() · source line 155
static void update_time_strings(void)
Calls into this documentation:
- Execute
const time_t now_time = time(NULL); - Execute
struct tm local_time = {0}; - Decision
now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL || local_time.tm_year + 1900 < CLOCK_VALID_YEARTrue- Decision
time_availableTrue- Execute
full_redraw = true;
FalseContinue after this decision.
- Execute
time_available = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
!time_available || last_day_of_year != local_time.tm_ydayTrue- Execute
full_redraw = true;
FalseContinue after this decision.
- Execute
time_available = true; - Execute
strftime(time_text, sizeof(time_text), "%H:%M:%S", &local_time); - Execute
strftime(day_text, sizeof(day_text), "%A", &local_time); - Execute
strftime(date_text, sizeof(date_text), "%d %B %Y", &local_time); - Execute
format_doomsday_countdown(monday_countdown, sizeof(monday_countdown), now_time); - Execute
format_countdown(friday_countdown, sizeof(friday_countdown), now_time, &local_time, 5, 15, 0); - Execute
last_day_of_year = local_time.tm_yday;
Read original function source
static void update_time_strings(void)
{
const time_t now_time = time(NULL);
struct tm local_time = {0};
if (now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL ||
local_time.tm_year + 1900 < CLOCK_VALID_YEAR) {
if (time_available) full_redraw = true;
time_available = false;
return;
}
if (!time_available || last_day_of_year != local_time.tm_yday) full_redraw = true;
time_available = true;
strftime(time_text, sizeof(time_text), "%H:%M:%S", &local_time);
strftime(day_text, sizeof(day_text), "%A", &local_time);
strftime(date_text, sizeof(date_text), "%d %B %Y", &local_time);
format_doomsday_countdown(monday_countdown, sizeof(monday_countdown), now_time);
format_countdown(friday_countdown, sizeof(friday_countdown), now_time,
&local_time, 5, 15, 0);
last_day_of_year = local_time.tm_yday;
}startClock() · source line 177
void startClock(uint32_t now_ms)
Calls into this documentation:
- Execute
configure_clock_timezone(); - Execute
running = true; - Execute
frame_ready = true; - Execute
full_redraw = true; - Execute
time_available = false; - Execute
last_day_of_year = -1; - Execute
last_update = now_ms - CLOCK_UPDATE_MS; - Execute
last_layout_change = now_ms; - Execute
choose_section_layout();
Read original function source
void startClock(uint32_t now_ms)
{
configure_clock_timezone();
running = true;
frame_ready = true;
full_redraw = true;
time_available = false;
last_day_of_year = -1;
last_update = now_ms - CLOCK_UPDATE_MS;
last_layout_change = now_ms;
choose_section_layout();
}stopClock() · source line 190
void stopClock(void)
- Execute
running = false; - Execute
frame_ready = false;
Read original function source
void stopClock(void)
{
running = false;
frame_ready = false;
}updateClock() · source line 196
void updateClock(uint32_t now_ms)
Calls into this documentation:
- Decision
!running || now_ms - last_update < CLOCK_UPDATE_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
last_update = now_ms; - Decision
now_ms - last_layout_change >= CLOCK_LAYOUT_CHANGE_MSTrue- Execute
choose_section_layout(); - Execute
last_layout_change = now_ms; - Execute
full_redraw = true;
FalseContinue after this decision.
- Execute
update_time_strings(); - Execute
frame_ready = true;
Read original function source
void updateClock(uint32_t now_ms)
{
if (!running || now_ms - last_update < CLOCK_UPDATE_MS) return;
last_update = now_ms;
if (now_ms - last_layout_change >= CLOCK_LAYOUT_CHANGE_MS) {
choose_section_layout();
last_layout_change = now_ms;
full_redraw = true;
}
update_time_strings();
frame_ready = true;
}clockTakeFrame() · source line 209
bool clockTakeFrame(void)
- Execute
const bool ready = frame_ready; - Execute
frame_ready = false; - Exit this function
return ready;
Read original function source
bool clockTakeFrame(void)
{
const bool ready = frame_ready;
frame_ready = false;
return ready;
}drawClock() · source line 216
void drawClock(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
full_redrawTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Decision
!time_availableTrue- Execute
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), "--:--:--", COLOR_RED); - Execute
fill_rect(frame, 0, section_y(CLOCK_SECTION_DATE), LCD_WIDTH, 42, COLOR_BLACK); - Execute
draw_unsynchronized_status(frame, section_y(CLOCK_SECTION_DATE)); - Execute
full_redraw = false; - Exit this function
return;
FalseContinue after this decision.
- Execute
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), time_text, COLOR_RED); - Execute
draw_date_section(frame, section_y(CLOCK_SECTION_DATE)); - Execute
draw_countdown_section(frame, section_y(CLOCK_SECTION_COUNTDOWNS)); - Execute
full_redraw = false;
Read original function source
void drawClock(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (full_redraw) {
clear(frame, COLOR_BLACK);
}
if (!time_available) {
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), "--:--:--", COLOR_RED);
fill_rect(frame, 0, section_y(CLOCK_SECTION_DATE), LCD_WIDTH, 42, COLOR_BLACK);
draw_unsynchronized_status(frame, section_y(CLOCK_SECTION_DATE));
full_redraw = false;
return;
}
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), time_text, COLOR_RED);
draw_date_section(frame, section_y(CLOCK_SECTION_DATE));
draw_countdown_section(frame, section_y(CLOCK_SECTION_COUNTDOWNS));
full_redraw = false;
}updateDoomCountdownOverlay() · source line 238
void updateDoomCountdownOverlay(void)
Calls into this documentation:
- Execute
configure_clock_timezone(); - Execute
const time_t now_time = time(NULL); - Execute
struct tm local_time = {0}; - Decision
now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL || local_time.tm_year + 1900 < CLOCK_VALID_YEARTrue- Execute
globalOverlayClearItem(0U); - Exit this function
return;
FalseContinue after this decision.
- Execute
char countdown[20]; - Execute
char overlay_text[32]; - Execute
format_doomsday_countdown(countdown, sizeof(countdown), now_time); - Execute
snprintf(overlay_text, sizeof(overlay_text), "DOOM %s", countdown); - Execute
(void)globalOverlaySetItem(0U, overlay_text, 2, 2, 1, COLOR_ORANGE);
Read original function source
void updateDoomCountdownOverlay(void)
{
configure_clock_timezone();
const time_t now_time = time(NULL);
struct tm local_time = {0};
if (now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL ||
local_time.tm_year + 1900 < CLOCK_VALID_YEAR) {
globalOverlayClearItem(0U);
return;
}
char countdown[20];
char overlay_text[32];
format_doomsday_countdown(countdown, sizeof(countdown), now_time);
snprintf(overlay_text, sizeof(overlay_text), "DOOM %s", countdown);
// Pixel coordinates are deliberately independent of individual screens: move this
// item anywhere on the LCD by changing its x/y values without changing a renderer.
(void)globalOverlaySetItem(0U, overlay_text, 2, 2, 1, COLOR_ORANGE);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "clock.h"
#include "esp_random.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "global_overlay.h"
#include "time_sync.h"
#include "wifi.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define CLOCK_UPDATE_MS 1000U
#define CLOCK_LAYOUT_CHANGE_MS (10U * 60U * 1000U)
#define CLOCK_VALID_YEAR 2024
#define CLOCK_ZONE_COUNT 3U
typedef enum { CLOCK_SECTION_TIME, CLOCK_SECTION_DATE, CLOCK_SECTION_COUNTDOWNS } clock_section_t;
static bool running;
static bool frame_ready;
static bool full_redraw;
static bool time_available;
static uint32_t last_update;
static uint32_t last_layout_change;
static int last_day_of_year;
static char time_text[16];
static char day_text[16];
static char date_text[32];
static char monday_countdown[20];
static char friday_countdown[20];
static uint8_t section_zone[CLOCK_ZONE_COUNT];
static const int zone_y[CLOCK_ZONE_COUNT] = {4, 58, 112};
static void configure_clock_timezone(void)
{
static bool configured;
if (configured) return;
setenv("TZ", "CET-1CEST,M3.5.0/02,M10.5.0/03", 1);
tzset();
configured = true;
}
static void choose_section_layout(void)
{
uint8_t previous_layout[CLOCK_ZONE_COUNT];
memcpy(previous_layout, section_zone, sizeof(previous_layout));
do {
for (uint8_t index = 0U; index < CLOCK_ZONE_COUNT; ++index) section_zone[index] = index;
for (uint8_t index = CLOCK_ZONE_COUNT - 1U; index > 0U; --index) {
const uint8_t swap = (uint8_t)(esp_random() % (index + 1U));
const uint8_t temporary = section_zone[index];
section_zone[index] = section_zone[swap];
section_zone[swap] = temporary;
}
} while (memcmp(previous_layout, section_zone, sizeof(section_zone)) == 0);
}
static int section_y(clock_section_t section)
{
return zone_y[section_zone[section]];
}
static void draw_unsynchronized_status(uint16_t *frame, int y)
{
const wifi_connection_state_t connection = wifiServiceConnectionState();
const time_sync_status_t sync_status = timeSyncStatus();
const char *status_text = "CLOCK NOT SET";
uint16_t status_colour = COLOR_YELLOW;
if (connection == WIFI_CONNECTION_CONNECTING) status_text = "WIFI CONNECTING...";
else if (connection == WIFI_CONNECTION_FAILED) {
status_text = "WIFI CONNECTION FAILED";
status_colour = COLOR_RED;
} else if (sync_status == TIME_SYNC_SYNCING) status_text = "TIME SYNCING...";
else if (sync_status == TIME_SYNC_FAILED) {
status_text = "TIME SYNC FAILED";
status_colour = COLOR_RED;
}
drawPet2001TextCentredTransparent(frame, 160, y + 18, status_text, status_colour, 1);
}
static void draw_time_section(uint16_t *frame, int y, const char *text, uint16_t colour)
{
fill_rect(frame, 0, y, LCD_WIDTH, 38, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, y, text, colour, 4);
}
static void draw_date_section(uint16_t *frame, int y)
{
fill_rect(frame, 0, y, LCD_WIDTH, 42, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, y + 2, day_text, COLOR_ORANGE, 2);
drawPet2001TextCentredTransparent(frame, 160, y + 24, date_text, COLOR_ORANGE, 1);
}
static void draw_countdown_section(uint16_t *frame, int y)
{
fill_rect(frame, 0, y, LCD_WIDTH, 43, COLOR_BLACK);
drawPet2001TextTransparent(frame, 8, y + 2, "DOOM", COLOR_CRIMSON, 1);
drawPet2001TextCentredTransparent(frame, 208, y, monday_countdown, COLOR_CRIMSON, 2);
drawPet2001TextTransparent(frame, 8, y + 25, "FREEDOM", COLOR_OLIVE, 1);
drawPet2001TextCentredTransparent(frame, 208, y + 23, friday_countdown, COLOR_OLIVE, 2);
}
static void format_countdown(char *text, size_t text_size, time_t now_time,
const struct tm *now, int target_weekday,
int target_hour, int target_minute)
{
struct tm target = *now;
int days_until_target = (target_weekday - target.tm_wday + 7) % 7;
target.tm_mday += days_until_target;
target.tm_hour = target_hour;
target.tm_min = target_minute;
target.tm_sec = 0;
target.tm_isdst = -1;
time_t target_time = mktime(&target);
if (difftime(target_time, now_time) <= 0.0) {
target.tm_mday += 7;
target.tm_isdst = -1;
target_time = mktime(&target);
}
long remaining = (long)difftime(target_time, now_time);
const long days = remaining / 86400L;
remaining %= 86400L;
const long hours = remaining / 3600L;
remaining %= 3600L;
const long minutes = remaining / 60L;
const long seconds = remaining % 60L;
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
}
static void format_doomsday_countdown(char *text, size_t text_size, time_t now_time)
{
struct tm target = {
.tm_year = 2027 - 1900,
.tm_mon = 1,
.tm_mday = 5,
.tm_isdst = -1,
};
long remaining = (long)difftime(mktime(&target), now_time);
if (remaining < 0L) remaining = 0L;
const long days = remaining / 86400L;
remaining %= 86400L;
const long hours = remaining / 3600L;
remaining %= 3600L;
const long minutes = remaining / 60L;
const long seconds = remaining % 60L;
snprintf(text, text_size, "%ldd %02ld:%02ld:%02ld", days, hours, minutes, seconds);
}
static void update_time_strings(void)
{
const time_t now_time = time(NULL);
struct tm local_time = {0};
if (now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL ||
local_time.tm_year + 1900 < CLOCK_VALID_YEAR) {
if (time_available) full_redraw = true;
time_available = false;
return;
}
if (!time_available || last_day_of_year != local_time.tm_yday) full_redraw = true;
time_available = true;
strftime(time_text, sizeof(time_text), "%H:%M:%S", &local_time);
strftime(day_text, sizeof(day_text), "%A", &local_time);
strftime(date_text, sizeof(date_text), "%d %B %Y", &local_time);
format_doomsday_countdown(monday_countdown, sizeof(monday_countdown), now_time);
format_countdown(friday_countdown, sizeof(friday_countdown), now_time,
&local_time, 5, 15, 0);
last_day_of_year = local_time.tm_yday;
}
void startClock(uint32_t now_ms)
{
configure_clock_timezone();
running = true;
frame_ready = true;
full_redraw = true;
time_available = false;
last_day_of_year = -1;
last_update = now_ms - CLOCK_UPDATE_MS;
last_layout_change = now_ms;
choose_section_layout();
}
void stopClock(void)
{
running = false;
frame_ready = false;
}
void updateClock(uint32_t now_ms)
{
if (!running || now_ms - last_update < CLOCK_UPDATE_MS) return;
last_update = now_ms;
if (now_ms - last_layout_change >= CLOCK_LAYOUT_CHANGE_MS) {
choose_section_layout();
last_layout_change = now_ms;
full_redraw = true;
}
update_time_strings();
frame_ready = true;
}
bool clockTakeFrame(void)
{
const bool ready = frame_ready;
frame_ready = false;
return ready;
}
void drawClock(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (full_redraw) {
clear(frame, COLOR_BLACK);
}
if (!time_available) {
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), "--:--:--", COLOR_RED);
fill_rect(frame, 0, section_y(CLOCK_SECTION_DATE), LCD_WIDTH, 42, COLOR_BLACK);
draw_unsynchronized_status(frame, section_y(CLOCK_SECTION_DATE));
full_redraw = false;
return;
}
draw_time_section(frame, section_y(CLOCK_SECTION_TIME), time_text, COLOR_RED);
draw_date_section(frame, section_y(CLOCK_SECTION_DATE));
draw_countdown_section(frame, section_y(CLOCK_SECTION_COUNTDOWNS));
full_redraw = false;
}
void updateDoomCountdownOverlay(void)
{
configure_clock_timezone();
const time_t now_time = time(NULL);
struct tm local_time = {0};
if (now_time == (time_t)-1 || localtime_r(&now_time, &local_time) == NULL ||
local_time.tm_year + 1900 < CLOCK_VALID_YEAR) {
globalOverlayClearItem(0U);
return;
}
char countdown[20];
char overlay_text[32];
format_doomsday_countdown(countdown, sizeof(countdown), now_time);
snprintf(overlay_text, sizeof(overlay_text), "DOOM %s", countdown);
// Pixel coordinates are deliberately independent of individual screens: move this
// item anywhere on the LCD by changing its x/y values without changing a renderer.
(void)globalOverlaySetItem(0U, overlay_text, 2, 2, 1, COLOR_ORANGE);
}