1 | /* |
2 | * This file is part of the MicroPython project, http://micropython.org/ |
3 | * |
4 | * The MIT License (MIT) |
5 | * |
6 | * Copyright (c) 2020 Jim Mussared |
7 | * |
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
9 | * of this software and associated documentation files (the "Software"), to deal |
10 | * in the Software without restriction, including without limitation the rights |
11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
12 | * copies of the Software, and to permit persons to whom the Software is |
13 | * furnished to do so, subject to the following conditions: |
14 | * |
15 | * The above copyright notice and this permission notice shall be included in |
16 | * all copies or substantial portions of the Software. |
17 | * |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
24 | * THE SOFTWARE. |
25 | */ |
26 | |
27 | #include "py/runtime.h" |
28 | #include "py/mperrno.h" |
29 | #include "py/mphal.h" |
30 | |
31 | #if MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) |
32 | |
33 | #if !MICROPY_PY_THREAD |
34 | #error Unix HCI UART requires MICROPY_PY_THREAD |
35 | #endif |
36 | |
37 | #include "extmod/modbluetooth.h" |
38 | #include "extmod/mpbthci.h" |
39 | |
40 | #include <pthread.h> |
41 | #include <unistd.h> |
42 | |
43 | #include <termios.h> |
44 | #include <fcntl.h> |
45 | #include <stdlib.h> |
46 | #include <string.h> |
47 | |
48 | #define DEBUG_printf(...) // printf(__VA_ARGS__) |
49 | #define DEBUG_HCI_DUMP (0) |
50 | |
51 | uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; |
52 | |
53 | STATIC int uart_fd = -1; |
54 | |
55 | // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). |
56 | extern bool mp_bluetooth_hci_poll(void); |
57 | |
58 | #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS |
59 | |
60 | // For synchronous mode, we run all BLE stack code inside a scheduled task. |
61 | // This task is scheduled periodically (every 1ms) by a background thread. |
62 | |
63 | // Allows the stack to tell us that we should stop trying to schedule. |
64 | extern bool mp_bluetooth_hci_active(void); |
65 | |
66 | // Prevent double-enqueuing of the scheduled task. |
67 | STATIC volatile bool events_task_is_scheduled = false; |
68 | |
69 | STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { |
70 | (void)none_in; |
71 | MICROPY_PY_BLUETOOTH_ENTER |
72 | events_task_is_scheduled = false; |
73 | MICROPY_PY_BLUETOOTH_EXIT |
74 | mp_bluetooth_hci_poll(); |
75 | return mp_const_none; |
76 | } |
77 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task); |
78 | |
79 | #endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS |
80 | |
81 | STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; |
82 | STATIC pthread_t hci_poll_thread_id; |
83 | |
84 | STATIC void *hci_poll_thread(void *arg) { |
85 | (void)arg; |
86 | |
87 | DEBUG_printf("hci_poll_thread: starting\n" ); |
88 | |
89 | #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS |
90 | |
91 | events_task_is_scheduled = false; |
92 | |
93 | while (mp_bluetooth_hci_active()) { |
94 | MICROPY_PY_BLUETOOTH_ENTER |
95 | if (!events_task_is_scheduled) { |
96 | events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none); |
97 | } |
98 | MICROPY_PY_BLUETOOTH_EXIT |
99 | usleep(UART_POLL_INTERVAL_US); |
100 | } |
101 | |
102 | #else |
103 | |
104 | // In asynchronous (i.e. ringbuffer) mode, we run the BLE stack directly from the thread. |
105 | // This will return false when the stack is shutdown. |
106 | while (mp_bluetooth_hci_poll()) { |
107 | usleep(UART_POLL_INTERVAL_US); |
108 | } |
109 | |
110 | #endif |
111 | |
112 | DEBUG_printf("hci_poll_thread: stopped\n" ); |
113 | |
114 | return NULL; |
115 | } |
116 | |
117 | STATIC int configure_uart(void) { |
118 | struct termios toptions; |
119 | |
120 | // Get existing config. |
121 | if (tcgetattr(uart_fd, &toptions) < 0) { |
122 | DEBUG_printf("Couldn't get term attributes" ); |
123 | return -1; |
124 | } |
125 | |
126 | // Raw mode (disable all processing). |
127 | cfmakeraw(&toptions); |
128 | |
129 | // 8N1, no parity. |
130 | toptions.c_cflag &= ~CSTOPB; |
131 | toptions.c_cflag |= CS8; |
132 | toptions.c_cflag &= ~PARENB; |
133 | |
134 | // Enable receiver, ignore modem control lines |
135 | toptions.c_cflag |= CREAD | CLOCAL; |
136 | |
137 | // Blocking, single-byte reads. |
138 | toptions.c_cc[VMIN] = 1; |
139 | toptions.c_cc[VTIME] = 0; |
140 | |
141 | // Enable HW RTS/CTS flow control. |
142 | toptions.c_iflag &= ~(IXON | IXOFF | IXANY); |
143 | toptions.c_cflag |= CRTSCTS; |
144 | |
145 | // 1Mbit (TODO: make this configurable). |
146 | speed_t brate = B1000000; |
147 | cfsetospeed(&toptions, brate); |
148 | cfsetispeed(&toptions, brate); |
149 | |
150 | // Apply immediately. |
151 | if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) { |
152 | DEBUG_printf("Couldn't set term attributes" ); |
153 | |
154 | close(uart_fd); |
155 | uart_fd = -1; |
156 | |
157 | return -1; |
158 | } |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | // HCI UART bindings. |
164 | int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { |
165 | (void)port; |
166 | (void)baudrate; |
167 | |
168 | DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n" ); |
169 | |
170 | if (uart_fd != -1) { |
171 | DEBUG_printf("mp_bluetooth_hci_uart_init: already active\n" ); |
172 | return 0; |
173 | } |
174 | |
175 | char uart_device_name[256] = "/dev/ttyUSB0" ; |
176 | |
177 | char *path = getenv("MICROPYBTUART" ); |
178 | if (path != NULL) { |
179 | strcpy(uart_device_name, path); |
180 | } |
181 | DEBUG_printf("mp_bluetooth_hci_uart_init: Using HCI UART: %s\n" , uart_device_name); |
182 | |
183 | int flags = O_RDWR | O_NOCTTY | O_NONBLOCK; |
184 | uart_fd = open(uart_device_name, flags); |
185 | if (uart_fd == -1) { |
186 | printf("mp_bluetooth_hci_uart_init: Unable to open port %s\n" , uart_device_name); |
187 | return -1; |
188 | } |
189 | |
190 | if (configure_uart()) { |
191 | return -1; |
192 | } |
193 | |
194 | // Create a thread to run the polling loop. |
195 | pthread_attr_t attr; |
196 | pthread_attr_init(&attr); |
197 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
198 | pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | int mp_bluetooth_hci_uart_deinit(void) { |
204 | DEBUG_printf("mp_bluetooth_hci_uart_deinit\n" ); |
205 | |
206 | if (uart_fd == -1) { |
207 | return 0; |
208 | } |
209 | |
210 | // Wait for the poll loop to terminate when the state is set to OFF. |
211 | pthread_join(hci_poll_thread_id, NULL); |
212 | |
213 | // Close the UART. |
214 | close(uart_fd); |
215 | uart_fd = -1; |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { |
221 | (void)baudrate; |
222 | DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate\n" ); |
223 | return 0; |
224 | } |
225 | |
226 | int mp_bluetooth_hci_uart_readchar(void) { |
227 | // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n"); |
228 | |
229 | if (uart_fd == -1) { |
230 | return -1; |
231 | } |
232 | |
233 | uint8_t c; |
234 | ssize_t bytes_read = read(uart_fd, &c, 1); |
235 | |
236 | if (bytes_read == 1) { |
237 | #if DEBUG_HCI_DUMP |
238 | printf("[% 8ld] RX: %02x\n" , mp_hal_ticks_ms(), c); |
239 | #endif |
240 | return c; |
241 | } else { |
242 | return -1; |
243 | } |
244 | } |
245 | |
246 | int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { |
247 | // DEBUG_printf("mp_bluetooth_hci_uart_write\n"); |
248 | |
249 | if (uart_fd == -1) { |
250 | return 0; |
251 | } |
252 | |
253 | #if DEBUG_HCI_DUMP |
254 | printf("[% 8ld] TX: %02x" , mp_hal_ticks_ms(), buf[0]); |
255 | for (size_t i = 1; i < len; ++i) { |
256 | printf(":%02x" , buf[i]); |
257 | } |
258 | printf("\n" ); |
259 | #endif |
260 | |
261 | return write(uart_fd, buf, len); |
262 | } |
263 | |
264 | // No-op implementations of HCI controller interface. |
265 | int mp_bluetooth_hci_controller_init(void) { |
266 | return 0; |
267 | } |
268 | |
269 | int mp_bluetooth_hci_controller_deinit(void) { |
270 | return 0; |
271 | } |
272 | |
273 | int mp_bluetooth_hci_controller_sleep_maybe(void) { |
274 | return 0; |
275 | } |
276 | |
277 | bool mp_bluetooth_hci_controller_woken(void) { |
278 | return true; |
279 | } |
280 | |
281 | int mp_bluetooth_hci_controller_wakeup(void) { |
282 | return 0; |
283 | } |
284 | |
285 | #endif // MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) |
286 | |