1 /*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2017 Glenn Ruben Bakke
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 #if BLUETOOTH_SD
28
29 #include <string.h>
30 #include "ble_uart.h"
31 #include "ringbuffer.h"
32 #include "mphalport.h"
33 #include "shared/runtime/interrupt_char.h"
34 #include "py/runtime.h"
35
36 #if MICROPY_PY_SYS_STDFILES
37 #include "py/stream.h"
38 #endif
39
40 #if MICROPY_PY_BLE_NUS
41
42 static ubluepy_uuid_obj_t uuid_obj_service = {
43 .base.type = &ubluepy_uuid_type,
44 .type = UBLUEPY_UUID_128_BIT,
45 .value = {0x01, 0x00}
46 };
47
48 static ubluepy_uuid_obj_t uuid_obj_char_tx = {
49 .base.type = &ubluepy_uuid_type,
50 .type = UBLUEPY_UUID_128_BIT,
51 .value = {0x03, 0x00}
52 };
53
54 static ubluepy_uuid_obj_t uuid_obj_char_rx = {
55 .base.type = &ubluepy_uuid_type,
56 .type = UBLUEPY_UUID_128_BIT,
57 .value = {0x02, 0x00}
58 };
59
60 static ubluepy_service_obj_t ble_uart_service = {
61 .base.type = &ubluepy_service_type,
62 .p_uuid = &uuid_obj_service,
63 .type = UBLUEPY_SERVICE_PRIMARY
64 };
65
66 static ubluepy_characteristic_obj_t ble_uart_char_rx = {
67 .base.type = &ubluepy_characteristic_type,
68 .p_uuid = &uuid_obj_char_rx,
69 .props = UBLUEPY_PROP_WRITE | UBLUEPY_PROP_WRITE_WO_RESP,
70 .attrs = 0,
71 };
72
73 static ubluepy_characteristic_obj_t ble_uart_char_tx = {
74 .base.type = &ubluepy_characteristic_type,
75 .p_uuid = &uuid_obj_char_tx,
76 .props = UBLUEPY_PROP_NOTIFY,
77 .attrs = UBLUEPY_ATTR_CCCD,
78 };
79
80 static ubluepy_peripheral_obj_t ble_uart_peripheral = {
81 .base.type = &ubluepy_peripheral_type,
82 .conn_handle = 0xFFFF,
83 };
84
85 static volatile bool m_cccd_enabled;
86 static volatile bool m_connected;
87
88 ringBuffer_typedef(uint8_t, ringbuffer_t);
89
90 static ringbuffer_t m_rx_ring_buffer;
91 static ringbuffer_t * mp_rx_ring_buffer = &m_rx_ring_buffer;
92 static uint8_t m_rx_ring_buffer_data[128];
93
94 static ubluepy_advertise_data_t m_adv_data_uart_service;
95
96 #if BLUETOOTH_WEBBLUETOOTH_REPL
97 static ubluepy_advertise_data_t m_adv_data_eddystone_url;
98 #endif // BLUETOOTH_WEBBLUETOOTH_REPL
99
mp_hal_stdin_rx_chr(void)100 int mp_hal_stdin_rx_chr(void) {
101 while (!ble_uart_enabled()) {
102 // wait for connection
103 }
104 while (isBufferEmpty(mp_rx_ring_buffer)) {
105 ;
106 }
107
108 uint8_t byte;
109 bufferRead(mp_rx_ring_buffer, byte);
110 return (int)byte;
111 }
112
mp_hal_stdout_tx_strn(const char * str,size_t len)113 void mp_hal_stdout_tx_strn(const char *str, size_t len) {
114 // Not connected: drop output
115 if (!ble_uart_enabled()) return;
116
117 uint8_t *buf = (uint8_t *)str;
118 size_t send_len;
119
120 while (len > 0) {
121 if (len >= 20) {
122 send_len = 20; // (GATT_MTU_SIZE_DEFAULT - 3)
123 } else {
124 send_len = len;
125 }
126
127 ubluepy_characteristic_obj_t * p_char = &ble_uart_char_tx;
128
129 ble_drv_attr_s_notify(p_char->p_service->p_periph->conn_handle,
130 p_char->handle,
131 send_len,
132 buf);
133
134 len -= send_len;
135 buf += send_len;
136 }
137 }
138
ble_uart_tx_char(char c)139 void ble_uart_tx_char(char c) {
140 // Not connected: drop output
141 if (!ble_uart_enabled()) return;
142
143 ubluepy_characteristic_obj_t * p_char = &ble_uart_char_tx;
144
145 ble_drv_attr_s_notify(p_char->p_service->p_periph->conn_handle,
146 p_char->handle,
147 1,
148 (uint8_t *)&c);
149 }
150
mp_hal_stdout_tx_strn_cooked(const char * str,mp_uint_t len)151 void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len) {
152 for (const char *top = str + len; str < top; str++) {
153 if (*str == '\n') {
154 ble_uart_tx_char('\r');
155 }
156 ble_uart_tx_char(*str);
157 }
158 }
159
160 #if MICROPY_PY_SYS_STDFILES
mp_hal_stdio_poll(uintptr_t poll_flags)161 uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
162 uintptr_t ret = 0;
163 if ((poll_flags & MP_STREAM_POLL_RD) && ble_uart_enabled()
164 && !isBufferEmpty(mp_rx_ring_buffer)) {
165 ret |= MP_STREAM_POLL_RD;
166 }
167 return ret;
168 }
169 #endif
170
gap_event_handler(mp_obj_t self_in,uint16_t event_id,uint16_t conn_handle,uint16_t length,uint8_t * data)171 STATIC void gap_event_handler(mp_obj_t self_in, uint16_t event_id, uint16_t conn_handle, uint16_t length, uint8_t * data) {
172 ubluepy_peripheral_obj_t * self = MP_OBJ_TO_PTR(self_in);
173
174 if (event_id == 16) { // connect event
175 self->conn_handle = conn_handle;
176 m_connected = true;
177 } else if (event_id == 17) { // disconnect event
178 self->conn_handle = 0xFFFF; // invalid connection handle
179 m_connected = false;
180 m_cccd_enabled = false;
181 ble_uart_advertise();
182 }
183 }
184
gatts_event_handler(mp_obj_t self_in,uint16_t event_id,uint16_t attr_handle,uint16_t length,uint8_t * data)185 STATIC void gatts_event_handler(mp_obj_t self_in, uint16_t event_id, uint16_t attr_handle, uint16_t length, uint8_t * data) {
186 ubluepy_peripheral_obj_t * self = MP_OBJ_TO_PTR(self_in);
187 (void)self;
188
189 if (event_id == 80) { // gatts write
190 if (ble_uart_char_tx.cccd_handle == attr_handle) {
191 m_cccd_enabled = true;
192 } else if (ble_uart_char_rx.handle == attr_handle) {
193 for (uint16_t i = 0; i < length; i++) {
194 #if MICROPY_KBD_EXCEPTION
195 if (data[i] == mp_interrupt_char) {
196 mp_sched_keyboard_interrupt();
197 m_rx_ring_buffer.start = 0;
198 m_rx_ring_buffer.end = 0;
199 } else
200 #endif
201 {
202 bufferWrite(mp_rx_ring_buffer, data[i]);
203 }
204 }
205 }
206 }
207 }
208
ble_uart_init0(void)209 void ble_uart_init0(void) {
210 uint8_t base_uuid[] = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E};
211 uint8_t uuid_vs_idx;
212
213 (void)ble_drv_uuid_add_vs(base_uuid, &uuid_vs_idx);
214
215 uuid_obj_service.uuid_vs_idx = uuid_vs_idx;
216 uuid_obj_char_tx.uuid_vs_idx = uuid_vs_idx;
217 uuid_obj_char_rx.uuid_vs_idx = uuid_vs_idx;
218
219 (void)ble_drv_service_add(&ble_uart_service);
220 ble_uart_service.char_list = mp_obj_new_list(0, NULL);
221
222 // add TX characteristic
223 ble_uart_char_tx.service_handle = ble_uart_service.handle;
224 bool retval = ble_drv_characteristic_add(&ble_uart_char_tx);
225 if (retval) {
226 ble_uart_char_tx.p_service = &ble_uart_service;
227 }
228 mp_obj_list_append(ble_uart_service.char_list, MP_OBJ_FROM_PTR(&ble_uart_char_tx));
229
230 // add RX characteristic
231 ble_uart_char_rx.service_handle = ble_uart_service.handle;
232 retval = ble_drv_characteristic_add(&ble_uart_char_rx);
233 if (retval) {
234 ble_uart_char_rx.p_service = &ble_uart_service;
235 }
236 mp_obj_list_append(ble_uart_service.char_list, MP_OBJ_FROM_PTR(&ble_uart_char_rx));
237
238 // setup the peripheral
239 ble_uart_peripheral.service_list = mp_obj_new_list(0, NULL);
240 mp_obj_list_append(ble_uart_peripheral.service_list, MP_OBJ_FROM_PTR(&ble_uart_service));
241 ble_uart_service.p_periph = &ble_uart_peripheral;
242
243 ble_drv_gap_event_handler_set(MP_OBJ_FROM_PTR(&ble_uart_peripheral), gap_event_handler);
244 ble_drv_gatts_event_handler_set(MP_OBJ_FROM_PTR(&ble_uart_peripheral), gatts_event_handler);
245
246 ble_uart_peripheral.conn_handle = 0xFFFF;
247
248 static char device_name[] = "mpus";
249
250 mp_obj_t service_list = mp_obj_new_list(0, NULL);
251 mp_obj_list_append(service_list, MP_OBJ_FROM_PTR(&ble_uart_service));
252
253 mp_obj_t * services = NULL;
254 mp_uint_t num_services;
255 mp_obj_get_array(service_list, &num_services, &services);
256
257 m_adv_data_uart_service.p_services = services;
258 m_adv_data_uart_service.num_of_services = num_services;
259 m_adv_data_uart_service.p_device_name = (uint8_t *)device_name;
260 m_adv_data_uart_service.device_name_len = strlen(device_name);
261 m_adv_data_uart_service.connectable = true;
262 m_adv_data_uart_service.p_data = NULL;
263
264 #if BLUETOOTH_WEBBLUETOOTH_REPL
265 // for now point eddystone URL to https://goo.gl/F7fZ69 => https://aykevl.nl/apps/nus/
266 static uint8_t eddystone_url_data[27] = {0x2, 0x1, 0x6,
267 0x3, 0x3, 0xaa, 0xfe,
268 19, 0x16, 0xaa, 0xfe, 0x10, 0xee, 0x3, 'g', 'o', 'o', '.', 'g', 'l', '/', 'F', '7', 'f', 'Z', '6', '9'};
269 // eddystone url adv data
270 m_adv_data_eddystone_url.p_data = eddystone_url_data;
271 m_adv_data_eddystone_url.data_len = sizeof(eddystone_url_data);
272 m_adv_data_eddystone_url.connectable = false;
273 #endif
274
275 m_cccd_enabled = false;
276
277 // initialize ring buffer
278 m_rx_ring_buffer.size = sizeof(m_rx_ring_buffer_data) + 1;
279 m_rx_ring_buffer.start = 0;
280 m_rx_ring_buffer.end = 0;
281 m_rx_ring_buffer.elems = m_rx_ring_buffer_data;
282
283 m_connected = false;
284
285 ble_uart_advertise();
286 }
287
ble_uart_advertise(void)288 void ble_uart_advertise(void) {
289 #if BLUETOOTH_WEBBLUETOOTH_REPL
290 while (!m_connected) {
291 (void)ble_drv_advertise_data(&m_adv_data_uart_service);
292 mp_hal_delay_ms(500);
293 (void)ble_drv_advertise_data(&m_adv_data_eddystone_url);
294 mp_hal_delay_ms(500);
295 }
296
297 ble_drv_advertise_stop();
298 #else
299 (void)ble_drv_advertise_data(&m_adv_data_uart_service);
300 #endif // BLUETOOTH_WEBBLUETOOTH_REPL
301 }
302
ble_uart_connected(void)303 bool ble_uart_connected(void) {
304 return (m_connected);
305 }
306
ble_uart_enabled(void)307 bool ble_uart_enabled(void) {
308 return (m_cccd_enabled);
309 }
310
311 #endif // MICROPY_PY_BLE_NUS
312
313 #endif // BLUETOOTH_SD
314