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