Implementation of DateTime Configuration in NWatch Settings Menu

Initializing the DateTime Configuraton Screen

When entering the DateTime settings screen, the interface is initialized with appropriate menu options and callbacks. This screen uses string-based (STR) rendering.

static void updateTimeDisplay(void) {
    char buffer[17];
    uint8_t displayMode = (timeDateSet.time.ampm != CHAR_24) ? 12 : 24;
    sprintf(buffer, PSTR("   %hhuhr %02hhu:%02hhu%c"), displayMode,
            timeDateSet.time.hour, timeDateSet.time.mins, timeDateSet.time.ampm);
    SetMenuOption(3, buffer, NULL, handleTimeSelection);
}

static void updateDateDisplay(void) {
    char buffer[BUFFSIZE_STR_MONTHS + 14];
    char monthName[BUFFSIZE_STR_MONTHS];
    strcpy(monthName, months[timeDateSet.date.month]);
    sprintf(buffer, PSTR("   %02hhu %s 20%02hhu"),
            timeDateSet.date.date, monthName, timeDateSet.date.year);
    SetMenuOption(1, buffer, NULL, handleDateSelection);
}

static void dateTimeLoader(uint8_t unused) {
    (void)unused;
    updateDateDisplay();
    updateTimeDisplay();
    SetMenuOption(5, saved ? PSTR(STR_SAVED) : PSTR(STR_SAVE),
                  &menu_volume[appConfig.volAlarm][0], persistDateTimeSettings);
    addExitOption();
}

void initializeDateTimeMenu(void) {
    memcpy(&timeDateSet, &timeDate, sizeof(timeDate_s));
    timeMode = appConfig.timeMode;
    saved = false;

    SetButtonFunc(menuUp, (bool (*)(void))handleDateTimeSelect, menuDown);
    SetMenuFunc(navigateUp, handleDateTimeSelect, navigateDown, dateTimeLoader);
    SetMenuInfo(7, MENU_TYPE_STR, PSTR(STR_TIMEDATEMENU));
    SetMenuPre(&returnToPreviousMenu, initializeDateTimeMenu);

    Main_Menu.selected = 1;
    AnimotionInto(NULL);
}

Editing Date and Time Using a State Machine

A state machine manages field-by-field editing. Upon confirmation, editing starts from SETTING_NOW_TIMEMODE. Each press cycles through time mode, hour, minute, and AM/PM (if applicable), or date, month, and year for the date section. After the final field, the editor exits back to the main DateTime view.

static void startEditing(void) {
    Main_Menu.func.draw = renderDateTimeEdit;
    SetMenuFunc(openTimeEditorUp, handleDateTimeSelect, openTimeEditorDown, dateTimeLoader);
}

static void finishEditing(void) {
    setting.now = SETTING_NOW_NONE;
    Main_Menu.func.draw = NULL;
    SetMenuFunc(navigateUp, handleDateTimeSelect, navigateDown, dateTimeLoader);
}

static void handleTimeSelection(void) {
    startEditing();

    switch (setting.now) {
        case SETTING_NOW_NONE:
            setting.now = SETTING_NOW_TIMEMODE;
            setting.val = timeMode;
            break;
        case SETTING_NOW_TIMEMODE:
            timeMode = (timemode_t)setting.val;
            TimeSetMode(&timeDateSet.time, timeMode);
            setting.val = timeDateSet.time.hour;
            setting.now = SETTING_NOW_HOUR;
            break;
        case SETTING_NOW_HOUR:
            timeDateSet.time.hour = setting.val;
            setting.val = timeDateSet.time.mins;
            setting.now = SETTING_NOW_MIN;
            break;
        case SETTING_NOW_MIN:
            timeDateSet.time.mins = setting.val;
            if (timeMode == TIMEMODE_12HR) {
                setting.now = SETTING_NOW_AMPM;
                setting.val = (timeDateSet.time.ampm == CHAR_AM);
            } else {
                timeDateSet.time.ampm = CHAR_24;
                finishEditing();
            }
            break;
        default: // SETTING_NOW_AMPM
            timeDateSet.time.ampm = setting.val ? CHAR_AM : CHAR_PM;
            finishEditing();
            break;
    }
}

static void handleDateSelection(void) {
    startEditing();

    switch (setting.now) {
        case SETTING_NOW_NONE:
            setting.now = SETTING_NOW_DATE;
            setting.val = timeDateSet.date.date;
            break;
        case SETTING_NOW_DATE:
            timeDateSet.date.date = setting.val;
            if (timeDateSet.date.date < 1) timeDateSet.date.date = 1;
            setting.now = SETTING_NOW_MONTH;
            setting.val = timeDateSet.date.month;
            break;
        case SETTING_NOW_MONTH: {
            timeDateSet.date.month = (month_t)setting.val;
            uint8_t maxDays = TimeMonthDayCount(timeDateSet.date.month, timeDateSet.date.year);
            if (timeDateSet.date.date > maxDays)
                timeDateSet.date.date = maxDays;
            setting.now = SETTING_NOW_YEAR;
            setting.val = timeDateSet.date.year;
            break;
        }
        default: // SETTING_NOW_YEAR
            timeDateSet.date.year = setting.val;
            timeDateSet.date.day = TimeDow(timeDateSet.date.year,
                                           timeDateSet.date.month,
                                           timeDateSet.date.date);
            finishEditing();
            break;
    }
}

Rednering the Active Edit Field on OLED

During editing, the current field is highlighted and its value is dynamically rendered on the OLED display at the apropriate position.

static void renderDateTimeEdit(void) {
    uint8_t x, y, width = 16;

    switch (setting.now) {
        case SETTING_NOW_DATE:     x = 30; y = 16; width = 16; break;
        case SETTING_NOW_MONTH:    x = 54; y = 16; width = 24; break;
        case SETTING_NOW_YEAR:     x = 102; y = 16; width = 16; break;
        case SETTING_NOW_TIMEMODE: x = 30; y = 32; width = 16; break;
        case SETTING_NOW_HOUR:     x = 70; y = 32; width = 16; break;
        case SETTING_NOW_MIN:      x = 94; y = 32; width = 16; break;
        case SETTING_NOW_AMPM:     x = 110; y = 32; width = 8; break;
        default: return;
    }

    OledClearArea(x, y, width);
    char displayBuf[5];

    if (setting.now == SETTING_NOW_MONTH) {
        strcpy(displayBuf, months[setting.val]);
    } else if (setting.now == SETTING_NOW_AMPM) {
        displayBuf[0] = setting.val ? CHAR_AM : CHAR_PM;
        displayBuf[1] = '\0';
    } else if (setting.now == SETTING_NOW_TIMEMODE) {
        if (setting.val) {
            displayBuf[0] = '1'; displayBuf[1] = '2';
        } else {
            displayBuf[0] = '2'; displayBuf[1] = '4';
        }
        displayBuf[2] = 'h'; displayBuf[3] = 'r'; displayBuf[4] = '\0';
    } else {
        sprintf(displayBuf, PSTR("%02hhu"), setting.val);
    }

    OledShowStr(x, y, displayBuf, OLED_8X8);
}

Tags: embedded-systems C real-time-clock OLED-display state-machine

Posted on Thu, 11 Jun 2026 18:29:00 +0000 by John_S