1 /*
2  * Copyright (c) 2024 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/shell/shell.h>
9 #include <zephyr/modem/chat.h>
10 #include <zephyr/modem/pipelink.h>
11 #include <zephyr/sys/atomic.h>
12 
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(modem_at_shell, CONFIG_MODEM_LOG_LEVEL);
15 
16 #define AT_SHELL_MODEM_NODE DT_ALIAS(modem)
17 #define AT_SHELL_PIPELINK_NAME _CONCAT(user_pipe_, CONFIG_MODEM_AT_SHELL_USER_PIPE)
18 
19 #define AT_SHELL_STATE_ATTACHED_BIT       0
20 #define AT_SHELL_STATE_SCRIPT_RUNNING_BIT 1
21 
22 MODEM_PIPELINK_DT_DECLARE(AT_SHELL_MODEM_NODE, AT_SHELL_PIPELINK_NAME);
23 
24 static struct modem_pipelink *at_shell_pipelink =
25 	MODEM_PIPELINK_DT_GET(AT_SHELL_MODEM_NODE, AT_SHELL_PIPELINK_NAME);
26 
27 static struct modem_chat at_shell_chat;
28 static uint8_t at_shell_chat_receive_buf[CONFIG_MODEM_AT_SHELL_CHAT_RECEIVE_BUF_SIZE];
29 static uint8_t *at_shell_chat_argv_buf[2];
30 static uint8_t at_shell_request_buf[CONFIG_MODEM_AT_SHELL_COMMAND_MAX_SIZE];
31 static struct modem_chat_script_chat at_shell_script_chat[1];
32 static struct modem_chat_match at_shell_script_chat_matches[2];
33 static uint8_t at_shell_match_buf[CONFIG_MODEM_AT_SHELL_RESPONSE_MAX_SIZE];
34 static const struct shell *at_shell_active_shell;
35 static struct k_work at_shell_open_pipe_work;
36 static struct k_work at_shell_attach_chat_work;
37 static struct k_work at_shell_release_chat_work;
38 static atomic_t at_shell_state;
39 
at_shell_print_any_match(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)40 static void at_shell_print_any_match(struct modem_chat *chat, char **argv, uint16_t argc,
41 				     void *user_data)
42 {
43 	if (at_shell_active_shell == NULL) {
44 		return;
45 	}
46 
47 	if (argc != 2) {
48 		return;
49 	}
50 
51 	shell_print(at_shell_active_shell, "%s", argv[1]);
52 }
53 
at_shell_print_match(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)54 static void at_shell_print_match(struct modem_chat *chat, char **argv, uint16_t argc,
55 				 void *user_data)
56 {
57 	if (at_shell_active_shell == NULL) {
58 		return;
59 	}
60 
61 	if (argc != 1) {
62 		return;
63 	}
64 
65 	shell_print(at_shell_active_shell, "%s", argv[0]);
66 }
67 
68 MODEM_CHAT_MATCHES_DEFINE(
69 	at_shell_abort_matches,
70 	MODEM_CHAT_MATCH("ERROR", "", at_shell_print_match),
71 );
72 
at_shell_script_callback(struct modem_chat * chat,enum modem_chat_script_result result,void * user_data)73 static void at_shell_script_callback(struct modem_chat *chat,
74 				     enum modem_chat_script_result result,
75 				     void *user_data)
76 {
77 	atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT);
78 }
79 
80 MODEM_CHAT_SCRIPT_DEFINE(
81 	at_shell_script,
82 	at_shell_script_chat,
83 	at_shell_abort_matches,
84 	at_shell_script_callback,
85 	CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S
86 );
87 
at_shell_pipe_callback(struct modem_pipe * pipe,enum modem_pipe_event event,void * user_data)88 static void at_shell_pipe_callback(struct modem_pipe *pipe,
89 				   enum modem_pipe_event event,
90 				   void *user_data)
91 {
92 	ARG_UNUSED(user_data);
93 
94 	switch (event) {
95 	case MODEM_PIPE_EVENT_OPENED:
96 		LOG_INF("pipe opened");
97 		k_work_submit(&at_shell_attach_chat_work);
98 		break;
99 
100 	default:
101 		break;
102 	}
103 }
104 
at_shell_pipelink_callback(struct modem_pipelink * link,enum modem_pipelink_event event,void * user_data)105 void at_shell_pipelink_callback(struct modem_pipelink *link,
106 				enum modem_pipelink_event event,
107 				void *user_data)
108 {
109 	ARG_UNUSED(user_data);
110 
111 	switch (event) {
112 	case MODEM_PIPELINK_EVENT_CONNECTED:
113 		LOG_INF("pipe connected");
114 		k_work_submit(&at_shell_open_pipe_work);
115 		break;
116 
117 	case MODEM_PIPELINK_EVENT_DISCONNECTED:
118 		LOG_INF("pipe disconnected");
119 		k_work_submit(&at_shell_release_chat_work);
120 		break;
121 
122 	default:
123 		break;
124 	}
125 }
126 
at_shell_open_pipe_handler(struct k_work * work)127 static void at_shell_open_pipe_handler(struct k_work *work)
128 {
129 	ARG_UNUSED(work);
130 
131 	LOG_INF("opening pipe");
132 
133 	modem_pipe_attach(modem_pipelink_get_pipe(at_shell_pipelink),
134 			  at_shell_pipe_callback,
135 			  NULL);
136 
137 	modem_pipe_open_async(modem_pipelink_get_pipe(at_shell_pipelink));
138 }
139 
at_shell_attach_chat_handler(struct k_work * work)140 static void at_shell_attach_chat_handler(struct k_work *work)
141 {
142 	ARG_UNUSED(work);
143 
144 	modem_chat_attach(&at_shell_chat, modem_pipelink_get_pipe(at_shell_pipelink));
145 	atomic_set_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT);
146 	LOG_INF("chat attached");
147 }
148 
at_shell_release_chat_handler(struct k_work * work)149 static void at_shell_release_chat_handler(struct k_work *work)
150 {
151 	ARG_UNUSED(work);
152 
153 	modem_chat_release(&at_shell_chat);
154 	atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT);
155 	LOG_INF("chat released");
156 }
157 
at_shell_init_work(void)158 static void at_shell_init_work(void)
159 {
160 	k_work_init(&at_shell_open_pipe_work, at_shell_open_pipe_handler);
161 	k_work_init(&at_shell_attach_chat_work, at_shell_attach_chat_handler);
162 	k_work_init(&at_shell_release_chat_work, at_shell_release_chat_handler);
163 }
164 
at_shell_init_chat(void)165 static void at_shell_init_chat(void)
166 {
167 	const struct modem_chat_config at_shell_chat_config = {
168 		.receive_buf = at_shell_chat_receive_buf,
169 		.receive_buf_size = sizeof(at_shell_chat_receive_buf),
170 		.delimiter = "\r",
171 		.delimiter_size = sizeof("\r") - 1,
172 		.filter = "\n",
173 		.filter_size = sizeof("\n") - 1,
174 		.argv = at_shell_chat_argv_buf,
175 		.argv_size = ARRAY_SIZE(at_shell_chat_argv_buf),
176 	};
177 
178 	modem_chat_init(&at_shell_chat, &at_shell_chat_config);
179 }
180 
at_shell_init_script_chat(void)181 static void at_shell_init_script_chat(void)
182 {
183 	/* Match anything except the expected response without progressing script */
184 	modem_chat_match_init(&at_shell_script_chat_matches[0]);
185 	modem_chat_match_set_match(&at_shell_script_chat_matches[0], "");
186 	modem_chat_match_set_separators(&at_shell_script_chat_matches[0], "");
187 	modem_chat_match_set_callback(&at_shell_script_chat_matches[0], at_shell_print_any_match);
188 	modem_chat_match_set_partial(&at_shell_script_chat_matches[0], true);
189 	modem_chat_match_enable_wildcards(&at_shell_script_chat_matches[0], false);
190 
191 	/* Match the expected response and terminate script */
192 	modem_chat_match_init(&at_shell_script_chat_matches[1]);
193 	modem_chat_match_set_match(&at_shell_script_chat_matches[1], "");
194 	modem_chat_match_set_separators(&at_shell_script_chat_matches[1], "");
195 	modem_chat_match_set_callback(&at_shell_script_chat_matches[1], at_shell_print_match);
196 	modem_chat_match_set_partial(&at_shell_script_chat_matches[1], false);
197 	modem_chat_match_enable_wildcards(&at_shell_script_chat_matches[1], false);
198 
199 	modem_chat_script_chat_init(at_shell_script_chat);
200 	modem_chat_script_chat_set_response_matches(at_shell_script_chat,
201 						    at_shell_script_chat_matches,
202 						    ARRAY_SIZE(at_shell_script_chat_matches));
203 	modem_chat_script_chat_set_timeout(at_shell_script_chat,
204 					   CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S);
205 }
206 
at_shell_init_pipelink(void)207 static void at_shell_init_pipelink(void)
208 {
209 	modem_pipelink_attach(at_shell_pipelink, at_shell_pipelink_callback, NULL);
210 }
211 
at_shell_init(void)212 static int at_shell_init(void)
213 {
214 	at_shell_init_work();
215 	at_shell_init_chat();
216 	at_shell_init_script_chat();
217 	at_shell_init_pipelink();
218 	return 0;
219 }
220 
221 SYS_INIT(at_shell_init, POST_KERNEL, 99);
222 
at_shell_cmd_handler(const struct shell * sh,size_t argc,char ** argv)223 static int at_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv)
224 {
225 	int ret;
226 
227 	if (argc < 2) {
228 		return -EINVAL;
229 	}
230 
231 	if (!atomic_test_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT)) {
232 		shell_error(sh, "modem is not ready");
233 		return -EPERM;
234 	}
235 
236 	if (atomic_test_and_set_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT)) {
237 		shell_error(sh, "script is already running");
238 		return -EBUSY;
239 	}
240 
241 	strncpy(at_shell_request_buf, argv[1], sizeof(at_shell_request_buf) - 1);
242 	ret = modem_chat_script_chat_set_request(at_shell_script_chat, at_shell_request_buf);
243 	if (ret < 0) {
244 		return -EINVAL;
245 	}
246 
247 	if (argc == 3) {
248 		strncpy(at_shell_match_buf, argv[2], sizeof(at_shell_match_buf) - 1);
249 	} else {
250 		strncpy(at_shell_match_buf, "OK", sizeof(at_shell_match_buf) - 1);
251 	}
252 
253 	ret = modem_chat_match_set_match(&at_shell_script_chat_matches[1], at_shell_match_buf);
254 	if (ret < 0) {
255 		return -EINVAL;
256 	}
257 
258 	at_shell_active_shell = sh;
259 
260 	ret = modem_chat_run_script_async(&at_shell_chat, &at_shell_script);
261 	if (ret < 0) {
262 		shell_error(sh, "failed to start script");
263 		atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT);
264 	}
265 
266 	return ret;
267 }
268 
269 SHELL_STATIC_SUBCMD_SET_CREATE(modem_sub_cmds,
270 	SHELL_CMD_ARG(at, NULL, "at <command> <response>", at_shell_cmd_handler, 1, 2),
271 	SHELL_SUBCMD_SET_END
272 );
273 
274 SHELL_CMD_REGISTER(modem, &modem_sub_cmds, "Modem commands", NULL);
275