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/at/user_pipe.h>
10 #include <zephyr/modem/chat.h>
11 #include <zephyr/modem/pipelink.h>
12 #include <zephyr/sys/atomic.h>
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(modem_at_shell, CONFIG_MODEM_LOG_LEVEL);
16 
17 static struct modem_chat at_shell_chat;
18 static uint8_t at_shell_chat_receive_buf[CONFIG_MODEM_AT_SHELL_CHAT_RECEIVE_BUF_SIZE];
19 static uint8_t *at_shell_chat_argv_buf[2];
20 static uint8_t at_shell_request_buf[CONFIG_MODEM_AT_SHELL_COMMAND_MAX_SIZE];
21 static struct modem_chat_script_chat at_shell_script_chat[1];
22 static struct modem_chat_match at_shell_script_chat_matches[2];
23 static uint8_t at_shell_match_buf[CONFIG_MODEM_AT_SHELL_RESPONSE_MAX_SIZE];
24 static const struct shell *at_shell_active_shell;
25 
at_shell_print_any_match(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)26 static void at_shell_print_any_match(struct modem_chat *chat, char **argv, uint16_t argc,
27 				     void *user_data)
28 {
29 	if (at_shell_active_shell == NULL) {
30 		return;
31 	}
32 
33 	if (argc != 2) {
34 		return;
35 	}
36 
37 	shell_print(at_shell_active_shell, "%s", argv[1]);
38 }
39 
at_shell_print_match(struct modem_chat * chat,char ** argv,uint16_t argc,void * user_data)40 static void at_shell_print_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 != 1) {
48 		return;
49 	}
50 
51 	shell_print(at_shell_active_shell, "%s", argv[0]);
52 }
53 
54 MODEM_CHAT_MATCHES_DEFINE(
55 	at_shell_abort_matches,
56 	MODEM_CHAT_MATCH("ERROR", "", at_shell_print_match),
57 );
58 
at_shell_script_callback(struct modem_chat * chat,enum modem_chat_script_result result,void * user_data)59 static void at_shell_script_callback(struct modem_chat *chat,
60 				     enum modem_chat_script_result result,
61 				     void *user_data)
62 {
63 	modem_at_user_pipe_release();
64 }
65 
66 MODEM_CHAT_SCRIPT_DEFINE(
67 	at_shell_script,
68 	at_shell_script_chat,
69 	at_shell_abort_matches,
70 	at_shell_script_callback,
71 	CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S
72 );
73 
at_shell_init_chat(void)74 static void at_shell_init_chat(void)
75 {
76 	const struct modem_chat_config at_shell_chat_config = {
77 		.receive_buf = at_shell_chat_receive_buf,
78 		.receive_buf_size = sizeof(at_shell_chat_receive_buf),
79 		.delimiter = "\r",
80 		.delimiter_size = sizeof("\r") - 1,
81 		.filter = "\n",
82 		.filter_size = sizeof("\n") - 1,
83 		.argv = at_shell_chat_argv_buf,
84 		.argv_size = ARRAY_SIZE(at_shell_chat_argv_buf),
85 	};
86 
87 	modem_chat_init(&at_shell_chat, &at_shell_chat_config);
88 }
89 
at_shell_init_script_chat(void)90 static void at_shell_init_script_chat(void)
91 {
92 	/* Match anything except the expected response without progressing script */
93 	modem_chat_match_init(&at_shell_script_chat_matches[0]);
94 	modem_chat_match_set_match(&at_shell_script_chat_matches[0], "");
95 	modem_chat_match_set_separators(&at_shell_script_chat_matches[0], "");
96 	modem_chat_match_set_callback(&at_shell_script_chat_matches[0], at_shell_print_any_match);
97 	modem_chat_match_set_partial(&at_shell_script_chat_matches[0], true);
98 	modem_chat_match_enable_wildcards(&at_shell_script_chat_matches[0], false);
99 
100 	/* Match the expected response and terminate script */
101 	modem_chat_match_init(&at_shell_script_chat_matches[1]);
102 	modem_chat_match_set_match(&at_shell_script_chat_matches[1], "");
103 	modem_chat_match_set_separators(&at_shell_script_chat_matches[1], "");
104 	modem_chat_match_set_callback(&at_shell_script_chat_matches[1], at_shell_print_match);
105 	modem_chat_match_set_partial(&at_shell_script_chat_matches[1], false);
106 	modem_chat_match_enable_wildcards(&at_shell_script_chat_matches[1], false);
107 
108 	modem_chat_script_chat_init(at_shell_script_chat);
109 	modem_chat_script_chat_set_response_matches(at_shell_script_chat,
110 						    at_shell_script_chat_matches,
111 						    ARRAY_SIZE(at_shell_script_chat_matches));
112 	modem_chat_script_chat_set_timeout(at_shell_script_chat,
113 					   CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S);
114 }
115 
at_shell_init(void)116 static int at_shell_init(void)
117 {
118 	at_shell_init_chat();
119 	at_shell_init_script_chat();
120 	modem_at_user_pipe_init(&at_shell_chat);
121 	return 0;
122 }
123 
124 SYS_INIT(at_shell_init, POST_KERNEL, 99);
125 
at_shell_cmd_handler(const struct shell * sh,size_t argc,char ** argv)126 static int at_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv)
127 {
128 	int ret;
129 
130 	if (argc < 2) {
131 		return -EINVAL;
132 	}
133 
134 	ret = modem_at_user_pipe_claim();
135 	if (ret < 0) {
136 		switch (ret) {
137 		case -EPERM:
138 			shell_error(sh, "modem is not ready");
139 			break;
140 		case -EBUSY:
141 			shell_error(sh, "script is already running");
142 			break;
143 		default:
144 			shell_error(sh, "unknown");
145 		}
146 		return ret;
147 	}
148 
149 	strncpy(at_shell_request_buf, argv[1], sizeof(at_shell_request_buf) - 1);
150 	ret = modem_chat_script_chat_set_request(at_shell_script_chat, at_shell_request_buf);
151 	if (ret < 0) {
152 		return -EINVAL;
153 	}
154 
155 	if (argc == 3) {
156 		strncpy(at_shell_match_buf, argv[2], sizeof(at_shell_match_buf) - 1);
157 	} else {
158 		strncpy(at_shell_match_buf, "OK", sizeof(at_shell_match_buf) - 1);
159 	}
160 
161 	ret = modem_chat_match_set_match(&at_shell_script_chat_matches[1], at_shell_match_buf);
162 	if (ret < 0) {
163 		return -EINVAL;
164 	}
165 
166 	at_shell_active_shell = sh;
167 
168 	ret = modem_chat_run_script_async(&at_shell_chat, &at_shell_script);
169 	if (ret < 0) {
170 		shell_error(sh, "failed to start script");
171 		modem_at_user_pipe_release();
172 	}
173 
174 	return ret;
175 }
176 
177 SHELL_STATIC_SUBCMD_SET_CREATE(modem_sub_cmds,
178 	SHELL_CMD_ARG(at, NULL, "at <command> <response>", at_shell_cmd_handler, 1, 2),
179 	SHELL_SUBCMD_SET_END
180 );
181 
182 SHELL_CMD_REGISTER(modem, &modem_sub_cmds, "Modem commands", NULL);
183