Add ability to adjust RTC (#107)

<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->
This PR adds the ability to set the time from the menu.
The underlying logic is sound given what is currently available within
libdragon, but expected changes are noted (like `settimeofday`) for
future improvement and will need to be revisited once available.
A future PR will need to add better GUI components for numeric control.

## Motivation and Context
<!--- What does this sample do? What problem does it solve? -->
<!--- If it fixes/closes/resolves an open issue, please link to the
issue here -->
#106

## How Has This Been Tested?
<!-- (if applicable) -->
<!--- Please describe in detail how you tested your sample/changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->
locally on a SC64

## Screenshots
<!-- (if appropriate): -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] Improvement (non-breaking change that adds a new feature)
- [ ] Bug fix (fixes an issue)
- [ ] Breaking change (breaking change)
- [ ] Config and build (change in the configuration and build system,
has no impact on code or features)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.

<!--- It would be nice if you could sign off your contribution by
replacing the name with your GitHub user name and GitHub email contact.
-->
Signed-off-by: GITHUB_USER <GITHUB_USER_EMAIL>
This commit is contained in:
Robin Jones 2024-11-07 20:16:35 +00:00 committed by GitHub
parent 17c214537f
commit e7608dc557
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 184 additions and 30 deletions

View File

@ -1,26 +1,165 @@
#include <time.h> #include <stdbool.h>
#include <stdio.h>
#include <libdragon.h>
#include <sys/time.h>
#include "../sound.h" #include "../sound.h"
#include "views.h" #include "views.h"
// FIXME: add implementation! #define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; })
// struct { #define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; })
// uint16_t seconds; #define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max)))
// uint16_t minutes;
// uint16_t hours;
// uint16_t day;
// uint16_t month;
// uint16_t year;
// } adjusted_datetime;
// static void save_adjusted_datetime () { #define YEAR_MIN 1996
#define YEAR_MAX 2095
// } typedef enum {
RTC_EDIT_YEAR,
RTC_EDIT_MONTH,
RTC_EDIT_DAY,
RTC_EDIT_HOUR,
RTC_EDIT_MIN,
RTC_EDIT_SEC,
} rtc_field_t;
static const char* const DAYS_OF_WEEK[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static struct tm rtc_tm = {0};
static bool is_editing_mode;
static rtc_field_t editing_field_type;
int wrap( uint16_t val, uint16_t min, uint16_t max ) {
if( val < min ) return max;
if( val > max ) return min;
return val;
}
rtc_time_t rtc_time_from_tm( struct tm *time ) {
return(rtc_time_t){
.year = CLAMP(time->tm_year + 1900, YEAR_MIN, YEAR_MAX),
.month = CLAMP(time->tm_mon, 1, 12),
.day = CLAMP(time->tm_mday, 1, 31),
.hour = CLAMP(time->tm_hour, 0, 23),
.min = CLAMP(time->tm_min, 0, 59),
.sec = CLAMP(time->tm_sec, 0, 59),
.week_day = CLAMP(time->tm_wday, 0, 6),
};
}
void adjust_rtc_time( struct tm *t, int incr ) {
switch(editing_field_type)
{
case RTC_EDIT_YEAR:
t->tm_year = wrap( t->tm_year + incr, YEAR_MIN - 1900, YEAR_MAX - 1900 );
break;
case RTC_EDIT_MONTH:
t->tm_mon = wrap( t->tm_mon + incr, 0, 11 );
break;
case RTC_EDIT_DAY:
t->tm_mday = wrap( t->tm_mday + incr, 1, 31 );
break;
case RTC_EDIT_HOUR:
t->tm_hour = wrap( t->tm_hour + incr, 0, 23 );
break;
case RTC_EDIT_MIN:
t->tm_min = wrap( t->tm_min + incr, 0, 59 );
break;
case RTC_EDIT_SEC:
t->tm_sec = wrap( t->tm_sec + incr, 0, 59 );
break;
}
// Recalculate day-of-week and day-of-year
time_t timestamp = mktime( t );
*t = *gmtime( &timestamp );
}
void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) {
// FIXME: move this to components.c once improved.
/* Format RTC date/time as strings */
char full_dt[30];
char current_selection_chars[30];
snprintf( full_dt, sizeof(full_dt), ">%04d|%02d|%02d|%02d|%02d|%02d< %s",
t.tm_year + 1900,
t.tm_mon + 1,
t.tm_mday,
t.tm_hour,
t.tm_min,
t.tm_sec,
DAYS_OF_WEEK[t.tm_wday]
);
switch(selected_field)
{
// Note: for what ever reason, hat chars need to be duplicated to display correctly. This will be solved when there is a decent UI for it.
case RTC_EDIT_YEAR:
snprintf( current_selection_chars, sizeof(current_selection_chars), "*^^^^^^^^********************");
break;
case RTC_EDIT_MONTH:
snprintf( current_selection_chars, sizeof(current_selection_chars), "******^^^^*****************");
break;
case RTC_EDIT_DAY:
snprintf( current_selection_chars, sizeof(current_selection_chars), "*********^^^^**************");
break;
case RTC_EDIT_HOUR:
snprintf( current_selection_chars, sizeof(current_selection_chars), "************^^^^***********");
break;
case RTC_EDIT_MIN:
snprintf( current_selection_chars, sizeof(current_selection_chars), "***************^^^^********");
break;
case RTC_EDIT_SEC:
snprintf( current_selection_chars, sizeof(current_selection_chars), "******************^^^^*****");
break;
}
component_messagebox_draw(
"|YYYY|MM|DD|HH|MM|SS| DOW\n%s\n%s\n", full_dt, current_selection_chars);
}
static void process (menu_t *menu) { static void process (menu_t *menu) {
if (menu->actions.back) { if (menu->actions.back && !is_editing_mode) {
sound_play_effect(SFX_EXIT); sound_play_effect(SFX_EXIT);
menu->next_mode = MENU_MODE_BROWSER; menu->next_mode = MENU_MODE_BROWSER;
} }
else if (menu->actions.enter && !is_editing_mode) {
rtc_tm = *gmtime(&menu->current_time);
is_editing_mode = true;
}
if (is_editing_mode) {
if (menu->actions.go_left) {
if ( editing_field_type <= RTC_EDIT_YEAR ) { editing_field_type = RTC_EDIT_SEC; }
else { editing_field_type = editing_field_type - 1; }
}
else if (menu->actions.go_right) {
if ( editing_field_type >= RTC_EDIT_SEC ) { editing_field_type = RTC_EDIT_YEAR; }
else { editing_field_type = editing_field_type + 1; }
}
else if (menu->actions.go_up) {
adjust_rtc_time( &rtc_tm, +1 );
}
else if (menu->actions.go_down) {
adjust_rtc_time( &rtc_tm, -1 );
}
else if (menu->actions.options) { // R button = save
if(rtc_is_writable()) {
// FIXME: settimeofday is not available in libdragon yet.
// struct timeval new_time = { .tv_sec = mktime(&rtc_tm) };
// int res = settimeofday(&new_time, NULL);
rtc_time_t rtc_time = rtc_time_from_tm(&rtc_tm);
int res = rtc_set(&rtc_time);
if (res != 1) {
menu_show_error(menu, "Failed to set RTC time");
}
}
else {
menu_show_error(menu, "RTC is not writable");
}
is_editing_mode = false;
}
else if (menu->actions.back) { // cancel
is_editing_mode = false;
}
}
} }
static void draw (menu_t *menu, surface_t *d) { static void draw (menu_t *menu, surface_t *d) {
@ -35,31 +174,46 @@ static void draw (menu_t *menu, surface_t *d) {
"ADJUST REAL TIME CLOCK\n" "ADJUST REAL TIME CLOCK\n"
"\n" "\n"
"\n" "\n"
"To set the date and time, please use the PC terminal\n" "To set the RTC date and time, Press A.\n"
"application and set via USB,\n or a N64 game with RTC support.\n\n" "You can also use the PC terminal application via USB,\n"
"Current date & time: %s\n", "or even an N64 game with RTC support.\n"
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n" "\n"
"Current date & time: %s\n"
"\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown"
); );
component_main_text_draw( if (!is_editing_mode) {
ALIGN_LEFT, VALIGN_TOP,
"\n"
"\n"
);
component_actions_bar_text_draw( component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP, ALIGN_LEFT, VALIGN_TOP,
"\n" // "A: Save\n" "A: Change\n"
"B: Back" "B: Back"
); );
}
else {
component_actions_bar_text_draw(
ALIGN_RIGHT, VALIGN_TOP,
"Up/Down: Adjust Field\n"
"Left/Right: Switch Field"
);
component_actions_bar_text_draw(
ALIGN_LEFT, VALIGN_TOP,
"R: Save\n"
"B: Back"
);
}
if (is_editing_mode) {
component_editdatetime_draw(rtc_tm, editing_field_type);
}
rdpq_detach_show(); rdpq_detach_show();
} }
void view_rtc_init (menu_t *menu) { void view_rtc_init (menu_t *menu) {
// Nothing to initialize (yet) is_editing_mode = false;
editing_field_type = RTC_EDIT_YEAR;
} }
void view_rtc_display (menu_t *menu, surface_t *display) { void view_rtc_display (menu_t *menu, surface_t *display) {

View File

@ -57,7 +57,7 @@ static void draw (menu_t *menu, surface_t *d) {
"Joypad 2 is %sconnected %s\n" "Joypad 2 is %sconnected %s\n"
"Joypad 3 is %sconnected %s\n" "Joypad 3 is %sconnected %s\n"
"Joypad 4 is %sconnected %s\n", "Joypad 4 is %sconnected %s\n",
menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n", menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown",
is_memory_expanded() ? "" : "not ", is_memory_expanded() ? "" : "not ",
(joypad[0]) ? "" : "not ", format_accessory(0), (joypad[0]) ? "" : "not ", format_accessory(0),
(joypad[1]) ? "" : "not ", format_accessory(1), (joypad[1]) ? "" : "not ", format_accessory(1),