Skip to content

Commit 1dab746

Browse files
bjarki-andreasenMaureenHelm
authored andcommitted
drivers: modem: add at shell
Add AT shell which uses the newly exposed pipes to send/receive at commands from the modem identified by the chosen node zephyr,at-shell. To send an AT command, the shell command at is used, followed by the command to send, and an optional response which overwrites the default "OK" For example, sending "AT", which returns "OK" modem at at <- command OK <- response Enabling echo, then sending "AT" which will now return "AT" + "OK" modem at ati1 <- command OK <- response at at <- command at <- response OK <- response Signed-off-by: Bjarki Arge Andreasen <bjarki@arge-andreasen.me>
1 parent e878024 commit 1dab746

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed

drivers/modem/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ if (CONFIG_MODEM_SIM7080)
3636
endif()
3737

3838
zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c)
39+
zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c)

drivers/modem/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ source "drivers/modem/Kconfig.ublox-sara-r4"
190190
source "drivers/modem/Kconfig.quectel-bg9x"
191191
source "drivers/modem/Kconfig.wncm14a2a"
192192
source "drivers/modem/Kconfig.cellular"
193+
source "drivers/modem/Kconfig.at_shell"
193194

194195
source "drivers/modem/Kconfig.hl7800"
195196
source "drivers/modem/Kconfig.simcom-sim7080"

drivers/modem/Kconfig.at_shell

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2024 Trackunit Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config MODEM_AT_SHELL
5+
bool "AT command shell based on modem modules"
6+
select MODEM_MODULES
7+
select MODEM_CHAT
8+
select MODEM_PIPE
9+
select MODEM_PIPELINK
10+
depends on !MODEM_SHELL
11+
depends on !SHELL_WILDCARD
12+
depends on $(dt_alias_enabled,modem)
13+
14+
if MODEM_AT_SHELL
15+
16+
config MODEM_AT_SHELL_USER_PIPE
17+
int "User pipe number to use"
18+
default 0
19+
20+
config MODEM_AT_SHELL_RESPONSE_TIMEOUT_S
21+
int "Timeout waiting for response to AT command in seconds"
22+
default 5
23+
24+
config MODEM_AT_SHELL_COMMAND_MAX_SIZE
25+
int "Maximum size of AT command"
26+
default 32
27+
28+
config MODEM_AT_SHELL_RESPONSE_MAX_SIZE
29+
int "Maximum size of AT response"
30+
default 64
31+
32+
config MODEM_AT_SHELL_CHAT_RECEIVE_BUF_SIZE
33+
int "Size of modem chat receive buffer in bytes"
34+
default 128
35+
36+
endif # MODEM_AT_SHELL

drivers/modem/modem_at_shell.c

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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+
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+
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+
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+
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+
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+
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+
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+
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+
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+
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+
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+
207+
static void at_shell_init_pipelink(void)
208+
{
209+
modem_pipelink_attach(at_shell_pipelink, at_shell_pipelink_callback, NULL);
210+
}
211+
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+
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);

0 commit comments

Comments
 (0)