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