1 /*
2  * Copyright (c) 2023, Prevas A/S <kim.bondergaard@prevas.dk>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  */
7 
8 #include <zephyr/kernel.h>
9 #include <zephyr/shell/shell.h>
10 #include <zephyr/drivers/rtc.h>
11 #include <time.h>
12 #include <stdlib.h>
13 
14 /* Formats accepted when setting date and/or time */
15 static const char format_iso8601[] = "%FT%T";
16 static const char format_time[] = "%T";  /* hh:mm:ss */
17 static const char format_date[] = " %F"; /* yyyy-mm-dd */
18 
19 #if !defined CONFIG_BOARD_NATIVE_POSIX
20 
consume_chars(const char * s,char * dest,unsigned int cnt)21 static const char *consume_chars(const char *s, char *dest, unsigned int cnt)
22 {
23 	if (strlen(s) < cnt) {
24 		return NULL;
25 	}
26 
27 	memcpy(dest, s, cnt);
28 	dest[cnt] = '\0';
29 
30 	return s + cnt;
31 }
32 
consume_char(const char * s,char ch)33 static const char *consume_char(const char *s, char ch)
34 {
35 	if (*s != ch) {
36 		return NULL;
37 	}
38 	return ++s;
39 }
40 
consume_date(const char * s,struct tm * tm_time)41 static const char *consume_date(const char *s, struct tm *tm_time)
42 {
43 	char year[4 + 1];
44 	char month[2 + 1];
45 	char day[2 + 1];
46 
47 	s = consume_chars(s, year, 4);
48 	if (!s) {
49 		return NULL;
50 	}
51 
52 	s = consume_char(s, '-');
53 	if (!s) {
54 		return NULL;
55 	}
56 
57 	s = consume_chars(s, month, 2);
58 	if (!s) {
59 		return NULL;
60 	}
61 
62 	s = consume_char(s, '-');
63 	if (!s) {
64 		return NULL;
65 	}
66 
67 	s = consume_chars(s, day, 2);
68 	if (!s) {
69 		return NULL;
70 	}
71 
72 	tm_time->tm_year = atoi(year) - 1900;
73 	tm_time->tm_mon = atoi(month) - 1;
74 	tm_time->tm_mday = atoi(day);
75 
76 	return s;
77 }
78 
consume_time(const char * s,struct tm * tm_time)79 static const char *consume_time(const char *s, struct tm *tm_time)
80 {
81 	char hour[2 + 1];
82 	char minute[2 + 1];
83 	char second[2 + 1];
84 
85 	s = consume_chars(s, hour, 2);
86 	if (!s) {
87 		return NULL;
88 	}
89 
90 	s = consume_char(s, ':');
91 	if (!s) {
92 		return NULL;
93 	}
94 
95 	s = consume_chars(s, minute, 2);
96 	if (!s) {
97 		return NULL;
98 	}
99 
100 	s = consume_char(s, ':');
101 	if (!s) {
102 		return NULL;
103 	}
104 
105 	s = consume_chars(s, second, 2);
106 	if (!s) {
107 		return NULL;
108 	}
109 
110 	tm_time->tm_hour = atoi(hour);
111 	tm_time->tm_min = atoi(minute);
112 	tm_time->tm_sec = atoi(second);
113 
114 	return s;
115 }
116 
strptime(const char * s,const char * format,struct tm * tm_time)117 static char *strptime(const char *s, const char *format, struct tm *tm_time)
118 {
119 	/* Reduced implementation of strptime -
120 	 * accepting only the 3 different format strings
121 	 */
122 	if (!strcmp(format, format_iso8601)) {
123 		s = consume_date(s, tm_time);
124 		if (!s) {
125 			return NULL;
126 		}
127 
128 		s = consume_char(s, 'T');
129 		if (!s) {
130 			return NULL;
131 		}
132 
133 		s = consume_time(s, tm_time);
134 		if (!s) {
135 			return NULL;
136 		}
137 
138 		return (char *)s;
139 
140 	} else if (!strcmp(format, format_time)) {
141 		return (char *)consume_time(s, tm_time);
142 
143 	} else if (!strcmp(format, format_date)) {
144 		return (char *)consume_date(s, tm_time);
145 
146 	} else {
147 		return NULL;
148 	}
149 }
150 
151 #endif
152 
cmd_set(const struct shell * sh,size_t argc,char ** argv)153 static int cmd_set(const struct shell *sh, size_t argc, char **argv)
154 {
155 	const struct device *dev = device_get_binding(argv[1]);
156 
157 	if (!device_is_ready(dev)) {
158 		shell_error(sh, "device %s not ready", argv[1]);
159 		return -ENODEV;
160 	}
161 
162 	argc--;
163 	argv++;
164 
165 	struct rtc_time rtctime = {0};
166 	struct tm *tm_time = rtc_time_to_tm(&rtctime);
167 
168 	(void)rtc_get_time(dev, &rtctime);
169 
170 	const char *format;
171 
172 	if (strchr(argv[1], 'T')) {
173 		format = format_iso8601;
174 	} else if (strchr(argv[1], '-')) {
175 		format = format_date;
176 	} else {
177 		format = format_time;
178 	}
179 
180 	char *parseRes = strptime(argv[1], format, tm_time);
181 
182 	if (!parseRes || *parseRes != '\0') {
183 		shell_error(sh, "Error in argument format");
184 		return -EINVAL;
185 	}
186 
187 	int res = rtc_set_time(dev, &rtctime);
188 
189 	if (-EINVAL == res) {
190 		shell_error(sh, "error in time");
191 		return -EINVAL;
192 	}
193 	return res;
194 }
195 
cmd_get(const struct shell * sh,size_t argc,char ** argv)196 static int cmd_get(const struct shell *sh, size_t argc, char **argv)
197 {
198 	const struct device *dev = device_get_binding(argv[1]);
199 
200 	if (!device_is_ready(dev)) {
201 		shell_error(sh, "device %s not ready", argv[1]);
202 		return -ENODEV;
203 	}
204 
205 	struct rtc_time rtctime;
206 
207 	int res = rtc_get_time(dev, &rtctime);
208 
209 	if (-ENODATA == res) {
210 		shell_print(sh, "RTC not set");
211 		return 0;
212 	}
213 	if (res < 0) {
214 		return res;
215 	}
216 
217 	shell_print(sh, "%04d-%02d-%02dT%02d:%02d:%02d:%06d", rtctime.tm_year + 1900,
218 		    rtctime.tm_mon + 1, rtctime.tm_mday, rtctime.tm_hour, rtctime.tm_min,
219 		    rtctime.tm_sec, rtctime.tm_nsec / 1000000);
220 
221 	return 0;
222 }
223 
224 #define RTC_GET_HELP                                                                               \
225 	("Get current time (UTC)\n"                                                                \
226 	 "Usage: rtc get <device>")
227 
228 #define RTC_SET_HELP                                                                               \
229 	("Set UTC time\n"                                                                          \
230 	 "Usage: rtc set <device> <YYYY-MM-DDThh:mm:ss> | <YYYY-MM-DD> | <hh:mm:ss>")
231 
232 SHELL_STATIC_SUBCMD_SET_CREATE(sub_rtc,
233 			       /* Alphabetically sorted */
234 			       SHELL_CMD_ARG(set, NULL, RTC_SET_HELP, cmd_set, 3, 0),
235 			       SHELL_CMD_ARG(get, NULL, RTC_GET_HELP, cmd_get, 2, 0),
236 			       SHELL_SUBCMD_SET_END);
237 
238 SHELL_CMD_REGISTER(rtc, &sub_rtc, "RTC commands", NULL);
239