├── LICENSE ├── README.md ├── debug ├── README.md ├── firmware.bin └── src │ ├── bt_spp_client.c │ └── bt_spp_server.c └── prod ├── README.md ├── firmware.bin └── src ├── bt_spp_client.c └── bt_spp_server.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sharil Tumin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLUETOOTH CLASSIC 2 | 3 | For some time now, vanilla MicroPython firmware for the ESP32 family of boards has provided us with Bluetooth Low Energy (BLE). I have yet to come across MicroPython firmware that supports Bluetooth Classic. 4 | 5 | I require RFCOMM and SPP Bluetooth support for my Bluetooth remote control robot car. This resulted in the development of MicroPython firmware for the ESP32. 6 | 7 | I am aware of several external Bluetooth modules that connect to the microcontroller UART, such as the JDY-31 and HC-05. I have several of these modules, and they all work fine. 8 | 9 | However, Bluetooth Classic support for ESP32 in MicroPython would be useful. 10 | Apart from reducing the hardware count by one, we can have full Bluetooth control in our MicroPython programs. We can avoid configuring a Bluetooth device using AT commands and save two GPIO pins. 11 | 12 | Bluetooth Classic can only be provided by the ESP32. Other variants, such as the ESP32-C3, ESP32-S2, and ESP32-S3, only support Bluetooth Low Energy. 13 | 14 | This custom firmware allows an ESP32 board to function as either a Bluetooth Slave or a Bluetooth Master device. A slave acts as a server, waiting for connections, whereas a master acts as a client, initiating connections. 15 | 16 | The firmware only allows one connection at a time, whether as a slave or master device. This simplifies the implementation and provides us with a more user-friendly interface. Because Bluetooth Classic requires a lot of resources, the firmware does not support WIFI. Adding WIFI, on the other hand, will result in a build error. 17 | 18 | | Master | Slave | Function | 19 | |:-------------------|:-------------------------|:----------------------------------------| 20 | | import btm | import bts | **btm** for Master. **bts** for Slave device. | 21 | | btm.init("MTR-1") | | Set up a master device. Set device name | 22 | | | | as "MTR-1". | 23 | | | bts.init("SLV-1", "2761")| Set up a slave device. Set device name | 24 | | | | as "SLV-1" and pairing PIN as "2761". | 25 | | btm.up() | bts.up() | Initialization is successful if True. | 26 | | | | False if Bluetooth is not ready. | 27 | | btm.open("SLV-1", "2761") | | Master connecting to salve, "SLV-1" using | 28 | | | | "2761" and pairing PIN. | 29 | | btm.ready() | bts.ready() | Device is ready to send data across a | 30 | | | | connection if True. | 31 | | btm.send_str("Hei")| bts.send_str("Hei") | Send a string message to the recipient. | 32 | | | | The maximum character count is 990. | 33 | | btm.send_bin(b'ok')| bts.send_bin(b'ok') | Send a bytearray data to the recipient. | 34 | | | | The maximum byte count is 990. | 35 | | btm.data() | bts.data() | Return the amount of data in the buffer.| 36 | | | | 0 if no data else n <= 1024. | 37 | | w=btm.get_str(100) | w=bts.get_str(100) | Read at most 100 bytes of data as string| 38 | | | | from the buffer. 39 | | | | The parameter n is 0 < n < 1024 | 40 | | | | btx.get_str(btx.data()) will read all | 41 | | | | from the buffer. 42 | | w=btm.get_bin(103) | w=bts.get_bin(103) | Read at most 103 bytes of data as bytes | 43 | | | | from the buffer. 44 | | | | The same as for string read. If btx.data()| 45 | | | | is 200 and n is 50 then 50 bytes is read. | 46 | | | | Next btx.data() will give 150. 47 | | btm.close() | bts.close() | Close the current connection. Either master|| | | or slave can initiate close. btx.ready() 48 | | | | will return False after close. 49 | | btm.deinit() | bts.deinit() | Take down and disable Bluetooth. 50 | | | | Not necessary under normal running, might | 51 | | | | be useful before deep-sleep. | 52 | 53 | 54 | The firmware disables Secure Simple Pairing (SSP). To connect, the master must enter a valid 4-digit PIN. When a slave is connected to a master, it stops listening for 'discover' packets. When the connection is terminated, the slave will reconfigure itself to listen for any 'discover' packets. A new connection with the slave can be established with a valid PIN provided by the master. 55 | 56 | We can test the functionality of the Bluetooth Serial Port Profile (SPP) on two ESP32 boards. 57 | One will serve as a slave device, while the other will serve as the master. To use it as a slave, start the first board and 'import bts'. Start the second board and then 'import btm'. First, initialize the slave. After that, initialize the master and connect to the slave with 'bt,.open()'. When the devices are ready, try sending and receiving messages. 58 | 59 | We can test an SPP slave on an ESP32 board and a master from a smart phone. Boot the ESP32 and initialize it as a slave. Enable Bluetooth on the phone and try to pair it with the "SLV-1" if that is the name given to the ESP32. Start an SPP application on the smart phone and try to send and receive messages. 60 | 61 | We can also try ESP32 as a master and connect it to a JDY-31 or HC-05. Due to some problems in the in the Bluetooth stack library, we will get a lot of warning messages. We cannot do anything about it. Since these warnings come from a binary blob of the Bluetooth stack library, there is no way to disable them. This will clutter our REPL with warnings and make it useless for interactive testing. 62 | 63 | The input data buffer is implemented as a ring buffer. If the data is received too fast and the buffer is full, incoming data is simply ignored. There is no provision for traffic congestion control. 64 | 65 | The ring buffer used by the Bluetooth module is protected by a lock. Since Bluetooth Classic is implemented as an event-driven system using callback, this lock is necessary. If the 'data-in' event callback tries to acquire the lock but fails, the data will be lost. 66 | 67 | Naturally, this firmware was not built with network and socket. The uasyncio was not included as a frozen modules. For preemptive multitasking we can use _thread module. For cooperative multitasking we can use worker module ( see - https://github.com/shariltumin/workers-framework-micropython). 68 | 69 | As mentioned earlier, this firmware is the result of a need for a Bluetooth Classic slave device on an ESP32 board for a robot car that can be remotely controlled by a smartphone. As such, the firmware serves its purpose. 70 | 71 | I provide two versions of custom MicroPython. The firmware under **prod** is for normal use. The one under **debug** is a firmware compiled with verbose logging enabled. The debug version is for those who want to know more about how the event-driven Bluetooth driver works. The debug firmware version is 1513216 bytes long, while the prod version is 1333696 bytes long. 72 | 73 | I hope some of you will find it useful. Good luck. 74 | 75 | At the monthly [Melbourne meeting](https://www.youtube.com/watch?v=nThCxRihyes), I received an honorary mention from the core MicroPython developers. Thanks! 76 | 77 | -------------------------------------------------------------------------------- /debug/README.md: -------------------------------------------------------------------------------- 1 | 2 | This is a debug version of the firmware. Verbose logging is enabled. This increases the size of the firmware. The sdkconfig.board is shown below: 3 | 4 | ``` 5 | # DEBUGGING 6 | CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y 7 | 8 | # Override some defaults so BT stack is enabled 9 | CONFIG_BT_ENABLED=y 10 | # CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY= 11 | CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY=y 12 | # CONFIG_BTDM_CONTROLLER_MODE_BTDM= 13 | CONFIG_CLASSIC_BT_ENABLED=y 14 | CONFIG_BT_SPP_ENABLED=y 15 | # disable Secure Simple Pairing 16 | CONFIG_BT_SSP_ENABLED= 17 | # Support ONE connection at a time 18 | # don't work --> CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=1 19 | # need to change esp_bt_gap_set_scan_mode to 20 | # ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE 21 | # and WiFi is disabled 22 | CONFIG_WIFI_ENABLED=n 23 | ``` 24 | 25 | Logging is very verbose. Every event is reported to the console. To disable disable logging in REPL, do this 26 | 27 | ``` 28 | import esp 29 | esp.osdebug(None) 30 | 31 | ``` 32 | 33 | For production use, please use the prod version of the firmware as it is smaller in size. 34 | 35 | This is a "hobby" grade software. It is not guaranteed to work or even be useful in any way. The use of the firmware is entirely at the user's own risk. 36 | 37 | -------------------------------------------------------------------------------- /debug/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shariltumin/esp32-bluetooth-classic-micropython/0fadb79e312329e038b0153bbe07e06098d7775b/debug/firmware.bin -------------------------------------------------------------------------------- /debug/src/bt_spp_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2023 Sharil Tumin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This is client, master, or central of Bluetooth Classic using SPP 25 | 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "nvs.h" 33 | #include "nvs_flash.h" 34 | #include "freertos/FreeRTOS.h" 35 | #include "freertos/task.h" 36 | #include "freertos/semphr.h" 37 | #include "esp_log.h" 38 | #include "esp_bt.h" 39 | #include "esp_bt_main.h" 40 | #include "esp_gap_bt_api.h" 41 | #include "esp_bt_device.h" 42 | #include "esp_spp_api.h" 43 | 44 | #include "py/obj.h" 45 | #include "py/runtime.h" 46 | 47 | #define TAG "SPP_CLIENT" 48 | 49 | #define NON_BLOCKING 0 50 | #define DEFAULT_PIPE_SIZE 1024 51 | 52 | typedef struct _pipe_obj_t { 53 | char *buffer; 54 | int head; 55 | int tail; 56 | int size; 57 | SemaphoreHandle_t lock; 58 | } pipe_obj_t; 59 | 60 | pipe_obj_t *pipe; /* will get value at btm.init() */ 61 | 62 | static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; 63 | static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; 64 | static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER; 65 | static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY; 66 | static const uint8_t inq_len = 30; 67 | static const uint8_t inq_num_rsps = 0; 68 | 69 | static int evn_cnt = 0; 70 | 71 | // use to get slave after discovery 72 | static char slave_device_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 73 | static uint8_t slave_device_name_len; 74 | 75 | // use for data in 76 | static uint8_t spp_data[ESP_SPP_MAX_MTU]; /* ESP_SPP_MAX_MTU = 990 bytes */ 77 | // static char msg_in[ESP_SPP_MAX_MTU]; 78 | 79 | typedef struct _master_obj_t { 80 | char name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 81 | char slave_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 82 | uint8_t slave_name_len; 83 | uint8_t slave_pin_code[17]; 84 | esp_bd_addr_t slave_addr; 85 | bool ready; 86 | uint32_t handle; /* current write handle */ 87 | uint32_t c_handle; /* connection handle */ 88 | } master_obj_t; 89 | 90 | master_obj_t *master; /* will get value at btm.init() */ 91 | 92 | static bool master_storage = false; /* master and pipe storage allocation flag */ 93 | 94 | static bool master_up = false; /* master not up, can do init */ 95 | 96 | static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len) 97 | { 98 | uint8_t *rmt_bdname = NULL; 99 | uint8_t rmt_bdname_len = 0; 100 | 101 | if (!eir) { 102 | return false; 103 | } 104 | 105 | rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); 106 | if (!rmt_bdname) { 107 | rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); 108 | } 109 | 110 | if (rmt_bdname) { 111 | if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { 112 | rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; 113 | } 114 | 115 | if (bdname) { 116 | memcpy(bdname, rmt_bdname, rmt_bdname_len); 117 | bdname[rmt_bdname_len] = '\0'; 118 | } 119 | if (bdname_len) { 120 | *bdname_len = rmt_bdname_len; 121 | } 122 | return true; 123 | } 124 | 125 | return false; 126 | } 127 | 128 | static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) 129 | { 130 | switch (event) { 131 | case ESP_SPP_INIT_EVT: 132 | evn_cnt++; 133 | ESP_LOGI(TAG, "%d - ESP_SPP_INIT_EVT SE#%d", evn_cnt, event); 134 | ESP_LOGI(TAG, "Status %d", param->init.status); 135 | break; 136 | case ESP_SPP_DISCOVERY_COMP_EVT: 137 | evn_cnt++; 138 | ESP_LOGI(TAG, "%d - ESP_SPP_DISCOVERY_COMP_EVT SE#%d",evn_cnt, event); 139 | ESP_LOGI(TAG, "Status=%d, Server Channel Number=%d", param->disc_comp.status, param->disc_comp.scn_num); 140 | if (param->disc_comp.status == ESP_SPP_SUCCESS) { 141 | ESP_LOGI(TAG, "Master connecting to slave"); 142 | esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], master->slave_addr); 143 | } 144 | break; 145 | case ESP_SPP_OPEN_EVT: 146 | evn_cnt++; 147 | ESP_LOGI(TAG, "%d - ESP_SPP_OPEN_EVT", evn_cnt); 148 | ESP_LOGI(TAG, "Master writing initial msg to slave"); 149 | master->handle = param->srv_open.handle; 150 | master->c_handle = param->srv_open.handle; 151 | master->ready = true; 152 | break; 153 | case ESP_SPP_CLOSE_EVT: 154 | evn_cnt++; 155 | ESP_LOGI(TAG, "%d - ESP_SPP_CLOSE_EVT", evn_cnt); 156 | master->ready = false; 157 | master->handle = NULL; 158 | master->c_handle = NULL; 159 | break; 160 | case ESP_SPP_START_EVT: 161 | evn_cnt++; 162 | ESP_LOGI(TAG, "%d - ESP_SPP_START_EVT", evn_cnt); 163 | break; 164 | case ESP_SPP_CL_INIT_EVT: 165 | evn_cnt++; 166 | ESP_LOGI(TAG, "%d - ESP_SPP_CL_INIT_EVT", evn_cnt); 167 | break; 168 | case ESP_SPP_DATA_IND_EVT: 169 | evn_cnt++; 170 | ESP_LOGI(TAG, "%d - ESP_SPP_DATA_IND_EVT", evn_cnt); 171 | uint8_t *items = param->data_ind.data; 172 | int count = param->data_ind.len; 173 | // int i, added = 0; 174 | int i = 0; 175 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 176 | for (i = 0; i < count; i++) { 177 | int next_tail = (pipe->tail + 1) % pipe->size; 178 | if (next_tail == pipe->head) { 179 | break; 180 | } 181 | pipe->buffer[pipe->tail] = (char) *items; 182 | pipe->tail = next_tail; 183 | items++; 184 | // added++; 185 | } 186 | xSemaphoreGive(pipe->lock); 187 | } 188 | // memcpy(msg_in, param->data_ind.data, param->data_ind.len); 189 | // msg_in[param->data_ind.len] = '\0'; /* array start at 0 */ 190 | ESP_LOGI(TAG, "#bytes in: %d", count); 191 | master->handle = param->data_ind.handle; 192 | break; 193 | case ESP_SPP_CONG_EVT: 194 | evn_cnt++; 195 | ESP_LOGI(TAG, "%d - ESP_SPP_CONG_EVT", evn_cnt); 196 | ESP_LOGI(TAG, "Traffic congestion cong=%d", param->cong.cong); 197 | master->handle = param->cong.handle; 198 | break; 199 | case ESP_SPP_WRITE_EVT: 200 | evn_cnt++; 201 | ESP_LOGI(TAG, "%d - ESP_SPP_WRITE_EVT", evn_cnt); 202 | ESP_LOGI(TAG, "ESP_SPP_WRITE_EVT len=%d cong=%d", param->write.len , param->write.cong); 203 | // esp_log_buffer_hex("",spp_data,param->write.len); 204 | master->handle = param->write.handle; 205 | break; 206 | case ESP_SPP_SRV_OPEN_EVT: 207 | evn_cnt++; 208 | ESP_LOGI(TAG, "%d - ESP_SPP_SRV_OPEN_EVT", evn_cnt); 209 | break; 210 | default: 211 | evn_cnt++; 212 | ESP_LOGI(TAG, "%d - Unhandled ESP_SPP event: %d", evn_cnt, event); 213 | break; 214 | } 215 | } 216 | 217 | static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) 218 | { 219 | switch(event){ 220 | case ESP_BT_GAP_DISC_RES_EVT: 221 | evn_cnt++; 222 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_DISC_RES_EVT GE#%d", evn_cnt, event); 223 | esp_log_buffer_hex(TAG, param->disc_res.bda, ESP_BD_ADDR_LEN); 224 | for (int i = 0; i < param->disc_res.num_prop; i++){ 225 | if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR 226 | && get_name_from_eir(param->disc_res.prop[i].val, slave_device_name, &slave_device_name_len)){ 227 | esp_log_buffer_char(TAG, slave_device_name, slave_device_name_len); 228 | if (strlen(slave_device_name) == master->slave_name_len 229 | && strncmp(master->slave_name, slave_device_name, master->slave_name_len) == 0) { 230 | memcpy(master->slave_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); 231 | ESP_LOGI(TAG, "Slave found. Master start SPP discovery"); 232 | esp_spp_start_discovery(master->slave_addr); 233 | esp_bt_gap_cancel_discovery(); 234 | } 235 | } 236 | } 237 | break; 238 | case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: 239 | evn_cnt++; 240 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_DISC_STATE_CHANGED_EVT GE#%d", evn_cnt, event); 241 | ESP_LOGI(TAG, "State: %d", param->disc_st_chg.state); 242 | break; 243 | case ESP_BT_GAP_RMT_SRVCS_EVT: 244 | evn_cnt++; 245 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_RMT_SRVCS_EVT GE#%d", evn_cnt, event); 246 | break; 247 | case ESP_BT_GAP_RMT_SRVC_REC_EVT: 248 | evn_cnt++; 249 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_RMT_SRVC_REC_EVT GE#%d", evn_cnt, event); 250 | break; 251 | case ESP_BT_GAP_AUTH_CMPL_EVT:{ 252 | evn_cnt++; 253 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_AUTH_CMPL_EVT GE#%d", evn_cnt, event); 254 | if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { 255 | ESP_LOGI(TAG, "Authentication success: %s", param->auth_cmpl.device_name); 256 | esp_log_buffer_hex(TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); 257 | } else { 258 | ESP_LOGE(TAG, "Authentication failed, status:%d", param->auth_cmpl.stat); 259 | } 260 | break; 261 | } 262 | case ESP_BT_GAP_PIN_REQ_EVT:{ 263 | evn_cnt++; 264 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_PIN_REQ_EVT GE#%d", evn_cnt, event); 265 | ESP_LOGI(TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); 266 | if (param->pin_req.min_16_digit) { 267 | ESP_LOGI(TAG, "Input pin code: 0000 0000 0000 0000"); 268 | esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, master->slave_pin_code); 269 | } else { 270 | ESP_LOGI(TAG, "Input pin code: 1234"); 271 | esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, master->slave_pin_code); 272 | } 273 | break; 274 | } 275 | 276 | default: 277 | evn_cnt++; 278 | ESP_LOGI(TAG, "%d - Unhandled ESP_BT_GAP event: GE#%d", evn_cnt, event); 279 | break; 280 | } 281 | } 282 | 283 | void btm_start() 284 | { 285 | esp_err_t ret = nvs_flash_init(); 286 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 287 | ESP_ERROR_CHECK(nvs_flash_erase()); 288 | ret = nvs_flash_init(); 289 | } 290 | ESP_ERROR_CHECK( ret ); 291 | 292 | ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); 293 | 294 | esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); 295 | if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { 296 | ESP_LOGE(TAG, "%s initialize Master controller failed: %s\n", __func__, esp_err_to_name(ret)); 297 | return; 298 | } 299 | 300 | if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { 301 | ESP_LOGE(TAG, "%s enable Master controller failed: %s\n", __func__, esp_err_to_name(ret)); 302 | return; 303 | } 304 | 305 | if ((ret = esp_bluedroid_init()) != ESP_OK) { 306 | ESP_LOGE(TAG, "%s initialize Master bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); 307 | return; 308 | } 309 | 310 | if ((ret = esp_bluedroid_enable()) != ESP_OK) { 311 | ESP_LOGE(TAG, "%s enable Master bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); 312 | return; 313 | } 314 | 315 | if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { 316 | ESP_LOGE(TAG, "%s Master gap register failed: %s\n", __func__, esp_err_to_name(ret)); 317 | return; 318 | } 319 | 320 | if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { 321 | ESP_LOGE(TAG, "%s Master spp register failed: %s\n", __func__, esp_err_to_name(ret)); 322 | return; 323 | } 324 | 325 | if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { 326 | ESP_LOGE(TAG, "%s Master spp init failed: %s\n", __func__, esp_err_to_name(ret)); 327 | return; 328 | } 329 | 330 | // set others 331 | esp_bt_dev_set_device_name(master->name); 332 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 333 | 334 | ESP_LOGI(TAG, "My device name: %s", master->name); 335 | return; 336 | } 337 | 338 | STATIC mp_obj_t btm_init(mp_obj_t name){ 339 | if (master_up == true) { 340 | return mp_const_false; 341 | } 342 | char *mn = mp_obj_str_get_str(name); 343 | if (master_storage == false) { 344 | // create master object 345 | master_obj_t *mo = m_new_obj(master_obj_t); 346 | memcpy(mo->name, mn, strlen(mn)); // master name 347 | master = mo; 348 | // create pipe object 349 | int size = DEFAULT_PIPE_SIZE; 350 | pipe_obj_t *po = m_new_obj(pipe_obj_t); 351 | po->buffer = NULL; 352 | po->head = 0; 353 | po->tail = 0; 354 | po->lock = xSemaphoreCreateMutex(); 355 | char *buff = malloc(sizeof(char) * size); 356 | if (buff == NULL) { 357 | po->buffer = NULL; 358 | po->size = 0; 359 | } else { 360 | po->buffer = buff; 361 | po->size = size+1; 362 | } 363 | pipe = po; 364 | master_storage = true; // ready with storage 365 | } else { 366 | memcpy(master->name, mn, strlen(mn)); // master name 367 | master->ready = false; 368 | master->handle = NULL; 369 | master->c_handle = NULL; 370 | pipe->head = 0; 371 | pipe->tail = 0; 372 | } 373 | btm_start(); 374 | master_up = true; // master is up, can deinit 375 | return mp_const_true; 376 | } 377 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_init_obj, btm_init); 378 | 379 | STATIC mp_obj_t btm_data() { 380 | int size = 0; 381 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 382 | if (pipe->tail >= pipe->head) { 383 | size = pipe->tail - pipe->head; 384 | } else { 385 | size = pipe->size - pipe->head + pipe->tail; 386 | } 387 | xSemaphoreGive(pipe->lock); 388 | } 389 | return mp_obj_new_int(size); 390 | } 391 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_data_obj, btm_data); 392 | 393 | STATIC mp_obj_t btm_get_str(const mp_obj_t what) { 394 | const int count = mp_obj_get_int(what); 395 | if (count > 0) { 396 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 397 | char items[count]; 398 | int i, removed = 0; 399 | for (i = 0; i < count; i++) { 400 | if (pipe->head == pipe->tail) { 401 | break; 402 | } 403 | items[i] = (char) pipe->buffer[pipe->head]; 404 | pipe->head = (pipe->head + 1) % pipe->size; 405 | removed++; 406 | } 407 | xSemaphoreGive(pipe->lock); 408 | if (removed > 0) { 409 | mp_obj_t data = mp_obj_new_str(items, removed); 410 | return data; 411 | } 412 | } 413 | } 414 | return mp_const_none; // count<=0 or can't get lock or empty pipe 415 | } 416 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_get_str_obj, btm_get_str); 417 | 418 | STATIC mp_obj_t btm_get_bin(const mp_obj_t what) { 419 | const int count = mp_obj_get_int(what); 420 | if (count > 0) { 421 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 422 | uint8_t items[count]; 423 | int i, removed = 0; 424 | for (i = 0; i < count; i++) { 425 | if (pipe->head == pipe->tail) { 426 | break; 427 | } 428 | items[i] = (uint8_t) pipe->buffer[pipe->head]; 429 | pipe->head = (pipe->head + 1) % pipe->size; 430 | removed++; 431 | } 432 | xSemaphoreGive(pipe->lock); 433 | if (removed > 0) { 434 | mp_obj_t data = mp_obj_new_bytes(items, removed); 435 | return data; 436 | } 437 | } 438 | } 439 | return mp_const_none; // count<=0 or can't get lock or empty pipe 440 | } 441 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_get_bin_obj, btm_get_bin); 442 | 443 | STATIC mp_obj_t btm_send_str(mp_obj_t data) { 444 | if (master->ready == true) { 445 | char *str = mp_obj_str_get_str(data); 446 | memcpy(spp_data, str, strlen(str)); // convert char to uint8_t 447 | esp_spp_write(master->handle, strlen(str), spp_data); 448 | } 449 | return mp_const_none; 450 | } 451 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_send_str_obj, btm_send_str); 452 | 453 | STATIC mp_obj_t btm_send_bin(mp_obj_t data) { 454 | size_t len; 455 | if (master->ready == true) { 456 | char *bin = mp_obj_str_get_data(data, &len); 457 | memcpy(spp_data, bin, len); // convert char to uint8_t 458 | esp_spp_write(master->handle, len, spp_data); 459 | } 460 | return mp_const_none; 461 | } 462 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_send_bin_obj, btm_send_bin); 463 | 464 | STATIC mp_obj_t btm_open(mp_obj_t name, mp_obj_t pin) { 465 | char *sn = mp_obj_str_get_str(name); 466 | char *sp = mp_obj_str_get_str(pin); 467 | memcpy(master->slave_name, sn, strlen(sn)); // slave name 468 | master->slave_name_len = strlen(sn); // slave name length 469 | memcpy(master->slave_pin_code, sp, strlen(sp)); // binding PIN 470 | esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps); 471 | ESP_LOGI(TAG, "Master start discovery"); 472 | ESP_LOGI(TAG, "Mode: %d, Len: %d, #Res: %d", inq_mode, inq_len, inq_num_rsps); 473 | return mp_const_none; 474 | } 475 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(btm_open_obj, btm_open); 476 | 477 | STATIC mp_obj_t btm_close(){ 478 | if (master->ready == true) { 479 | esp_spp_disconnect(master->c_handle); 480 | master->ready = false; 481 | master->handle = NULL; 482 | master->c_handle = NULL; 483 | } 484 | return mp_const_true; 485 | } 486 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_close_obj, btm_close); 487 | 488 | STATIC mp_obj_t btm_ready(){ 489 | return mp_obj_new_bool(master->ready); 490 | } 491 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_ready_obj, btm_ready); 492 | 493 | STATIC mp_obj_t btm_up(){ 494 | return mp_obj_new_bool(master_up); 495 | } 496 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_up_obj, btm_up); 497 | 498 | STATIC mp_obj_t btm_deinit(){ 499 | if (master_up == false) { 500 | return mp_const_false; 501 | } 502 | esp_spp_deinit(); 503 | esp_bluedroid_disable(); 504 | esp_bluedroid_deinit(); 505 | esp_bt_controller_disable(); 506 | esp_bt_controller_deinit(); 507 | master->ready = false; 508 | master->handle = NULL; 509 | master->c_handle = NULL; 510 | master_up = false; // can do init 511 | return mp_const_true; 512 | } 513 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_deinit_obj, btm_deinit); 514 | 515 | STATIC const mp_rom_map_elem_t btm_module_globals_table[] = { 516 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_btm) }, 517 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&btm_init_obj) }, 518 | { MP_ROM_QSTR(MP_QSTR_up), MP_ROM_PTR(&btm_up_obj) }, 519 | { MP_ROM_QSTR(MP_QSTR_data), MP_ROM_PTR(&btm_data_obj) }, 520 | { MP_ROM_QSTR(MP_QSTR_get_str), MP_ROM_PTR(&btm_get_str_obj) }, 521 | { MP_ROM_QSTR(MP_QSTR_get_bin), MP_ROM_PTR(&btm_get_bin_obj) }, 522 | { MP_ROM_QSTR(MP_QSTR_send_str), MP_ROM_PTR(&btm_send_str_obj) }, 523 | { MP_ROM_QSTR(MP_QSTR_send_bin), MP_ROM_PTR(&btm_send_bin_obj) }, 524 | { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&btm_open_obj) }, 525 | { MP_ROM_QSTR(MP_QSTR_ready), MP_ROM_PTR(&btm_ready_obj) }, 526 | { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&btm_close_obj) }, 527 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&btm_deinit_obj) }, 528 | }; 529 | 530 | STATIC MP_DEFINE_CONST_DICT(btm_module_globals, btm_module_globals_table); 531 | 532 | const mp_obj_module_t mp_module_btm = { 533 | .base = { &mp_type_module }, 534 | .globals = (mp_obj_dict_t*)&btm_module_globals, 535 | }; 536 | 537 | MP_REGISTER_MODULE(MP_QSTR_btm, mp_module_btm); 538 | 539 | -------------------------------------------------------------------------------- /debug/src/bt_spp_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2023 Sharil Tumin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This the server, slave, or peripheral of Bluetooth Classic serving SPP 25 | 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "nvs.h" 33 | #include "nvs_flash.h" 34 | #include "freertos/FreeRTOS.h" 35 | #include "freertos/task.h" 36 | #include "freertos/semphr.h" 37 | #include "esp_log.h" 38 | #include "esp_bt.h" 39 | #include "esp_bt_main.h" 40 | #include "esp_gap_bt_api.h" 41 | #include "esp_bt_device.h" 42 | #include "esp_spp_api.h" 43 | 44 | #include "py/obj.h" 45 | #include "py/runtime.h" 46 | 47 | #define TAG "SPP_SERVER" 48 | 49 | #define NON_BLOCKING 0 50 | #define DEFAULT_PIPE_SIZE 1024 51 | 52 | typedef struct _pipe_obj_t { 53 | char *buffer; 54 | int head; 55 | int tail; 56 | int size; 57 | SemaphoreHandle_t lock; 58 | } pipe_obj_t; 59 | 60 | pipe_obj_t *pipe; /* will get value at bts.init() */ 61 | 62 | #define SPP_DATA_LEN ESP_SPP_MAX_MTU 63 | static uint8_t spp_data[SPP_DATA_LEN]; /* ESP_SPP_MAX_MTU = 990 bytes */ 64 | // static char msg_in[SPP_DATA_LEN]; 65 | 66 | static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; 67 | static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; 68 | // static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHORIZE; 69 | static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE; 70 | 71 | static int evn_cnt = 0; 72 | 73 | typedef struct _slave_obj_t { 74 | char name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 75 | uint8_t pin_code[17]; 76 | /* esp_bd_addr_t master_addr; */ 77 | bool ready; 78 | uint32_t handle; /* current write handle */ 79 | } slave_obj_t; 80 | 81 | slave_obj_t *slave; /* will get value at bts.init() */ 82 | 83 | static bool slave_storage = false; /* slave and pipe storage allocation flag */ 84 | 85 | static bool slave_up = false; /* slave not up, can do init */ 86 | 87 | static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) 88 | { 89 | switch (event) { 90 | case ESP_SPP_INIT_EVT: 91 | evn_cnt++; 92 | ESP_LOGI(TAG, "%d - ESP_SPP_INIT_EVT", evn_cnt); 93 | break; 94 | case ESP_SPP_DISCOVERY_COMP_EVT: 95 | evn_cnt++; 96 | ESP_LOGI(TAG, "%d - ESP_SPP_DISCOVERY_COMP_EVT", evn_cnt); 97 | break; 98 | case ESP_SPP_OPEN_EVT: 99 | evn_cnt++; 100 | ESP_LOGI(TAG, "%d - ESP_SPP_OPEN_EVT", evn_cnt); 101 | break; 102 | case ESP_SPP_CLOSE_EVT: 103 | evn_cnt++; 104 | ESP_LOGI(TAG, "%d - ESP_SPP_CLOSE_EVT", evn_cnt); 105 | slave->ready = false; 106 | slave->handle = NULL; 107 | // now waiting for new connection 108 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 109 | break; 110 | case ESP_SPP_START_EVT: 111 | evn_cnt++; 112 | ESP_LOGI(TAG, "%d - ESP_SPP_START_EVT", evn_cnt); 113 | break; 114 | case ESP_SPP_CL_INIT_EVT: 115 | evn_cnt++; 116 | ESP_LOGI(TAG, "%d - ESP_SPP_CL_INIT_EVT", evn_cnt); 117 | break; 118 | case ESP_SPP_DATA_IND_EVT: 119 | evn_cnt++; 120 | ESP_LOGI(TAG, "%d - ESP_SPP_DATA_IND_EVT", evn_cnt); 121 | uint8_t *items = param->data_ind.data; 122 | int count = param->data_ind.len; 123 | // int i, added = 0; 124 | int i = 0; 125 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 126 | for (i = 0; i < count; i++) { 127 | int next_tail = (pipe->tail + 1) % pipe->size; 128 | if (next_tail == pipe->head) { 129 | break; 130 | } 131 | pipe->buffer[pipe->tail] = (char) *items; 132 | pipe->tail = next_tail; 133 | items++; 134 | // added++; 135 | } 136 | xSemaphoreGive(pipe->lock); 137 | } 138 | // memcpy(msg_in, param->data_ind.data, param->data_ind.len); 139 | // msg_in[param->data_ind.len] = '\0'; /* array start at 0 */ 140 | ESP_LOGI(TAG, "#bytes in: %d", count); 141 | slave->handle = param->data_ind.handle; 142 | slave->ready = true; // master MUST send message slave first 143 | break; 144 | case ESP_SPP_CONG_EVT: 145 | evn_cnt++; 146 | ESP_LOGI(TAG, "%d - ESP_SPP_CONG_EVT", evn_cnt); 147 | break; 148 | case ESP_SPP_WRITE_EVT: 149 | evn_cnt++; 150 | ESP_LOGI(TAG, "%d - ESP_SPP_WRITE_EVT", evn_cnt); 151 | break; 152 | case ESP_SPP_SRV_OPEN_EVT: 153 | evn_cnt++; 154 | ESP_LOGI(TAG, "%d - ESP_SPP_SRV_OPEN_EVT", evn_cnt); 155 | // make the slave stop responding to discorery request 156 | esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); 157 | break; 158 | default: 159 | evn_cnt++; 160 | ESP_LOGI(TAG, "%d - Unhandled ESP_SPP event: %d", evn_cnt, event); 161 | 162 | break; 163 | } 164 | } 165 | 166 | void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) 167 | { 168 | switch (event) { 169 | case ESP_BT_GAP_AUTH_CMPL_EVT:{ 170 | evn_cnt++; 171 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_AUTH_CMPL_EVT", evn_cnt); 172 | if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { 173 | ESP_LOGI(TAG, "authentication success: %s", param->auth_cmpl.device_name); 174 | esp_log_buffer_hex(TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); 175 | } else { 176 | ESP_LOGE(TAG, "authentication failed, status:%d", param->auth_cmpl.stat); 177 | } 178 | break; 179 | } 180 | case ESP_BT_GAP_PIN_REQ_EVT:{ 181 | evn_cnt++; 182 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_PIN_REQ_EVT", evn_cnt); 183 | ESP_LOGI(TAG, "THIS WILL NEVER HAPPEN ON SLAVE"); 184 | break; 185 | } 186 | 187 | // These are for CONFIG_BT_SSP_ENABLED 188 | case ESP_BT_GAP_CFM_REQ_EVT: 189 | evn_cnt++; 190 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_CFM_REQ_EVT (SPP ENABLE)", evn_cnt); 191 | ESP_LOGI(TAG, "THIS WILL NEVER HAPPEN. SSP WAS DISABLE"); 192 | break; 193 | case ESP_BT_GAP_KEY_NOTIF_EVT: 194 | evn_cnt++; 195 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_KEY_NOTIF_EVT (SPP ENABLE)", evn_cnt); 196 | ESP_LOGI(TAG, "THIS WILL NEVER HAPPEN. SSP WAS DISABLE"); 197 | break; 198 | case ESP_BT_GAP_KEY_REQ_EVT: 199 | evn_cnt++; 200 | ESP_LOGI(TAG, "%d - ESP_BT_GAP_KEY_REQ_EVT (SPP ENABLE)", evn_cnt); 201 | ESP_LOGI(TAG, "THIS WILL NEVER HAPPEN. SSP WAS DISABLE"); 202 | break; 203 | // These are for CONFIG_BT_SSP_ENABLED 204 | 205 | default: { 206 | evn_cnt++; 207 | ESP_LOGI(TAG, "%d - Unhandled ESP_BT_GAP event: %d", evn_cnt, event); 208 | break; 209 | } 210 | } 211 | return; 212 | } 213 | 214 | void bts_start() 215 | { 216 | esp_err_t ret = nvs_flash_init(); 217 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 218 | ESP_ERROR_CHECK(nvs_flash_erase()); 219 | ret = nvs_flash_init(); 220 | } 221 | ESP_ERROR_CHECK( ret ); 222 | 223 | ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); 224 | 225 | esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); 226 | if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { 227 | ESP_LOGE(TAG, "%s initialize Slave controller failed: %s\n", __func__, esp_err_to_name(ret)); 228 | return; 229 | } 230 | 231 | if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { 232 | ESP_LOGE(TAG, "%s enable Slave controller failed: %s\n", __func__, esp_err_to_name(ret)); 233 | return; 234 | } 235 | 236 | if ((ret = esp_bluedroid_init()) != ESP_OK) { 237 | ESP_LOGE(TAG, "%s initialize Slave bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); 238 | return; 239 | } 240 | 241 | if ((ret = esp_bluedroid_enable()) != ESP_OK) { 242 | ESP_LOGE(TAG, "%s enable Slave bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); 243 | return; 244 | } 245 | 246 | if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { 247 | ESP_LOGE(TAG, "%s Slave gap register failed: %s\n", __func__, esp_err_to_name(ret)); 248 | return; 249 | } 250 | 251 | if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { 252 | ESP_LOGE(TAG, "%s Slave spp register failed: %s\n", __func__, esp_err_to_name(ret)); 253 | return; 254 | } 255 | 256 | if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { 257 | ESP_LOGE(TAG, "%s Slave spp init failed: %s\n", __func__, esp_err_to_name(ret)); 258 | return; 259 | } 260 | 261 | /* 262 | * Set default parameters for Legacy Pairing 263 | * Use variable pin, input pin code when pairing 264 | */ 265 | 266 | esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; 267 | esp_bt_gap_set_pin(pin_type, 4, slave->pin_code); 268 | 269 | esp_bt_dev_set_device_name(slave->name); 270 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 271 | ESP_LOGI(TAG, "Start server"); 272 | esp_spp_start_srv(sec_mask, role_slave, 0, slave->name); 273 | } 274 | 275 | STATIC mp_obj_t bts_init(mp_obj_t name, mp_obj_t pin){ 276 | if (slave_up == true) { 277 | return mp_const_false; 278 | } 279 | char *sn = mp_obj_str_get_str(name); 280 | char *sp = mp_obj_str_get_str(pin); 281 | if (slave_storage == false) { 282 | // create slave object 283 | slave_obj_t *so = m_new_obj(slave_obj_t); 284 | memcpy(so->name, sn, strlen(sn)); // slave name 285 | memcpy(so->pin_code, sp, strlen(sp)); // PIN 286 | slave = so; 287 | slave->ready = false; 288 | // create pipe object 289 | int size = DEFAULT_PIPE_SIZE; 290 | pipe_obj_t *po = m_new_obj(pipe_obj_t); 291 | po->buffer = NULL; 292 | po->head = 0; 293 | po->tail = 0; 294 | po->lock = xSemaphoreCreateMutex(); 295 | char *buff = malloc(sizeof(char) * size); 296 | if (buff == NULL) { 297 | po->buffer = NULL; 298 | po->size = 0; 299 | } else { 300 | po->buffer = buff; 301 | po->size = size+1; 302 | } 303 | pipe = po; 304 | slave_storage = true; // ready with storage 305 | } else { 306 | memcpy(slave->name, sn, strlen(sn)); // slave name 307 | memcpy(slave->pin_code, sp, strlen(sp)); // PIN 308 | slave->ready = false; 309 | slave->handle = NULL; 310 | pipe->head = 0; 311 | pipe->tail = 0; 312 | } 313 | bts_start(); 314 | slave_up = true; // slave is up, can deinit 315 | return mp_const_true; 316 | } 317 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(bts_init_obj, bts_init); 318 | 319 | STATIC mp_obj_t bts_data() { 320 | int size = 0; 321 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 322 | if (pipe->tail >= pipe->head) { 323 | size = pipe->tail - pipe->head; 324 | } else { 325 | size = pipe->size - pipe->head + pipe->tail; 326 | } 327 | xSemaphoreGive(pipe->lock); 328 | } 329 | return mp_obj_new_int(size); 330 | } 331 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_data_obj, bts_data); 332 | 333 | STATIC mp_obj_t bts_get_str(const mp_obj_t what) { 334 | const int count = mp_obj_get_int(what); 335 | if (count > 0) { 336 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 337 | char items[count]; 338 | int i, removed = 0; 339 | for (i = 0; i < count; i++) { 340 | if (pipe->head == pipe->tail) { 341 | break; 342 | } 343 | items[i] = (char) pipe->buffer[pipe->head]; 344 | pipe->head = (pipe->head + 1) % pipe->size; 345 | removed++; 346 | } 347 | xSemaphoreGive(pipe->lock); 348 | if (removed > 0) { 349 | mp_obj_t data = mp_obj_new_str(items, removed); 350 | return data; 351 | } 352 | } 353 | } 354 | return mp_const_none; // count<=0 or can't get lock or empty pipe 355 | } 356 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_get_str_obj, bts_get_str); 357 | 358 | STATIC mp_obj_t bts_get_bin(const mp_obj_t what) { 359 | const int count = mp_obj_get_int(what); 360 | if (count > 0) { 361 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 362 | uint8_t items[count]; 363 | int i, removed = 0; 364 | for (i = 0; i < count; i++) { 365 | if (pipe->head == pipe->tail) { 366 | break; 367 | } 368 | items[i] = (uint8_t) pipe->buffer[pipe->head]; 369 | pipe->head = (pipe->head + 1) % pipe->size; 370 | removed++; 371 | } 372 | xSemaphoreGive(pipe->lock); 373 | if (removed > 0) { 374 | mp_obj_t data = mp_obj_new_bytes(items, removed); 375 | return data; 376 | } 377 | } 378 | } 379 | return mp_const_none; // count<=0 or can't get lock or empty pipe 380 | } 381 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_get_bin_obj, bts_get_bin); 382 | 383 | 384 | STATIC mp_obj_t bts_send_str(mp_obj_t data) { 385 | if (slave->ready == true) { 386 | char *str = mp_obj_str_get_str(data); 387 | memcpy(spp_data, str, strlen(str)); // convert char to uint8_t 388 | esp_spp_write(slave->handle, strlen(str), spp_data); 389 | } 390 | return mp_const_none; 391 | } 392 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_send_str_obj, bts_send_str); 393 | 394 | STATIC mp_obj_t bts_send_bin(mp_obj_t data) { 395 | size_t len; 396 | if (slave->ready == true) { 397 | char *bin = mp_obj_str_get_data(data, &len); 398 | memcpy(spp_data, bin, len); // convert char to uint8_t 399 | esp_spp_write(slave->handle, len, spp_data); 400 | } 401 | return mp_const_none; 402 | } 403 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_send_bin_obj, bts_send_bin); 404 | 405 | STATIC mp_obj_t bts_close(){ 406 | if (slave->ready == true) { 407 | esp_spp_disconnect(slave->handle); 408 | slave->ready = false; 409 | slave->handle = NULL; 410 | } 411 | return mp_const_true; 412 | } 413 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_close_obj, bts_close); 414 | 415 | STATIC mp_obj_t bts_ready(){ 416 | return mp_obj_new_bool(slave->ready); 417 | } 418 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_ready_obj, bts_ready); 419 | 420 | STATIC mp_obj_t bts_up(){ 421 | return mp_obj_new_bool(slave_up); 422 | } 423 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_up_obj, bts_up); 424 | 425 | STATIC mp_obj_t bts_deinit(){ 426 | if (slave_up == false) { 427 | return mp_const_false; 428 | } 429 | esp_spp_deinit(); 430 | esp_bluedroid_disable(); 431 | esp_bluedroid_deinit(); 432 | esp_bt_controller_disable(); 433 | esp_bt_controller_deinit(); 434 | slave->ready = false; 435 | slave->handle = NULL; 436 | slave_up = false; // can do init 437 | return mp_const_true; 438 | } 439 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_deinit_obj, bts_deinit); 440 | 441 | STATIC const mp_rom_map_elem_t bts_module_globals_table[] = { 442 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bts) }, 443 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&bts_init_obj) }, 444 | { MP_ROM_QSTR(MP_QSTR_up), MP_ROM_PTR(&bts_up_obj) }, 445 | { MP_ROM_QSTR(MP_QSTR_data), MP_ROM_PTR(&bts_data_obj) }, 446 | { MP_ROM_QSTR(MP_QSTR_get_str), MP_ROM_PTR(&bts_get_str_obj) }, 447 | { MP_ROM_QSTR(MP_QSTR_get_bin), MP_ROM_PTR(&bts_get_bin_obj) }, 448 | { MP_ROM_QSTR(MP_QSTR_send_str), MP_ROM_PTR(&bts_send_str_obj) }, 449 | { MP_ROM_QSTR(MP_QSTR_send_bin), MP_ROM_PTR(&bts_send_bin_obj) }, 450 | { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&bts_close_obj) }, 451 | { MP_ROM_QSTR(MP_QSTR_ready), MP_ROM_PTR(&bts_ready_obj) }, 452 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bts_deinit_obj) }, 453 | }; 454 | 455 | STATIC MP_DEFINE_CONST_DICT(bts_module_globals, bts_module_globals_table); 456 | 457 | const mp_obj_module_t mp_module_bts = { 458 | .base = { &mp_type_module }, 459 | .globals = (mp_obj_dict_t*)&bts_module_globals, 460 | }; 461 | 462 | MP_REGISTER_MODULE(MP_QSTR_bts, mp_module_bts); 463 | 464 | 465 | -------------------------------------------------------------------------------- /prod/README.md: -------------------------------------------------------------------------------- 1 | 2 | This is a production version of the firmware. No logging has been enabled. This reduces the size of the firmware. The sdkconfig.board is shown below: 3 | 4 | ``` 5 | # DEBUGGING 6 | CONFIG_LOG_DEFAULT_LEVEL=none 7 | 8 | # Override some defaults so BT stack is enabled 9 | CONFIG_BT_ENABLED=y 10 | # CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY= 11 | CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY=y 12 | # CONFIG_BTDM_CONTROLLER_MODE_BTDM= 13 | CONFIG_CLASSIC_BT_ENABLED=y 14 | CONFIG_BT_SPP_ENABLED=y 15 | # disable Secure Simple Pairing 16 | CONFIG_BT_SSP_ENABLED= 17 | # Support ONE connection at a time 18 | # don't work --> CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=1 19 | # need to change esp_bt_gap_set_scan_mode to 20 | # ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE 21 | # and WiFi is disabled 22 | CONFIG_WIFI_ENABLED=n 23 | ``` 24 | 25 | This is a "hobby" grade software. It is not guaranteed to work or even be useful in any way. The use of the firmware is entirely at the user's own risk. 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /prod/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shariltumin/esp32-bluetooth-classic-micropython/0fadb79e312329e038b0153bbe07e06098d7775b/prod/firmware.bin -------------------------------------------------------------------------------- /prod/src/bt_spp_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2023 Sharil Tumin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This is client, master, or central of Bluetooth Classic using SPP 25 | 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "nvs.h" 33 | #include "nvs_flash.h" 34 | #include "freertos/FreeRTOS.h" 35 | #include "freertos/task.h" 36 | #include "freertos/semphr.h" 37 | // -include "esp_log.h" 38 | #include "esp_bt.h" 39 | #include "esp_bt_main.h" 40 | #include "esp_gap_bt_api.h" 41 | #include "esp_bt_device.h" 42 | #include "esp_spp_api.h" 43 | 44 | #include "py/obj.h" 45 | #include "py/runtime.h" 46 | 47 | // -define TAG "SPP_CLIENT" 48 | 49 | #define NON_BLOCKING 0 50 | #define DEFAULT_PIPE_SIZE 1024 51 | 52 | typedef struct _pipe_obj_t { 53 | char *buffer; 54 | int head; 55 | int tail; 56 | int size; 57 | SemaphoreHandle_t lock; 58 | } pipe_obj_t; 59 | 60 | pipe_obj_t *pipe; /* will get value at btm.init() */ 61 | 62 | static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; 63 | static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; 64 | static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER; 65 | static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY; 66 | static const uint8_t inq_len = 30; 67 | static const uint8_t inq_num_rsps = 0; 68 | 69 | // use to get slave after discovery 70 | static char slave_device_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 71 | static uint8_t slave_device_name_len; 72 | 73 | // use for data in 74 | static uint8_t spp_data[ESP_SPP_MAX_MTU]; /* ESP_SPP_MAX_MTU = 990 bytes */ 75 | // static char msg_in[ESP_SPP_MAX_MTU]; 76 | 77 | typedef struct _master_obj_t { 78 | char name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 79 | char slave_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 80 | uint8_t slave_name_len; 81 | uint8_t slave_pin_code[17]; 82 | esp_bd_addr_t slave_addr; 83 | bool ready; 84 | uint32_t handle; /* current write handle */ 85 | uint32_t c_handle; /* connection handle */ 86 | } master_obj_t; 87 | 88 | master_obj_t *master; /* will get value at btm.init() */ 89 | 90 | static bool master_storage = false; /* master and pipe storage allocation flag */ 91 | 92 | static bool master_up = false; /* master not up, can do init */ 93 | static bool master_auth = false; /* master not authenticated */ 94 | 95 | static bool get_name_from_eir(uint8_t *eir, char *bdname, uint8_t *bdname_len) 96 | { 97 | uint8_t *rmt_bdname = NULL; 98 | uint8_t rmt_bdname_len = 0; 99 | 100 | if (!eir) { 101 | return false; 102 | } 103 | 104 | rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); 105 | if (!rmt_bdname) { 106 | rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); 107 | } 108 | 109 | if (rmt_bdname) { 110 | if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { 111 | rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; 112 | } 113 | 114 | if (bdname) { 115 | memcpy(bdname, rmt_bdname, rmt_bdname_len); 116 | bdname[rmt_bdname_len] = '\0'; 117 | } 118 | if (bdname_len) { 119 | *bdname_len = rmt_bdname_len; 120 | } 121 | return true; 122 | } 123 | 124 | return false; 125 | } 126 | 127 | static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) 128 | { 129 | uint8_t *items; 130 | int count; 131 | 132 | switch (event) { 133 | case ESP_SPP_INIT_EVT: 134 | break; 135 | case ESP_SPP_DISCOVERY_COMP_EVT: 136 | if (param->disc_comp.status == ESP_SPP_SUCCESS) { 137 | esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], master->slave_addr); 138 | } 139 | break; 140 | case ESP_SPP_OPEN_EVT: 141 | master->handle = param->srv_open.handle; 142 | master->c_handle = param->srv_open.handle; 143 | master->ready = true; 144 | break; 145 | case ESP_SPP_CLOSE_EVT: 146 | master->ready = false; 147 | master->handle = NULL; 148 | master->c_handle = NULL; 149 | break; 150 | case ESP_SPP_START_EVT: 151 | break; 152 | case ESP_SPP_CL_INIT_EVT: 153 | break; 154 | case ESP_SPP_DATA_IND_EVT: 155 | items = param->data_ind.data; 156 | count = param->data_ind.len; 157 | int i = 0; 158 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 159 | for (i = 0; i < count; i++) { 160 | int next_tail = (pipe->tail + 1) % pipe->size; 161 | if (next_tail == pipe->head) { 162 | break; 163 | } 164 | pipe->buffer[pipe->tail] = (char) *items; 165 | pipe->tail = next_tail; 166 | items++; 167 | } 168 | xSemaphoreGive(pipe->lock); 169 | } 170 | master->handle = param->data_ind.handle; 171 | break; 172 | case ESP_SPP_CONG_EVT: 173 | master->handle = param->cong.handle; 174 | break; 175 | case ESP_SPP_WRITE_EVT: 176 | master->handle = param->write.handle; 177 | break; 178 | case ESP_SPP_SRV_OPEN_EVT: 179 | break; 180 | default: 181 | break; 182 | } 183 | } 184 | 185 | static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) 186 | { 187 | switch(event){ 188 | case ESP_BT_GAP_DISC_RES_EVT: 189 | for (int i = 0; i < param->disc_res.num_prop; i++){ 190 | if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR 191 | && get_name_from_eir(param->disc_res.prop[i].val, slave_device_name, &slave_device_name_len)){ 192 | if (strlen(slave_device_name) == master->slave_name_len 193 | && strncmp(master->slave_name, slave_device_name, master->slave_name_len) == 0) { 194 | memcpy(master->slave_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); 195 | esp_spp_start_discovery(master->slave_addr); 196 | esp_bt_gap_cancel_discovery(); 197 | } 198 | } 199 | } 200 | break; 201 | case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: 202 | break; 203 | case ESP_BT_GAP_RMT_SRVCS_EVT: 204 | break; 205 | case ESP_BT_GAP_RMT_SRVC_REC_EVT: 206 | break; 207 | case ESP_BT_GAP_AUTH_CMPL_EVT:{ 208 | if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { 209 | master_auth = true; 210 | } else { 211 | master_auth = false; 212 | } 213 | break; 214 | } 215 | case ESP_BT_GAP_PIN_REQ_EVT:{ 216 | if (param->pin_req.min_16_digit) { 217 | esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, master->slave_pin_code); 218 | } else { 219 | esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, master->slave_pin_code); 220 | } 221 | break; 222 | } 223 | 224 | default: 225 | break; 226 | } 227 | } 228 | 229 | void btm_start() 230 | { 231 | esp_err_t ret = nvs_flash_init(); 232 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 233 | ESP_ERROR_CHECK(nvs_flash_erase()); 234 | ret = nvs_flash_init(); 235 | } 236 | ESP_ERROR_CHECK( ret ); 237 | 238 | ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); 239 | 240 | esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); 241 | if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { 242 | return; 243 | } 244 | 245 | if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { 246 | return; 247 | } 248 | 249 | if ((ret = esp_bluedroid_init()) != ESP_OK) { 250 | return; 251 | } 252 | 253 | if ((ret = esp_bluedroid_enable()) != ESP_OK) { 254 | return; 255 | } 256 | 257 | if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { 258 | return; 259 | } 260 | 261 | if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { 262 | return; 263 | } 264 | 265 | if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { 266 | return; 267 | } 268 | 269 | // set others 270 | esp_bt_dev_set_device_name(master->name); 271 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 272 | return; 273 | } 274 | 275 | STATIC mp_obj_t btm_init(mp_obj_t name){ 276 | if (master_up == true) { 277 | return mp_const_false; 278 | } 279 | char *mn = mp_obj_str_get_str(name); 280 | if (master_storage == false) { 281 | // create master object 282 | master_obj_t *mo = m_new_obj(master_obj_t); 283 | memcpy(mo->name, mn, strlen(mn)); // master name 284 | master = mo; 285 | // create pipe object 286 | int size = DEFAULT_PIPE_SIZE; 287 | pipe_obj_t *po = m_new_obj(pipe_obj_t); 288 | po->buffer = NULL; 289 | po->head = 0; 290 | po->tail = 0; 291 | po->lock = xSemaphoreCreateMutex(); 292 | char *buff = malloc(sizeof(char) * size); 293 | if (buff == NULL) { 294 | po->buffer = NULL; 295 | po->size = 0; 296 | } else { 297 | po->buffer = buff; 298 | po->size = size+1; 299 | } 300 | pipe = po; 301 | master_storage = true; // ready with storage 302 | } else { 303 | memcpy(master->name, mn, strlen(mn)); // master name 304 | master->ready = false; 305 | master->handle = NULL; 306 | master->c_handle = NULL; 307 | pipe->head = 0; 308 | pipe->tail = 0; 309 | } 310 | btm_start(); 311 | master_up = true; // master is up, can deinit 312 | return mp_const_true; 313 | } 314 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_init_obj, btm_init); 315 | 316 | STATIC mp_obj_t btm_data() { 317 | int size = 0; 318 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 319 | if (pipe->tail >= pipe->head) { 320 | size = pipe->tail - pipe->head; 321 | } else { 322 | size = pipe->size - pipe->head + pipe->tail; 323 | } 324 | xSemaphoreGive(pipe->lock); 325 | } 326 | return mp_obj_new_int(size); 327 | } 328 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_data_obj, btm_data); 329 | 330 | STATIC mp_obj_t btm_get_str(const mp_obj_t what) { 331 | const int count = mp_obj_get_int(what); 332 | if (count > 0) { 333 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 334 | char items[count]; 335 | int i, removed = 0; 336 | for (i = 0; i < count; i++) { 337 | if (pipe->head == pipe->tail) { 338 | break; 339 | } 340 | items[i] = (char) pipe->buffer[pipe->head]; 341 | pipe->head = (pipe->head + 1) % pipe->size; 342 | removed++; 343 | } 344 | xSemaphoreGive(pipe->lock); 345 | if (removed > 0) { 346 | mp_obj_t data = mp_obj_new_str(items, removed); 347 | return data; 348 | } 349 | } 350 | } 351 | return mp_const_none; // count<=0 or can't get lock or empty pipe 352 | } 353 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_get_str_obj, btm_get_str); 354 | 355 | STATIC mp_obj_t btm_get_bin(const mp_obj_t what) { 356 | const int count = mp_obj_get_int(what); 357 | if (count > 0) { 358 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 359 | uint8_t items[count]; 360 | int i, removed = 0; 361 | for (i = 0; i < count; i++) { 362 | if (pipe->head == pipe->tail) { 363 | break; 364 | } 365 | items[i] = (uint8_t) pipe->buffer[pipe->head]; 366 | pipe->head = (pipe->head + 1) % pipe->size; 367 | removed++; 368 | } 369 | xSemaphoreGive(pipe->lock); 370 | if (removed > 0) { 371 | mp_obj_t data = mp_obj_new_bytes(items, removed); 372 | return data; 373 | } 374 | } 375 | } 376 | return mp_const_none; // count<=0 or can't get lock or empty pipe 377 | } 378 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_get_bin_obj, btm_get_bin); 379 | 380 | STATIC mp_obj_t btm_send_str(mp_obj_t data) { 381 | if (master->ready == true) { 382 | char *str = mp_obj_str_get_str(data); 383 | memcpy(spp_data, str, strlen(str)); // convert char to uint8_t 384 | esp_spp_write(master->handle, strlen(str), spp_data); 385 | } 386 | return mp_const_none; 387 | } 388 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_send_str_obj, btm_send_str); 389 | 390 | STATIC mp_obj_t btm_send_bin(mp_obj_t data) { 391 | size_t len; 392 | if (master->ready == true) { 393 | char *bin = mp_obj_str_get_data(data, &len); 394 | memcpy(spp_data, bin, len); // convert char to uint8_t 395 | esp_spp_write(master->handle, len, spp_data); 396 | } 397 | return mp_const_none; 398 | } 399 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(btm_send_bin_obj, btm_send_bin); 400 | 401 | STATIC mp_obj_t btm_open(mp_obj_t name, mp_obj_t pin) { 402 | char *sn = mp_obj_str_get_str(name); 403 | char *sp = mp_obj_str_get_str(pin); 404 | memcpy(master->slave_name, sn, strlen(sn)); // slave name 405 | master->slave_name_len = strlen(sn); // slave name length 406 | memcpy(master->slave_pin_code, sp, strlen(sp)); // binding PIN 407 | esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps); 408 | return mp_const_none; 409 | } 410 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(btm_open_obj, btm_open); 411 | 412 | STATIC mp_obj_t btm_close(){ 413 | if (master->ready == true) { 414 | esp_spp_disconnect(master->c_handle); 415 | master->ready = false; 416 | master->handle = NULL; 417 | master->c_handle = NULL; 418 | } 419 | return mp_const_true; 420 | } 421 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_close_obj, btm_close); 422 | 423 | STATIC mp_obj_t btm_ready(){ 424 | return mp_obj_new_bool(master->ready); 425 | } 426 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_ready_obj, btm_ready); 427 | 428 | STATIC mp_obj_t btm_up(){ 429 | return mp_obj_new_bool(master_up); 430 | } 431 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_up_obj, btm_up); 432 | 433 | STATIC mp_obj_t btm_deinit(){ 434 | if (master_up == false) { 435 | return mp_const_false; 436 | } 437 | esp_spp_deinit(); 438 | esp_bluedroid_disable(); 439 | esp_bluedroid_deinit(); 440 | esp_bt_controller_disable(); 441 | esp_bt_controller_deinit(); 442 | master->ready = false; 443 | master->handle = NULL; 444 | master->c_handle = NULL; 445 | master_up = false; // can do init 446 | return mp_const_true; 447 | } 448 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(btm_deinit_obj, btm_deinit); 449 | 450 | STATIC const mp_rom_map_elem_t btm_module_globals_table[] = { 451 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_btm) }, 452 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&btm_init_obj) }, 453 | { MP_ROM_QSTR(MP_QSTR_up), MP_ROM_PTR(&btm_up_obj) }, 454 | { MP_ROM_QSTR(MP_QSTR_data), MP_ROM_PTR(&btm_data_obj) }, 455 | { MP_ROM_QSTR(MP_QSTR_get_str), MP_ROM_PTR(&btm_get_str_obj) }, 456 | { MP_ROM_QSTR(MP_QSTR_get_bin), MP_ROM_PTR(&btm_get_bin_obj) }, 457 | { MP_ROM_QSTR(MP_QSTR_send_str), MP_ROM_PTR(&btm_send_str_obj) }, 458 | { MP_ROM_QSTR(MP_QSTR_send_bin), MP_ROM_PTR(&btm_send_bin_obj) }, 459 | { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&btm_open_obj) }, 460 | { MP_ROM_QSTR(MP_QSTR_ready), MP_ROM_PTR(&btm_ready_obj) }, 461 | { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&btm_close_obj) }, 462 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&btm_deinit_obj) }, 463 | }; 464 | 465 | STATIC MP_DEFINE_CONST_DICT(btm_module_globals, btm_module_globals_table); 466 | 467 | const mp_obj_module_t mp_module_btm = { 468 | .base = { &mp_type_module }, 469 | .globals = (mp_obj_dict_t*)&btm_module_globals, 470 | }; 471 | 472 | MP_REGISTER_MODULE(MP_QSTR_btm, mp_module_btm); 473 | 474 | -------------------------------------------------------------------------------- /prod/src/bt_spp_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2023 Sharil Tumin 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This the server, slave, or peripheral of Bluetooth Classic serving SPP 25 | 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "nvs.h" 33 | #include "nvs_flash.h" 34 | #include "freertos/FreeRTOS.h" 35 | #include "freertos/task.h" 36 | #include "freertos/semphr.h" 37 | // -include "esp_log.h" 38 | #include "esp_bt.h" 39 | #include "esp_bt_main.h" 40 | #include "esp_gap_bt_api.h" 41 | #include "esp_bt_device.h" 42 | #include "esp_spp_api.h" 43 | 44 | #include "py/obj.h" 45 | #include "py/runtime.h" 46 | 47 | // -define TAG "SPP_SERVER" 48 | 49 | #define NON_BLOCKING 0 50 | #define DEFAULT_PIPE_SIZE 1024 51 | 52 | typedef struct _pipe_obj_t { 53 | char *buffer; 54 | int head; 55 | int tail; 56 | int size; 57 | SemaphoreHandle_t lock; 58 | } pipe_obj_t; 59 | 60 | pipe_obj_t *pipe; /* will get value at bts.init() */ 61 | 62 | #define SPP_DATA_LEN ESP_SPP_MAX_MTU 63 | static uint8_t spp_data[SPP_DATA_LEN]; /* ESP_SPP_MAX_MTU = 990 bytes */ 64 | 65 | static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; 66 | static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; 67 | // static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHORIZE; 68 | static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE; 69 | 70 | typedef struct _slave_obj_t { 71 | char name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; 72 | uint8_t pin_code[17]; 73 | /* esp_bd_addr_t master_addr; */ 74 | bool ready; 75 | uint32_t handle; /* current write handle */ 76 | } slave_obj_t; 77 | 78 | slave_obj_t *slave; /* will get value at bts.init() */ 79 | 80 | static bool slave_storage = false; /* slave and pipe storage allocation flag */ 81 | 82 | static bool slave_up = false; /* slave not up, can do init */ 83 | 84 | static bool slave_auth = false; /* slave not autenticated */ 85 | 86 | static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) 87 | { 88 | uint8_t *items; 89 | int count; 90 | 91 | switch (event) { 92 | case ESP_SPP_INIT_EVT: 93 | break; 94 | case ESP_SPP_DISCOVERY_COMP_EVT: 95 | break; 96 | case ESP_SPP_OPEN_EVT: 97 | break; 98 | case ESP_SPP_CLOSE_EVT: 99 | slave->ready = false; 100 | slave->handle = NULL; 101 | // now waiting for new connection 102 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 103 | break; 104 | case ESP_SPP_START_EVT: 105 | break; 106 | case ESP_SPP_CL_INIT_EVT: 107 | break; 108 | case ESP_SPP_DATA_IND_EVT: 109 | items = param->data_ind.data; 110 | count = param->data_ind.len; 111 | int i = 0; 112 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 113 | for (i = 0; i < count; i++) { 114 | int next_tail = (pipe->tail + 1) % pipe->size; 115 | if (next_tail == pipe->head) { 116 | break; 117 | } 118 | pipe->buffer[pipe->tail] = (char) *items; 119 | pipe->tail = next_tail; 120 | items++; 121 | // added++; 122 | } 123 | xSemaphoreGive(pipe->lock); 124 | } 125 | slave->handle = param->data_ind.handle; 126 | slave->ready = true; // master MUST send message slave first 127 | break; 128 | case ESP_SPP_CONG_EVT: 129 | break; 130 | case ESP_SPP_WRITE_EVT: 131 | break; 132 | case ESP_SPP_SRV_OPEN_EVT: 133 | // make the slave stop responding to discorery request 134 | esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); 135 | break; 136 | default: 137 | break; 138 | } 139 | } 140 | 141 | void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) 142 | { 143 | switch (event) { 144 | case ESP_BT_GAP_AUTH_CMPL_EVT:{ 145 | if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { 146 | slave_auth = true; 147 | } else { 148 | slave_auth = false; 149 | } 150 | break; 151 | } 152 | case ESP_BT_GAP_PIN_REQ_EVT:{ 153 | break; 154 | } 155 | 156 | // These are for CONFIG_BT_SSP_ENABLED 157 | case ESP_BT_GAP_CFM_REQ_EVT: 158 | break; 159 | case ESP_BT_GAP_KEY_NOTIF_EVT: 160 | break; 161 | case ESP_BT_GAP_KEY_REQ_EVT: 162 | break; 163 | // These are for CONFIG_BT_SSP_ENABLED 164 | 165 | default: { 166 | break; 167 | } 168 | } 169 | return; 170 | } 171 | 172 | void bts_start() 173 | { 174 | esp_err_t ret = nvs_flash_init(); 175 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 176 | ESP_ERROR_CHECK(nvs_flash_erase()); 177 | ret = nvs_flash_init(); 178 | } 179 | ESP_ERROR_CHECK( ret ); 180 | 181 | ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); 182 | 183 | esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); 184 | if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { 185 | return; 186 | } 187 | 188 | if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { 189 | return; 190 | } 191 | 192 | if ((ret = esp_bluedroid_init()) != ESP_OK) { 193 | return; 194 | } 195 | 196 | if ((ret = esp_bluedroid_enable()) != ESP_OK) { 197 | return; 198 | } 199 | 200 | if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { 201 | return; 202 | } 203 | 204 | if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { 205 | return; 206 | } 207 | 208 | if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { 209 | return; 210 | } 211 | 212 | /* 213 | * Set default parameters for Legacy Pairing 214 | * Use variable pin, input pin code when pairing 215 | */ 216 | 217 | esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; 218 | esp_bt_gap_set_pin(pin_type, 4, slave->pin_code); 219 | 220 | esp_bt_dev_set_device_name(slave->name); 221 | esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); 222 | esp_spp_start_srv(sec_mask, role_slave, 0, slave->name); 223 | } 224 | 225 | STATIC mp_obj_t bts_init(mp_obj_t name, mp_obj_t pin){ 226 | if (slave_up == true) { 227 | return mp_const_false; 228 | } 229 | char *sn = mp_obj_str_get_str(name); 230 | char *sp = mp_obj_str_get_str(pin); 231 | if (slave_storage == false) { 232 | // create slave object 233 | slave_obj_t *so = m_new_obj(slave_obj_t); 234 | memcpy(so->name, sn, strlen(sn)); // slave name 235 | memcpy(so->pin_code, sp, strlen(sp)); // PIN 236 | slave = so; 237 | slave->ready = false; 238 | // create pipe object 239 | int size = DEFAULT_PIPE_SIZE; 240 | pipe_obj_t *po = m_new_obj(pipe_obj_t); 241 | po->buffer = NULL; 242 | po->head = 0; 243 | po->tail = 0; 244 | po->lock = xSemaphoreCreateMutex(); 245 | char *buff = malloc(sizeof(char) * size); 246 | if (buff == NULL) { 247 | po->buffer = NULL; 248 | po->size = 0; 249 | } else { 250 | po->buffer = buff; 251 | po->size = size+1; 252 | } 253 | pipe = po; 254 | slave_storage = true; // ready with storage 255 | } else { 256 | memcpy(slave->name, sn, strlen(sn)); // slave name 257 | memcpy(slave->pin_code, sp, strlen(sp)); // PIN 258 | slave->ready = false; 259 | slave->handle = NULL; 260 | pipe->head = 0; 261 | pipe->tail = 0; 262 | } 263 | bts_start(); 264 | slave_up = true; // slave is up, can deinit 265 | return mp_const_true; 266 | } 267 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(bts_init_obj, bts_init); 268 | 269 | STATIC mp_obj_t bts_data() { 270 | int size = 0; 271 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 272 | if (pipe->tail >= pipe->head) { 273 | size = pipe->tail - pipe->head; 274 | } else { 275 | size = pipe->size - pipe->head + pipe->tail; 276 | } 277 | xSemaphoreGive(pipe->lock); 278 | } 279 | return mp_obj_new_int(size); 280 | } 281 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_data_obj, bts_data); 282 | 283 | STATIC mp_obj_t bts_get_str(const mp_obj_t what) { 284 | const int count = mp_obj_get_int(what); 285 | if (count > 0) { 286 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 287 | char items[count]; 288 | int i, removed = 0; 289 | for (i = 0; i < count; i++) { 290 | if (pipe->head == pipe->tail) { 291 | break; 292 | } 293 | items[i] = (char) pipe->buffer[pipe->head]; 294 | pipe->head = (pipe->head + 1) % pipe->size; 295 | removed++; 296 | } 297 | xSemaphoreGive(pipe->lock); 298 | if (removed > 0) { 299 | mp_obj_t data = mp_obj_new_str(items, removed); 300 | return data; 301 | } 302 | } 303 | } 304 | return mp_const_none; // count<=0 or can't get lock or empty pipe 305 | } 306 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_get_str_obj, bts_get_str); 307 | 308 | STATIC mp_obj_t bts_get_bin(const mp_obj_t what) { 309 | const int count = mp_obj_get_int(what); 310 | if (count > 0) { 311 | if (xSemaphoreTake(pipe->lock, (TickType_t) NON_BLOCKING) == pdTRUE) { 312 | uint8_t items[count]; 313 | int i, removed = 0; 314 | for (i = 0; i < count; i++) { 315 | if (pipe->head == pipe->tail) { 316 | break; 317 | } 318 | items[i] = (uint8_t) pipe->buffer[pipe->head]; 319 | pipe->head = (pipe->head + 1) % pipe->size; 320 | removed++; 321 | } 322 | xSemaphoreGive(pipe->lock); 323 | if (removed > 0) { 324 | mp_obj_t data = mp_obj_new_bytes(items, removed); 325 | return data; 326 | } 327 | } 328 | } 329 | return mp_const_none; // count<=0 or can't get lock or empty pipe 330 | } 331 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_get_bin_obj, bts_get_bin); 332 | 333 | 334 | STATIC mp_obj_t bts_send_str(mp_obj_t data) { 335 | if (slave->ready == true) { 336 | char *str = mp_obj_str_get_str(data); 337 | memcpy(spp_data, str, strlen(str)); // convert char to uint8_t 338 | esp_spp_write(slave->handle, strlen(str), spp_data); 339 | } 340 | return mp_const_none; 341 | } 342 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_send_str_obj, bts_send_str); 343 | 344 | STATIC mp_obj_t bts_send_bin(mp_obj_t data) { 345 | size_t len; 346 | if (slave->ready == true) { 347 | char *bin = mp_obj_str_get_data(data, &len); 348 | memcpy(spp_data, bin, len); // convert char to uint8_t 349 | esp_spp_write(slave->handle, len, spp_data); 350 | } 351 | return mp_const_none; 352 | } 353 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(bts_send_bin_obj, bts_send_bin); 354 | 355 | STATIC mp_obj_t bts_close(){ 356 | if (slave->ready == true) { 357 | esp_spp_disconnect(slave->handle); 358 | slave->ready = false; 359 | slave->handle = NULL; 360 | } 361 | return mp_const_true; 362 | } 363 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_close_obj, bts_close); 364 | 365 | STATIC mp_obj_t bts_ready(){ 366 | return mp_obj_new_bool(slave->ready); 367 | } 368 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_ready_obj, bts_ready); 369 | 370 | STATIC mp_obj_t bts_up(){ 371 | return mp_obj_new_bool(slave_up); 372 | } 373 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_up_obj, bts_up); 374 | 375 | STATIC mp_obj_t bts_deinit(){ 376 | if (slave_up == false) { 377 | return mp_const_false; 378 | } 379 | esp_spp_deinit(); 380 | esp_bluedroid_disable(); 381 | esp_bluedroid_deinit(); 382 | esp_bt_controller_disable(); 383 | esp_bt_controller_deinit(); 384 | slave->ready = false; 385 | slave->handle = NULL; 386 | slave_up = false; // can do init 387 | return mp_const_true; 388 | } 389 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(bts_deinit_obj, bts_deinit); 390 | 391 | STATIC const mp_rom_map_elem_t bts_module_globals_table[] = { 392 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bts) }, 393 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&bts_init_obj) }, 394 | { MP_ROM_QSTR(MP_QSTR_up), MP_ROM_PTR(&bts_up_obj) }, 395 | { MP_ROM_QSTR(MP_QSTR_data), MP_ROM_PTR(&bts_data_obj) }, 396 | { MP_ROM_QSTR(MP_QSTR_get_str), MP_ROM_PTR(&bts_get_str_obj) }, 397 | { MP_ROM_QSTR(MP_QSTR_get_bin), MP_ROM_PTR(&bts_get_bin_obj) }, 398 | { MP_ROM_QSTR(MP_QSTR_send_str), MP_ROM_PTR(&bts_send_str_obj) }, 399 | { MP_ROM_QSTR(MP_QSTR_send_bin), MP_ROM_PTR(&bts_send_bin_obj) }, 400 | { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&bts_close_obj) }, 401 | { MP_ROM_QSTR(MP_QSTR_ready), MP_ROM_PTR(&bts_ready_obj) }, 402 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bts_deinit_obj) }, 403 | }; 404 | 405 | STATIC MP_DEFINE_CONST_DICT(bts_module_globals, bts_module_globals_table); 406 | 407 | const mp_obj_module_t mp_module_bts = { 408 | .base = { &mp_type_module }, 409 | .globals = (mp_obj_dict_t*)&bts_module_globals, 410 | }; 411 | 412 | MP_REGISTER_MODULE(MP_QSTR_bts, mp_module_bts); 413 | 414 | 415 | --------------------------------------------------------------------------------