| /* |
| * Copyright (c) 2023, Prevas A/S <kim.bondergaard@prevas.dk> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/shell/shell.h> |
| #include <zephyr/drivers/rtc.h> |
| #include <time.h> |
| #include <stdlib.h> |
| |
| /* Formats accepted when setting date and/or time */ |
| static const char format_iso8601[] = "%FT%T"; |
| static const char format_time[] = "%T"; /* hh:mm:ss */ |
| static const char format_date[] = " %F"; /* yyyy-mm-dd */ |
| |
| static const char *consume_chars(const char *s, char *dest, unsigned int cnt) |
| { |
| if (strlen(s) < cnt) { |
| return NULL; |
| } |
| |
| memcpy(dest, s, cnt); |
| dest[cnt] = '\0'; |
| |
| return s + cnt; |
| } |
| |
| static const char *consume_char(const char *s, char ch) |
| { |
| if (*s != ch) { |
| return NULL; |
| } |
| return ++s; |
| } |
| |
| static const char *consume_date(const char *s, struct tm *tm_time) |
| { |
| char year[4 + 1]; |
| char month[2 + 1]; |
| char day[2 + 1]; |
| |
| s = consume_chars(s, year, 4); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_char(s, '-'); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_chars(s, month, 2); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_char(s, '-'); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_chars(s, day, 2); |
| if (!s) { |
| return NULL; |
| } |
| |
| tm_time->tm_year = atoi(year) - 1900; |
| tm_time->tm_mon = atoi(month) - 1; |
| tm_time->tm_mday = atoi(day); |
| |
| return s; |
| } |
| |
| static const char *consume_time(const char *s, struct tm *tm_time) |
| { |
| char hour[2 + 1]; |
| char minute[2 + 1]; |
| char second[2 + 1]; |
| |
| s = consume_chars(s, hour, 2); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_char(s, ':'); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_chars(s, minute, 2); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_char(s, ':'); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_chars(s, second, 2); |
| if (!s) { |
| return NULL; |
| } |
| |
| tm_time->tm_hour = atoi(hour); |
| tm_time->tm_min = atoi(minute); |
| tm_time->tm_sec = atoi(second); |
| |
| return s; |
| } |
| |
| static char *strptime(const char *s, const char *format, struct tm *tm_time) |
| { |
| /* Reduced implementation of strptime - |
| * accepting only the 3 different format strings |
| */ |
| if (!strcmp(format, format_iso8601)) { |
| s = consume_date(s, tm_time); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_char(s, 'T'); |
| if (!s) { |
| return NULL; |
| } |
| |
| s = consume_time(s, tm_time); |
| if (!s) { |
| return NULL; |
| } |
| |
| return (char *)s; |
| |
| } else if (!strcmp(format, format_time)) { |
| return (char *)consume_time(s, tm_time); |
| |
| } else if (!strcmp(format, format_date)) { |
| return (char *)consume_date(s, tm_time); |
| |
| } else { |
| return NULL; |
| } |
| } |
| |
| static int cmd_set(const struct shell *sh, size_t argc, char **argv) |
| { |
| const struct device *dev = device_get_binding(argv[1]); |
| |
| if (!device_is_ready(dev)) { |
| shell_error(sh, "device %s not ready", argv[1]); |
| return -ENODEV; |
| } |
| |
| argc--; |
| argv++; |
| |
| struct rtc_time rtctime = {0}; |
| struct tm *tm_time = rtc_time_to_tm(&rtctime); |
| |
| (void)rtc_get_time(dev, &rtctime); |
| |
| const char *format; |
| |
| if (strchr(argv[1], 'T')) { |
| format = format_iso8601; |
| } else if (strchr(argv[1], '-')) { |
| format = format_date; |
| } else { |
| format = format_time; |
| } |
| |
| char *parseRes = strptime(argv[1], format, tm_time); |
| |
| if (!parseRes || *parseRes != '\0') { |
| shell_error(sh, "Error in argument format"); |
| return -EINVAL; |
| } |
| |
| int res = rtc_set_time(dev, &rtctime); |
| |
| if (-EINVAL == res) { |
| shell_error(sh, "error in time"); |
| return -EINVAL; |
| } |
| return res; |
| } |
| |
| static int cmd_get(const struct shell *sh, size_t argc, char **argv) |
| { |
| const struct device *dev = device_get_binding(argv[1]); |
| |
| if (!device_is_ready(dev)) { |
| shell_error(sh, "device %s not ready", argv[1]); |
| return -ENODEV; |
| } |
| |
| struct rtc_time rtctime; |
| |
| int res = rtc_get_time(dev, &rtctime); |
| |
| if (-ENODATA == res) { |
| shell_print(sh, "RTC not set"); |
| return 0; |
| } |
| if (res < 0) { |
| return res; |
| } |
| |
| shell_print(sh, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", rtctime.tm_year + 1900, |
| rtctime.tm_mon + 1, rtctime.tm_mday, rtctime.tm_hour, rtctime.tm_min, |
| rtctime.tm_sec, rtctime.tm_nsec / 1000000); |
| |
| return 0; |
| } |
| |
| static void device_name_get(size_t idx, struct shell_static_entry *entry) |
| { |
| const struct device *dev = shell_device_lookup(idx, NULL); |
| |
| entry->syntax = (dev != NULL) ? dev->name : NULL; |
| entry->handler = NULL; |
| entry->help = NULL; |
| entry->subcmd = NULL; |
| } |
| |
| #define RTC_GET_HELP \ |
| ("Get current time (UTC)\n" \ |
| "Usage: rtc get <device>") |
| |
| #define RTC_SET_HELP \ |
| ("Set UTC time\n" \ |
| "Usage: rtc set <device> <YYYY-MM-DDThh:mm:ss> | <YYYY-MM-DD> | <hh:mm:ss>") |
| |
| SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get); |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(sub_rtc, |
| /* Alphabetically sorted */ |
| SHELL_CMD_ARG(set, &dsub_device_name, RTC_SET_HELP, cmd_set, 3, 0), |
| SHELL_CMD_ARG(get, &dsub_device_name, RTC_GET_HELP, cmd_get, 2, 0), |
| SHELL_SUBCMD_SET_END); |
| |
| SHELL_CMD_REGISTER(rtc, &sub_rtc, "RTC commands", NULL); |