// PRIMARY_SERVICE, GAP_SERVICE
// CHARACTERISTIC, GAP_DEVICE_NAME, READ, "FRIAMAT_X_SERIALNUMB"
PRIMARY_SERVICE, GATT_SERVICE
CHARACTERISTIC, GATT_DATABASE_HASH, READ,
// Dummy Service
PRIMARY_SERVICE, e80e0b86-3e0b-4d5a-a222-36ebea895000
// Dummy Service, Some Characteristic, with read and write
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea895002, READ | WRITE | DYNAMIC | NOTIFY, "Binding"
// Dummy Service, Characteristic, with read and notify + authentication
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea895004, READ | NOTIFY | DYNAMIC, "Status"
// Dummy Service, Some Characteristic, with read and write
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea895006, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Read"
// Dummy Service, Some Characteristic, with read and write
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea895008, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Write"
// Dummy Service, Some Characteristic, with read and write
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea89500a, NOTIFY | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Operation"
// Dummy Service, Some Characteristic, with read and write
CHARACTERISTIC, e80e0b86-3e0b-4d5a-a222-36ebea89500c, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Protocol"
```
The read operation for `e80e0b86-3e0b-4d5a-a222-36ebea895002`
reads on `e80e0b86-3e0b-4d5a-a222-36ebea895004`.
Subscribing has the same relative behavior.
Hitting other characteristics is a total bust.
The mobile side can be tested using the following repository:
https://github.com/Pluto-Y/Swift-LightBlue?tab=readme-ov-file
NRFConect, and the like all have the same issue.
```
const uint16_t ADVERTISEMENT_INTERVAL_MIN = 800;
const uint16_t ADVERTISEMENT_INTERVAL_MAX = 800;
const uint8_t G_ADV_FLAGS[] = { 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06 };
const uint8_t CHANNEL_MAP = 0x07; // based on example
/**
* @brief Handles packets and events
*
* Handles security manager events and ATT server events (Connect/Disconnect/Ready to notify)
* @param packet_type
* @param channel
* @param packet
* @param size
*/
void packet_handler(
uint8_t p_packet_type, uint16_t p_channel, uint8_t *p_packet_ptr, uint16_t p_size)
{
UNUSED(p_channel);
UNUSED(p_size);
if (p_packet_type != HCI_EVENT_PACKET) {
return;
}
auto &l_context = get_friable_context();
bd_addr_t l_pairing_partner = { 0 };
hci_con_handle_t l_con_handle = -1;
bd_addr_t l_addr = { 0 };
bd_addr_type_t l_addr_type = BD_ADDR_TYPE_UNKNOWN;
uint8_t l_status = 0;
// setup advertisements
uint16_t l_adv_int_min = ADVERTISEMENT_INTERVAL_MIN;
uint16_t l_adv_int_max = ADVERTISEMENT_INTERVAL_MAX;
uint8_t l_adv_type = 0;
bd_addr_t l_null_addr;
switch (hci_event_packet_get_type(p_packet_ptr)) {
case HCI_EVENT_META_GAP:
switch (hci_event_gap_meta_get_subevent_code(p_packet_ptr)) {
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
LOG_MSG("Connection complete\n");
l_con_handle = hci_subevent_le_connection_complete_get_connection_handle(p_packet_ptr);
l_context.connection.handle = l_con_handle;
l_context.connection.dirty = true;
update_state(friable::connection_state::ACTIVE);
break;
case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE_V1:
LOG_MSG("Enhanced Connection complete\n");
l_con_handle =
hci_subevent_le_enhanced_connection_complete_v1_get_connection_handle(p_packet_ptr);
l_context.connection.handle = l_con_handle;
l_context.connection.dirty = true;
update_state(friable::connection_state::ACTIVE);
break;
case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE_V2:
LOG_MSG("Enhanced Connection complete\n");
l_con_handle =
hci_subevent_le_enhanced_connection_complete_v2_get_connection_handle(p_packet_ptr);
l_context.connection.handle = l_con_handle;
l_context.connection.dirty = true;
update_state(friable::connection_state::ACTIVE);
break;
case GAP_SUBEVENT_LE_CONNECTION_COMPLETE:
LOG_MSG("LE Connection complete\n");
l_con_handle = gap_subevent_le_connection_complete_get_connection_handle(p_packet_ptr);
l_context.connection.handle = l_con_handle;
l_context.connection.dirty = true;
update_state(friable::connection_state::ACTIVE);
break;
default:
LOG_MSG("Unhandled GAP event %d\n", hci_event_gap_meta_get_subevent_code(p_packet_ptr));
break;
}
break;
case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
update_state(friable::connection_state::WAIT_CONFIRM);
if (l_context.callbacks.confirm_pairing == nullptr) {
LOG_MSG("No confirm_pairing callback set\n");
update_state(friable::error_e::INVALID_STATE);
break;
}
l_context.callbacks.confirm_pairing(
sm_event_numeric_comparison_request_get_passkey(p_packet_ptr));
break;
case SM_EVENT_PAIRING_STARTED:
sm_event_pairing_started_get_address(p_packet_ptr, l_pairing_partner);
LOG_MSG("Pairing started with %s\n", bd_addr_to_str(l_pairing_partner));
update_state(friable::connection_state::PAIRING);
break;
case SM_EVENT_PAIRING_COMPLETE:
switch (sm_event_pairing_complete_get_status(p_packet_ptr)) {
case ERROR_CODE_SUCCESS:
sm_event_pairing_complete_get_address(p_packet_ptr, l_addr);
LOG_MSG("Pairing complete with %s\n", bd_addr_to_str(l_addr));
l_con_handle = sm_event_pairing_complete_get_handle(p_packet_ptr);
memcpy(l_context.connection.address, l_addr, sizeof(bd_addr_t));
l_context.connection.handle = l_con_handle;
l_context.connection.dirty = true;
update_state(friable::connection_state::ACTIVE);
break;
case ERROR_CODE_CONNECTION_TIMEOUT:
LOG_MSG("Pairing failed, timeout\n");
update_state(friable::error_e::INVALID_STATE);
break;
case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
LOG_MSG("Pairing failed, disconnected\n");
update_state(friable::error_e::INVALID_STATE);
break;
case ERROR_CODE_AUTHENTICATION_FAILURE:
LOG_MSG("Pairing failed, authentication failure with reason = %u\n",
sm_event_pairing_complete_get_reason(p_packet_ptr));
update_state(friable::error_e::INVALID_STATE);
break;
default:
LOG_MSG("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(p_packet_ptr));
update_state(friable::error_e::INVALID_STATE);
break;
}
break;
case GATT_EVENT_QUERY_COMPLETE:
l_status = gatt_event_query_complete_get_att_status(p_packet_ptr);
switch (l_status) {
case ATT_ERROR_INSUFFICIENT_ENCRYPTION:
LOG_MSG("GATT Query failed, Insufficient Encryption\n");
break;
case ATT_ERROR_INSUFFICIENT_AUTHENTICATION:
LOG_MSG("GATT Query failed, Insufficient Authentication\n");
break;
case ATT_ERROR_BONDING_INFORMATION_MISSING:
LOG_MSG("GATT Query failed, Bonding Information Missing\n");
break;
case ATT_ERROR_SUCCESS:
LOG_MSG("GATT Query successful\n");
break;
default:
LOG_MSG("GATT Query failed, status 0x%02x\n",
gatt_event_query_complete_get_att_status(p_packet_ptr));
break;
}
break;
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(p_packet_ptr) != HCI_STATE_WORKING) {
return;
}
memset(l_null_addr, 0, sizeof(bd_addr_t));
gap_advertisements_set_params(
l_adv_int_min,
l_adv_int_max,
l_adv_type,
0,
l_null_addr,
CHANNEL_MAP,
0x00);
assert(sizeof(l_context.advertising_data.data) <= BLE_ADVERTISING_LIMIT); // ble limitation
gap_advertisements_set_data(
sizeof(l_context.advertising_data.data),
l_context.advertising_data.data);
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
// le_notification_enabled = 0;
LOG_MSG("HCI_EVENT_DISCONNECTION_COMPLETE\n");
update_state(friable::connection_state::DISCONNECTED);
break;
case ATT_EVENT_CAN_SEND_NOW:
process_notification(get_friable_context().connection.handle);
break;
default:
break;
}
}
```
```
/**
* @brief Initialize BTStack as peripheral device
*
*/
void sm_peripheral_setup(void)
{
l2cap_init();
// setup SM: Display only
sm_init();
// setup ATT server
att_server_init(profile_data, att_read_callback, att_write_callback);
// setup GATT Client
gatt_client_init();
// inform about BTstack state
v_sm_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&v_sm_event_callback_registration);
// register for SM events
v_sm_event_callback_registration.callback = &packet_handler;
sm_add_event_handler(&v_sm_event_callback_registration);
// register for ATT
att_server_register_packet_handler(packet_handler);
// LE Secure Pairing, Passkey entry initiator enter, responder (us) displays
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_YES_NO);
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_MITM_PROTECTION);
att_dump_attributes();
}
```
```
void generate_advertising_data(uint8_t *p_buffer_ptr,
size_t p_buffer_len,
device_info_t *p_device_info_ptr,
bd_addr_t p_mac,
bool p_force_pairing)
{
advertising_struct l_adv = {};
size_t l_offset = 0;
memcpy(l_adv.basic_flags, BASIC_ADVERTISING_FLAGS, sizeof(BASIC_ADVERTISING_FLAGS));
l_adv.name[0] = sizeof(l_adv.name) - 1;
l_adv.name[1] = BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME;
l_offset += 2;
//NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
strncpy((char *) l_adv.name + l_offset, DEVICE_BASE_NAME, sizeof(DEVICE_BASE_NAME) - 1);
l_offset += sizeof(DEVICE_BASE_NAME) - 1;
l_adv.name[l_offset++] = '_';
l_adv.name[l_offset++] = p_device_info_ptr->model;
l_adv.name[l_offset++] = '_';
//NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
strncpy((char *) l_adv.name + l_offset,
(const char *) p_device_info_ptr->serial_number,
sizeof(device_info_t::serial_number));
//NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
l_offset += sizeof(device_info_t::serial_number);
l_adv.manufacturer_data[0] = sizeof(l_adv.manufacturer_data) - 1;
l_adv.manufacturer_data[1] = FRIAMAT_ADVERTISING_FLAGS;
l_offset = 2;
memcpy(l_adv.manufacturer_data + l_offset, p_mac, sizeof(bd_addr_t));
l_offset += sizeof(bd_addr_t);
l_adv.manufacturer_data[l_offset++] = p_force_pairing;
memcpy(p_buffer_ptr, &l_adv, sizeof(l_adv));
log_advertising_data(&l_adv);
}
```