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