Summary
I will be teaching a video training workshop for Mouser on October 24. In the class I am going to show a bunch of Bluetooth things running on PSoC6 with WICED. Given that Bluetooth has two sides of a connection I wanted something that people could download from the iOS App Store to interact with my PSoC6/4343W design that looked like a remote control. And, I found a program called GoBLE which was developed by a company called DF Robot to control their robots. Here is a screenshot from my iPhone where you can see that it has four buttons on the right, a joystick on the left and two buttons in the middle.
I watched their youtube video, and it seems great… but how does it work? And how do I make a WICED Bluetooth project to work with it?
GoBLE Documentation
So, how does the App work to send button/joystick commands? Well, if you press the little “i” in the App, it will take you to this screen. You can see that it sends a variable length packet of data that tells you which buttons are being pressed and where the joystick location is in x & y. OK… but that doesn’t really tell you what to do on the Bluetooth Peripheral side.
If you look around on the internet you will find on GitHub a place where they tell you what the BLE Services/Characteristics need to look like: (sort of).
What they appear to have meant is that the SerialPortId and Command Id Characteristic need to have the GATT properties Read, WriteWithoutResponse, Write and Notify properties and that you need the Client Characteristic Configuration Descriptor for both Characteristics. In the WICED application (that I will show you in the next section) that I build to test the remote control, they do not appear to use the CCCD or Read properties. In addition they only use the Serial Port Id characteristic. In fact if you do not include that Characteristic, it will still work. The bottom line is that you database should look like this (in WICED).
/* Primary Service 'RobotService' */ PRIMARY_SERVICE_UUID128 (HDLS_ROBOTSERVICE, __UUID_ROBOTSERVICE), /* Characteristic 'SerialPortId' */ CHARACTERISTIC_UUID128_WRITABLE (HDLC_ROBOTSERVICE_SERIALPORTID, HDLC_ROBOTSERVICE_SERIALPORTID_VALUE, __UUID_ROBOTSERVICE_SERIALPORTID, LEGATTDB_CHAR_PROP_READ | LEGATTDB_CHAR_PROP_WRITE_NO_RESPONSE | LEGATTDB_CHAR_PROP_WRITE | LEGATTDB_CHAR_PROP_NOTIFY, LEGATTDB_PERM_READABLE | LEGATTDB_PERM_WRITE_CMD | LEGATTDB_PERM_WRITE_REQ), /* Descriptor 'Client Characteristic Configuration' */ CHAR_DESCRIPTOR_UUID16_WRITABLE (HDLD_ROBOTSERVICE_SERIALPORTID_CLIENT_CONFIGURATION, UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION, LEGATTDB_PERM_READABLE | LEGATTDB_PERM_WRITE_REQ | LEGATTDB_PERM_AUTH_WRITABLE),
The good news is they do service discovery and didn’t depend on a hardcoded handle.
WICED 20719 Implementation
Start by using the Bluetooth Designer to make a new project for the 20719-B1, which I will run on a CYW920719Q40EVB_01.
Click on the add Service button.
Add a Service Name as GoBLE, and then type in the UUID for the service, 0000dfb0-0000-1000-8000-00805f9b34fb. Notice that you need to do UUIDs as little endian.
The next step is to add a characteristic.
Name the Characteristic SerialPortId and setup the UUID 0000dfb1-0000-1000-8000-00805f9b34fb (once again little endian)
Now you need to configure the Characteristic properties to match the specification.
After all that is done, press the generate code button to build the project. Now you will need to edit a little bit of code. Start by enabling the PUART.
#if ((defined WICED_BT_TRACE_ENABLE) || (defined HCI_TRACE_OVER_TRANSPORT)) /* Set the Debug UART as WICED_ROUTE_DEBUG_NONE to get rid of prints */ // wiced_set_debug_uart( WICED_ROUTE_DEBUG_NONE ); /* Set Debug UART as WICED_ROUTE_DEBUG_TO_PUART to see debug traces on Peripheral UART (PUART) */ wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_PUART ); /* Set the Debug UART as WICED_ROUTE_DEBUG_TO_WICED_UART to send debug strings over the WICED debug interface */ //wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_WICED_UART ); #endif
The iPhone App recognizes you as a peripheral by seeing the Service UUID advertised. Unfortunately whoever wrote the advertising packet parser assumed that the Service UUID was first (which it doesn’t have to be). If you notice I added the name of the device to the advertising packet… but while I was trying to figure that out I put a #if 1 / #endif around that part of the packet. (notice that on line 178 I modified it the number of packet elements to be 3.
/* Set Advertisement Data */ void gobletest_set_advertisement_data( void ) { wiced_bt_ble_advert_elem_t adv_elem[3] = { 0 }; uint8_t adv_flag = BTM_BLE_GENERAL_DISCOVERABLE_FLAG | BTM_BLE_BREDR_NOT_SUPPORTED; uint8_t num_elem = 0; /* Advertisement Element for Flags */ adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_FLAG; adv_elem[num_elem].len = sizeof(uint8_t); adv_elem[num_elem].p_data = &adv_flag; num_elem++; uint8_t gobleuuid[] = {__UUID_GOBLE}; /* Advertisement Element for Name */ adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_128SRV_COMPLETE; adv_elem[num_elem].len = 16; adv_elem[num_elem].p_data = gobleuuid; num_elem++; #if 1 /* Advertisement Element for Name */ adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_NAME_COMPLETE; adv_elem[num_elem].len = strlen((const char*)BT_LOCAL_NAME); adv_elem[num_elem].p_data = BT_LOCAL_NAME; num_elem++; #endif /* Set Raw Advertisement Data */ wiced_bt_ble_set_raw_advertisement_data(num_elem, adv_elem); }
When the App writes to the 20719 you will get a callback in the function “goble_server_callback”. To figure it out I print out the raw data, as well as the parsed version.
/* GATT Server Event Callback */ wiced_bt_gatt_status_t gobletest_server_callback( uint16_t conn_id, wiced_bt_gatt_request_type_t type, wiced_bt_gatt_request_data_t *p_data ) { wiced_bt_gatt_status_t status = WICED_BT_GATT_ERROR; switch ( type ) { case GATTS_REQ_TYPE_READ: status = gobletest_read_handler( &p_data->read_req, conn_id ); break; case GATTS_REQ_TYPE_WRITE: if(p_data->write_req.handle == HDLC_GOBLE_SERIALPORTID_VALUE) { WICED_BT_TRACE("Activate = "); for(int i=0;i<p_data->write_req.val_len;i++) { WICED_BT_TRACE("%02X ",p_data->write_req.p_val[i]); } WICED_BT_TRACE("\n"); uint32_t numButtons = p_data->write_req.p_val[3]; uint32_t sliderX = p_data->write_req.p_val[5+numButtons]; uint32_t sliderY = p_data->write_req.p_val[6+numButtons]; uint32_t buttonMask = 0x00; for(int i=0;i<numButtons;i++) { buttonMask |= (1<<p_data->write_req.p_val[5+i]); } WICED_BT_TRACE("# Buttons = %d ButtonMask=%X Slider x=%X Slider Y=%X\n",numButtons,buttonMask,sliderX,sliderY); status = WICED_BT_GATT_SUCCESS; } else status = gobletest_write_handler( &p_data->write_req, conn_id ); break; } return status; }
In the file wiced_bt_cfg.c modify the advertising duration to “0” which means never timeout.
/* BLE Advertisement Settings */ .ble_advert_cfg = { .channel_map = BTM_BLE_ADVERT_CHNL_37 | /**< Advertising Channel Map (mask of BTM_BLE_ADVERT_CHNL_37, BTM_BLE_ADVERT_CHNL_38, BTM_BLE_ADVERT_CHNL_39) */ BTM_BLE_ADVERT_CHNL_38 | BTM_BLE_ADVERT_CHNL_39, .high_duty_min_interval = WICED_BT_CFG_DEFAULT_HIGH_DUTY_ADV_MIN_INTERVAL, /**< High Duty Undirected Connectable Minimum Advertising Interval */ .high_duty_max_interval = WICED_BT_CFG_DEFAULT_HIGH_DUTY_ADV_MAX_INTERVAL, /**< High Duty Undirected Connectable Maximum Advertising Interval */ .high_duty_duration = 0, /**< High Duty Undirected Connectable Advertising Duration in seconds (0 for infinite) */
Now that is all done I can program the development kit. The phone immediately recognizes the kit. And, when I press the buttons you can see that they are printed out. You can see that I pressed two buttons at a time to start, then I moved the slider around.
OK. All good.