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