1 /*
2 * Copyright (c) 2025 Netfeasa Ltd.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /**
8 * @file
9 * @brief AT Command Client - Bidirectional UART bridge
10 *
11 * This application creates a transparent bridge between the console UART
12 * and a cellular modem UART, allowing AT command interaction and firmware
13 * updates via XMODEM protocol.
14 */
15
16 #include <zephyr/kernel.h>
17 #include <zephyr/device.h>
18 #include <zephyr/drivers/uart_pipe.h>
19 #include <zephyr/modem/backend/uart.h>
20 #include <zephyr/modem/pipe.h>
21 #include <zephyr/logging/log.h>
22
23 LOG_MODULE_REGISTER(at_client, CONFIG_MODEM_MODULES_LOG_LEVEL);
24
25 #define DEV_CONSOLE DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
26 #define DEV_MODEM DEVICE_DT_GET(DT_CHOSEN(zephyr_modem_uart))
27
28 #define UART_RX_BUF_SIZE 128
29 #define UART_TX_BUF_SIZE 128
30 #define CONSOLE_RX_BUF_SIZE 128
31
32 /* Buffers for modem backend */
33 struct modem_data {
34 struct {
35 uint8_t uart_rx[UART_RX_BUF_SIZE];
36 uint8_t uart_tx[UART_TX_BUF_SIZE];
37 } buffers;
38
39 struct modem_backend_uart uart_backend;
40 struct modem_pipe *uart_pipe;
41 };
42
43 static struct modem_data data;
44
45 /* Console RX buffer for uart_pipe */
46 static uint8_t buf_rx_console[CONSOLE_RX_BUF_SIZE];
47
48 /* Callback when modem pipe receives data */
modem_pipe_event_handler(struct modem_pipe * pipe,enum modem_pipe_event event,void * user_data)49 static void modem_pipe_event_handler(struct modem_pipe *pipe, enum modem_pipe_event event,
50 void *user_data)
51 {
52 uint8_t buf[CONSOLE_RX_BUF_SIZE];
53 int ret;
54
55 switch (event) {
56 case MODEM_PIPE_EVENT_RECEIVE_READY:
57 /* Read from modem pipe and send directly to console */
58 do {
59 ret = modem_pipe_receive(pipe, buf, sizeof(buf));
60 if (ret > 0) {
61 /* Send directly to console via uart_pipe */
62 uart_pipe_send(buf, ret);
63 }
64 } while (ret > 0);
65 break;
66
67 case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
68 /* Can send more data if available */
69 break;
70
71 default:
72 break;
73 }
74 }
75
init_modem_pipe(void)76 static int init_modem_pipe(void)
77 {
78 int ret;
79
80 const struct modem_backend_uart_config uart_backend_config = {
81 .uart = DEV_MODEM,
82 .receive_buf = data.buffers.uart_rx,
83 .receive_buf_size = sizeof(data.buffers.uart_rx),
84 .transmit_buf = data.buffers.uart_tx,
85 .transmit_buf_size = sizeof(data.buffers.uart_tx),
86 };
87
88 data.uart_pipe = modem_backend_uart_init(&data.uart_backend, &uart_backend_config);
89 if (data.uart_pipe == NULL) {
90 LOG_ERR("Failed to initialize modem backend");
91 return -1;
92 }
93
94 modem_pipe_attach(data.uart_pipe, modem_pipe_event_handler, &data);
95
96 ret = modem_pipe_open(data.uart_pipe, K_MSEC(100));
97 if (ret < 0) {
98 LOG_ERR("Failed to open modem pipe");
99 return ret;
100 }
101
102 LOG_INF("Modem pipe initialized and opened");
103 return 0;
104 }
105
106 /* Console uart_pipe callback - receives data from console */
console_recv_cb(uint8_t * buf,size_t * off)107 static uint8_t *console_recv_cb(uint8_t *buf, size_t *off)
108 {
109 int ret;
110
111 /* Data received from console, send directly to modem pipe */
112 if (*off > 0) {
113 ret = modem_pipe_transmit(data.uart_pipe, buf, *off);
114 if (ret < 0) {
115 LOG_ERR("Failed to transmit to modem: %d", ret);
116 }
117 *off = 0;
118 }
119
120 return buf;
121 }
122
main(void)123 int main(void)
124 {
125 int ret;
126
127 LOG_INF("AT Command Client starting...");
128
129 /* Verify console device is ready */
130 if (!device_is_ready(DEV_CONSOLE)) {
131 LOG_ERR("Console device not ready");
132 return -ENODEV;
133 }
134
135 /* Verify modem device is ready */
136 if (!device_is_ready(DEV_MODEM)) {
137 LOG_ERR("Modem device not ready");
138 return -ENODEV;
139 }
140
141 /* Register console uart_pipe for receiving */
142 uart_pipe_register(buf_rx_console, sizeof(buf_rx_console), console_recv_cb);
143 LOG_INF("Console UART pipe registered");
144
145 /* Initialize modem pipe */
146 ret = init_modem_pipe();
147 if (ret < 0) {
148 LOG_ERR("Failed to initialize modem pipe: %d", ret);
149 return ret;
150 }
151
152 LOG_INF("Console <-> Modem communication established");
153 LOG_INF("Ready to forward AT commands");
154
155 /* Keep application running */
156 while (1) {
157 k_sleep(K_FOREVER);
158 }
159
160 return 0;
161 }
162