Introduction#

The Channel Sounding Demo showcases Bluetooth Channel Sounding functionality using the Simplicity Connect mobile app and Silicon Labs wireless devices. The demo supports the following operating modes:

  • Channel Sounding Demo (Phone as Initiator): The mobile app acts as the Channel Sounding (CS) Initiator and measures the distance to a CS Reflector device.

  • Channel Sounding Demo (Phone as Reflector): The mobile app acts as the Channel Sounding (CS) Reflector and serves as a Digital Key in a proximity-based lock and unlock application.

Channel Sounding Demo (Phone as Initiator)#

The Channel Sounding demo (Phone as Initiator) showcases how to measure the distance between a smartphone and a reflector device using Bluetooth Channel Sounding. This feature enables more accurate distance and direction measurements between Bluetooth devices compared to traditional RSSI-based methods.

The smartphone acts as the Channel Sounding (CS) Initiator, and the EFR32xG24 Development Kit (BRD2606A) acts as the Channel Sounding Reflector (CS Reflector). The initiator estimates the distance to the reflector, and the estimated values are also available on the reflector side.

Users interact with the Simplicity Connect mobile app on the smartphone to discover, connect to, and range with the reflector device. Once the devices are connected and the Channel Sounding session is established, distance measurements are displayed in real time.

Set Up the EFR32xG24 Development Kit as a Channel Sounding Reflector#

To use the EFR32xG24 Development Kit (BRD2606A) as a Channel Sounding Reflector:

  1. Connect the EFR32xG24 Development Kit (BRD2606A) to the PC.

  2. Open Simplicity Studio.

  3. Create the Bluetooth - SoC CS Reflector example project.

  4. Follow the configuration steps described in the project README file.

  5. Build and flash the project to the development kit.

mobile app screenshotmobile app screenshot

After programming is complete, the board starts advertising as a Channel Sounding Reflector and is ready to connect to the Simplicity Connect mobile app.

Set Up the Simplicity Connect Mobile App as a CS Initiator#

  1. Open the Simplicity Connect mobile app on your smartphone.

  2. Tap the Demo icon in the navigation bar.

  3. Locate the Channel Sounding demo tile and tap it.

    mobile app screenshotmobile app screenshot

  4. In the mode selection dialog, select Phone as Initiator.

    In this mode, the mobile app acts as the Channel Sounding (CS) Initiator and measures the distance to a paired EFR32xG24 Development Kit acting as the CS Reflector.

    mobile app screenshotmobile app screenshot

  5. In the device selection window, select the Channel Sounding Reflector device to connect to.

    mobile app screenshotmobile app screenshot

    Note: If the smartphone does not support Bluetooth Channel Sounding, an alert appears when you tap the Channel Sounding demo.

    mobile app screenshotmobile app screenshot

  6. After the smartphone connects to the Channel Sounding Reflector (CS Reflector), the app redirects you to the Channel Sounding home screen and starts the Bluetooth pairing process. Tap Pair in the pairing dialog to complete pairing and bonding with the reflector device.

    mobile app screenshotmobile app screenshot

  7. After pairing and bonding are complete, the CS Reflector starts sending Channel Sounding measurement data to the smartphone. The mobile app calculates and displays the measured distance.

    mobile app screenshotmobile app screenshot

  8. Tap Start/Stop in the top-right corner of the screen to start or stop Channel Sounding operations.

  9. To modify the Channel Sounding configuration, tap the Settings icon in the top-right corner of the screen.

    Note: If the smartphone has already saved the Channel Sounding Reflector in the connected devices list, the app prompts you to remove the device from the phone settings before continuing.

    mobile app screenshotmobile app screenshot

  10. From the Configuration screen, modify the following settings:

    • Measure Frequency – Selects the Bluetooth signal frequency range used for Channel Sounding measurements. The default value is High.

    • Measurement Count – Specifies the number of measurements collected during Channel Sounding operations. The default value is 0.

  11. Tap Save to apply the selected settings. The configuration remains in effect until you change it.

  12. Tap Reset to restore the default configuration values.

Kalman Filter#

The Channel Sounding demo (Phone as Initiator) includes a Kalman Filter that smooths raw BLE distance measurements to improve measurement stability and reduce the effect of noise. The filter is enabled by default and continuously processes Channel Sounding measurement data.

The measurement screen displays both the raw and filtered distance values:

  • Raw Distance (red): The unprocessed distance reported by the Bluetooth Channel Sounding API.

  • Filtered (green): The distance estimate after Kalman filtering.

mobile app screenshotmobile app screenshot

Configure the Kalman Filter#

To configure the Kalman filter:

  1. Tap the Settings icon in the top-right corner of the measurement screen.

  2. In the Configuration screen, locate the Kalman Filter section.

  3. Select the desired Filtering Level from the drop-down list.

    mobile app screenshotmobile app screenshot

The Filtering Level setting controls how aggressively the Kalman filter smooths distance measurements. Lower filtering levels provide faster response to distance changes, while higher filtering levels provide greater measurement stability by reducing noise and fluctuations.

The following table describes the available filtering levels.

Level Description Behavior When to Use
Light (fast response) Minimal smoothing, fastest reaction to distance changes. Best for dynamic scenarios where quick tracking is needed. Lower latency, more noise visible. When you need the filtered distance to track rapid movements closely.
Medium (balanced, default) Balanced trade-off between smoothing and responsiveness. Recommended for most use cases. Moderate latency, good stability. Provides a good balance between measurement stability and response time.
High (maximum smoothing) Maximum noise reduction with aggressive outlier rejection. Best for stationary or slow-moving scenarios where stability is critical. Higher latency, smoothest output. To get the most stable distance readings.

Save or Reset Configuration#

After selecting a filtering level:

  • Tap Save to apply and retain the selected filtering level. The configuration persists across app sessions.

  • Tap Reset to restore all settings, including the filtering level, to their default values.

The Kalman filter includes adaptive outlier rejection to reduce the impact of noisy or unexpected measurements. When a raw measurement deviates significantly from the current distance estimate, the filter automatically reduces the influence of that measurement instead of discarding it completely. This helps prevent sudden spikes from affecting the filtered distance while still allowing gradual distance changes to be tracked.

Note: Distance measurements may become less accurate when the smartphone is very close to the Channel Sounding Reflector. In this situation, a warning banner appears at the bottom of the screen:

"Your phone is very close to the reflector. This affects the accuracy of distance measurements. Please move slightly away to continue."

mobile app screenshotmobile app screenshot

Troubleshoot Distance Measurement Issues#

Issue Resolution
Filtered distance seems slow to react Switch to Light - Fast Response filtering level in the Configuration screen.
Distance readings are noisy Switch to High - Maximum Smoothing filtering level for more aggressive smoothing.
Proximity warning appears Move the smartphone farther from the reflector. Measurements below ~0.2 m may be unreliable.
Raw and filtered values differ significantly This is expected, the filter is smoothing out noise and rejecting outlier measurements. The Filtered (Kalman) value is the recommended reading.

Channel Sounding Demo (Phone as Reflector)#

The Channel Sounding demo demonstrates a proximity-based digital lock and unlock use case in which a Google Pixel smartphone functions as the Channel Sounding (CS) Reflector and serves as a Digital Key. In this mode, the EFR32xG24 Development Kit (BRD2606A) functions as the CS Initiator and acts as the door lock.

To implement this demonstration, modify the CS Initiator project (bt_cs_soc_initiator) as follows:

  1. Follow all five steps in the Setup of CS Reflector as a Central Device section.

  2. To ensure compatibility with Google Pixel devices, follow the two steps in the Pixel Device Compatibility section given on the same page as mentioned in the above point number 1.

  3. Modify the files in the bt_cs_soc_initiator project as described in the following steps.

  4. Open the config/app_config.h file and make the following changes to enable bonding and configure the proximity lock macros.

    
    --- a/config/app_config.h
    +++ b/config/app_config.h
    @@ -80,13 +80,13 @@
    
     // <q ALLOW_BONDING> Allow bonding
     // <i> Default: 0
     #ifndef ALLOW_BONDING
    -#define ALLOW_BONDING                 0
    +#define ALLOW_BONDING                 1
     #endif // ALLOW_BONDING
    
     // <q DELETE_BONDINGS_ON_STARTUP> Deletes all bondings on startup
     // <i> Default: 0
     #ifndef DELETE_BONDINGS_ON_STARTUP
    -#define DELETE_BONDINGS_ON_STARTUP    0
    +#define DELETE_BONDINGS_ON_STARTUP    1
     #endif // DELETE_BONDINGS_ON_STARTUP
     
     // <q ALLOW_DEBUG_KEYS> Allow debug keys
     @@ -116,6 +116,21 @@
     #define CS_APP_PASSKEY                 1234
     #endif // CS_APP_PASSKEY
    
     +// <o CS_INITIATOR_GATT_MAX_MTU> GATT MTU for RAS ranging data
     +// <i> Lab workaround for some Android reflectors (e.g. Pixel): use 245.
     +// <i> Default: 245
     +#ifndef CS_INITIATOR_GATT_MAX_MTU
     +#define CS_INITIATOR_GATT_MAX_MTU      245
     +#endif
     +
     +// <q CS_INITIATOR_PREFER_CS_CONFIG_BEFORE_SECURITY> CS capabilities before SM security
     +// <i> When set, sl_bt_cs_read_remote_supported_capabilities runs before
     +// <i> sl_bt_sm_increase_security on the first connection_parameters events.
     +// <i> Improves interop with Android CS reflectors (e.g. Pixel). Default: 1
     +#ifndef CS_INITIATOR_PREFER_CS_CONFIG_BEFORE_SECURITY
     +#define CS_INITIATOR_PREFER_CS_CONFIG_BEFORE_SECURITY 1
     +#endif
     +
     // <o CS_APP_CAPABILITY> Capabilities
     // <i> These values define the security management related I/O capabilities
     // <i> supported by the device.
     @@ -129,6 +144,32 @@
     #define CS_APP_CAPABILITY              sl_bt_sm_io_capability_noinputnooutput
     #endif // CS_APP_CAPABILITY
     
    
    +// <o CUSTOM_GATT_PROXIMITY_THRESHOLD_MM> Proximity LOCK/UNLOCK threshold in mm
    +// <i> Distance below this -> UNLOCK (0x00). Distance above -> LOCK (0x01).
    +// <i> Note: Channel Sounding is unreliable below ~300 mm due to near-field effects.
    +// <i> Default: 1000 (1 m)
    +#ifndef CUSTOM_GATT_PROXIMITY_THRESHOLD_MM
    +#define CUSTOM_GATT_PROXIMITY_THRESHOLD_MM     1000U
    +#endif
    +
    +// <o CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM> Hysteresis around the threshold in mm
    +// <i> Prevents rapid LOCK/UNLOCK flapping when the distance hovers around the
    +// <i> threshold. State changes only when distance crosses
    +// <i> threshold +/- hysteresis. Default: 100 (10 cm)
    +#ifndef CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM
    +#define CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM    100U
    +#endif
    +
    +// <o CUSTOM_GATT_MIN_LIKELINESS_X100> Minimum likeliness (x100) to act on a sample
    +// <i> Samples below this likeliness are ignored to avoid bogus state changes
    +// <i> from low-quality measurements. Range: 0..100 (0.00..1.00). Default: 30
    
    
    +#ifndef CUSTOM_GATT_MIN_LIKELINESS_X100
    +#define CUSTOM_GATT_MIN_LIKELINESS_X100        30U
    +#endif
    +
    +#define CUSTOM_GATT_PROXIMITY_UNLOCK           0x00U
    +#define CUSTOM_GATT_PROXIMITY_LOCK             0x01U
    +
    // <<< end of configuration section >>>
    
    #endif // APP_CONFIG_H
    
  5. Open config/cs_initiator_config.h and update the CS algorithm and antenna settings to ensure stable measurements when used with Pixel phones.

    
    --- a/config/cs_initiator_config.h
    +++ b/config/cs_initiator_config.h
    
    @@ -297,7 +297,7 @@
    // <i> The preferred peer-ordered antenna elements to be used by the remote device
    // <i> for the antenna configuration denoted by the tone antenna config selection.
    // <i> Default: 1
    -#define CS_INITIATOR_DEFAULT_PREFERRED_PEER_ANTENNA  1
    +#define CS_INITIATOR_DEFAULT_PREFERRED_PEER_ANTENNA  3
    
    // <o CS_INITIATOR_ANTENNA_OFFSET> Specify antenna offset
    // <0=> Wireless antenna offset
    
    
  6. Open config/sl_iostream_eusart_vcom_config.h and disable UART hardware flow control on VCOM to ensure that log output is displayed correctly on the board terminal.

    
    --- a/config/sl_iostream_eusart_vcom_config.h
    +++ b/config/sl_iostream_eusart_vcom_config.h
    @@ -65,7 +65,7 @@
    // <SL_IOSTREAM_EUSART_UART_FLOW_CTRL_CTS_RTS=> CTS/RTS
    // <SL_IOSTREAM_EUSART_UART_FLOW_CTRL_SOFT=> Software Flow control (XON/XOFF)
    // <i> Default: SL_IOSTREAM_EUSART_UART_FLOW_CTRL_NONE
    -#define SL_IOSTREAM_EUSART_VCOM_FLOW_CONTROL_TYPE    SL_IOSTREAM_EUSART_UART_FLOW_CTRL_CTS_RTS
    +#define SL_IOSTREAM_EUSART_VCOM_FLOW_CONTROL_TYPE    SL_IOSTREAM_EUSART_UART_FLOW_CTRL_NONE
    
    // <o SL_IOSTREAM_EUSART_VCOM_RX_BUFFER_SIZE> Receive buffer size
    // <i> Default: 32
    
  7. Open trace.c and modify it to redirect application logs to the BGAPI trace I/O stream when UART logging is disabled.

    
    --- a/trace.c
    +++ b/trace.c
    @@ -61,7 +61,9 @@
    
    void trace_init(void)
    {
    +#if !CS_INITIATOR_UART_LOG
      app_log_iostream_set(iostream_bgapi_trace_handle);
    +#endif
        #if (ALWAYS_INIT_TRACE == 0)
        if (!is_trace_requested()) {
      return;
    
  8. Open the config/btconf/gatt_configuration.btconf file in text view, as shown in the screenshot. Then add a custom GATT Lock service with UUID 0xAABB. Within this service, create a CSLock characteristic with UUID 0xAABB and add a CCCD descriptor (UUID 0x2902) to enable notifications. Configure the characteristic to transmit LOCK and UNLOCK status notifications, as shared below.

    mobile app screenshotmobile app screenshot

    Add the Lock service (insert before </gatt>)

    
    + <!--Lock-->
    + <service advertise="false" id="custom_service" name="Lock"
    +         requirement="mandatory" sourceId=""
    +         type="primary" uuid="AABB">
    +
    +  <!--Cs_lock-->
    +  <characteristic const="false" id="custom_characteristic"
    +                 name="Cs_lock" sourceId="" uuid="BBCC">
    +    <value length="1" type="hex" variable_length="false">00</value>
    +    <properties>
    +      <read  authenticated="false" bonded="false" encrypted="false"/>
    +      <notify authenticated="false" bonded="false" encrypted="false"/>
    +    </properties>
    +
    +    <!--Client Characteristic Configuration (CCCD)-->
    +    <descriptor const="false" discoverable="true"
    +               id="custom_characteristic_client_characteristic_configuration_0"
    +                name="Client Characteristic Configuration"
    +                sourceId="org.bluetooth.descriptor.gatt.client_characteristic_configuration"
    +                uuid="2902">
    +      <properties>
    +        <read  authenticated="false" bonded="false" encrypted="false"/>
    +        <write authenticated="false" bonded="false" encrypted="false"/>
    +      </properties>
    +      <value length="2" type="hex" variable_length="false"/>
    +    </descriptor>
    +  </characteristic>
    +</service>
    </gatt>
    
    
  9. Open app.c and implement the following changes:

    • Per-connection state management

    • Proximity LOCK/UNLOCK logic

    • CCCD subscription tracking

    • Pixel-friendly handshake (CS capabilities before SM security)

    • MTU configuration through CS_INITIATOR_GATT_MAX_MTU

    
    --- a/app.c
    +++ b/app.c
    @@ -35,6 +35,7 @@
    #include "sl_bluetooth.h"
    #include "sl_component_catalog.h"
    #include "app_assert.h"
    +#include "gatt_db.h"
    
    // app content
    #include "sl_main_init.h"
    @@ -115,7 +116,12 @@
    bool measurement_arrived;
    bool measurement_progress_changed;
    bool read_remote_capabilities;
    +  bool security_increased;
    uint8_t number_of_measurements;
    +  // Proximity LOCK/UNLOCK GATT notification state
    +  bool proximity_notify_enabled;    // CCCD subscription state
    +  uint8_t proximity_last_state;     // Last value notified (0x00 UNLOCK / 0x01 LOCK)
    +  bool proximity_state_valid;       // True after we have notified at least once
    } cs_initiator_instances_t;
    
    
    
    // -----------------------------------------------------------------------------
    @@ -136,11 +142,16 @@
                cs_error_event_t err_evt,
                sl_status_t sc);
    static sl_status_t get_instance_number(uint8_t conn_handle, uint8_t *instance_num);
    +static sl_status_t save_connection(uint8_t conn_handle);
    static void check_cli_values(void);
    static sl_status_t create_new_initiator_instance(uint8_t conn_handle);
    static void delete_initiator_instance(uint8_t conn_handle);
    static void app_timer_callback(app_timer_t *timer, void *data);
    static void check_supported_capabilities(const sl_bt_msg_t *evt);
    +static void proximity_lock_update(uint8_t instance_num,
    +                                  uint8_t conn_handle,
    +                                  float distance_m,
    +                                  float likeliness);
    
    // -----------------------------------------------------------------------------
    // Static variables
    @@ -174,7 +185,11 @@
     cs_initiator_instances[i].measurement_arrived = false;
     cs_initiator_instances[i].measurement_progress_changed = false;
     cs_initiator_instances[i].read_remote_capabilities = false;
    +    cs_initiator_instances[i].security_increased = false;
     cs_initiator_instances[i].number_of_measurements = 0u;
    +    cs_initiator_instances[i].proximity_notify_enabled = false;
    +    cs_initiator_instances[i].proximity_last_state = CUSTOM_GATT_PROXIMITY_LOCK;
    +    cs_initiator_instances[i].proximity_state_valid = false;
    }
    security_set_config_flags();
    
    @@ -314,6 +329,11 @@
            cs_initiator_instances[i].measurement_progress.progress_percentage,
            rtl_config.algo_mode,
            initiator_config.cs_main_mode);
    +    // Proximity LOCK/UNLOCK notification on the custom characteristic
    +    proximity_lock_update(i,
    +                          cs_initiator_instances[i].conn_handle,
    +                          cs_initiator_instances[i].measurement_mainmode.distance_filtered,
    
    +           cs_initiator_instances[i].measurement_mainmode.likeliness);
    } else if (cs_initiator_instances[i].measurement_progress_changed) {
        // write measurement progress to the display without changing the last valid
        // measurement results
    @@ -442,6 +462,25 @@
        }
    }
    return SL_STATUS_FAIL;
    +}
    + 
    +/***************************************************************************
    + * Reserve a connection slot before the CS initiator instance exists.
    + * Called from BLE_PEER_MANAGER_ON_CONN_OPENED_PERIPHERAL so that the very
    + * first connection_parameters event already has a valid slot to drive
    + * the CS-caps / SM-security handshake against (Pixel-friendly order).
    + ******************************************************************************/
    +static sl_status_t save_connection(uint8_t conn_handle)
    +{
    +  for (uint8_t i = 0u; i < CS_INITIATOR_MAX_CONNECTIONS; i++) {
    +    if (cs_initiator_instances[i].conn_handle == SL_BT_INVALID_CONNECTION_HANDLE) {
    +      cs_initiator_instances[i].conn_handle = conn_handle;
    +      cs_initiator_instances[i].read_remote_capabilities = false;
    +      cs_initiator_instances[i].security_increased = false;
    +      return SL_STATUS_OK;
    +    }
    +  }
    +  return SL_STATUS_FULL;
    }
    
    /*******************************************************************************
    @@ -771,9 +810,103 @@
        cs_initiator_instances[i].measurement_arrived = false;
        cs_initiator_instances[i].measurement_progress_changed = false;
        cs_initiator_instances[i].read_remote_capabilities = false;
    +   cs_initiator_instances[i].security_increased = false;
    +   cs_initiator_instances[i].proximity_notify_enabled = false;
    +   cs_initiator_instances[i].proximity_last_state = CUSTOM_GATT_PROXIMITY_LOCK;
    +    cs_initiator_instances[i].proximity_state_valid = false;
        num_reflector_connections--;
        break;
    }
    +  }
    +}
    + 
    +/*******************************************************************************
    + * Proximity-based LOCK/UNLOCK notification
    + *
    + * Sends a 1-byte notification on the custom characteristic (UUID 0xBBCC) when
    + * the proximity state crosses the configured threshold:
    + *   distance < (threshold - hysteresis) -> CUSTOM_GATT_PROXIMITY_UNLOCK (0x00)
    + *   distance > (threshold + hysteresis) -> CUSTOM_GATT_PROXIMITY_LOCK   (0x01)
    + *
    + * Notifications are only sent when the value actually changes (edge-triggered).
    + * Low-likeliness samples are ignored to avoid flapping on bad measurements.
    + ******************************************************************************/
    +static void proximity_lock_update(uint8_t instance_num,
    +                                 uint8_t conn_handle,
    +                                  float distance_m,
    +                                  float likeliness)
    +{
    +// Reject low-quality samples
    +if (likeliness * 100.0f < (float)CUSTOM_GATT_MIN_LIKELINESS_X100) {
    +   return;
    + }
    + 
    + uint32_t distance_mm = (uint32_t)(distance_m * 1000.0f);
    + const uint32_t hi = (uint32_t)CUSTOM_GATT_PROXIMITY_THRESHOLD_MM
    +                   + (uint32_t)CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM;
    + const uint32_t lo = ((uint32_t)CUSTOM_GATT_PROXIMITY_THRESHOLD_MM
    +                  > (uint32_t)CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM)
    
    
    +            ? ((uint32_t)CUSTOM_GATT_PROXIMITY_THRESHOLD_MM
    +               - (uint32_t)CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM)
    +            : 0U;
    +
    + uint8_t prev = cs_initiator_instances[instance_num].proximity_last_state;
    + uint8_t next = prev;
    +
    + if (distance_mm < lo) {
    +   next = CUSTOM_GATT_PROXIMITY_UNLOCK;
    + } else if (distance_mm > hi) {
    +   next = CUSTOM_GATT_PROXIMITY_LOCK;
    + }
    + // In the hysteresis band: keep previous state.
    +
    + bool first = !cs_initiator_instances[instance_num].proximity_state_valid;
    + if (!first && next == prev) {
    +   // No edge crossed; nothing to send.
    +   return;
    + }
    +
    + // Persist value into the local GATT DB so a remote READ returns the latest.
    + sl_status_t sc = sl_bt_gatt_server_write_attribute_value(
    +   gattdb_custom_characteristic, 0, 1, &next);
    + if (sc != SL_STATUS_OK) {
    +   log_error(APP_INSTANCE_PREFIX "GATT write_attribute_value failed: 0x%04lx" NL,
    +             conn_handle, (unsigned long)sc);
    + }
    
    
    +    cs_initiator_instances[instance_num].proximity_last_state = next;
    +    cs_initiator_instances[instance_num].proximity_state_valid = true;
    +
    +    const char *state_str =
    +      (next == CUSTOM_GATT_PROXIMITY_UNLOCK) ? "UNLOCK" : "LOCK";
    +
    +    // Notify only if the client (phone) has subscribed via CCCD.
    +    if (!cs_initiator_instances[instance_num].proximity_notify_enabled) {
    +      log_info(APP_INSTANCE_PREFIX
    +               "[PROXIMITY] State change %s -> 0x%02X at %lu mm "
    +               "(NOT notified: client has not subscribed to CCCD)" NL,
    +               conn_handle, state_str, next, (unsigned long)distance_mm);
    +      return;
    +    }
    +
    +    log_info(APP_INSTANCE_PREFIX
    +             "[PROXIMITY] Sending %s (0x%02X) at %lu mm "
    +             "(threshold=%u mm, hyst=%u mm)..." NL,
    +             conn_handle, state_str, next, (unsigned long)distance_mm,
    +             (unsigned)CUSTOM_GATT_PROXIMITY_THRESHOLD_MM,
    +             (unsigned)CUSTOM_GATT_PROXIMITY_HYSTERESIS_MM);
    +
    +    sc = sl_bt_gatt_server_send_notification(conn_handle,
    +                                            gattdb_custom_characteristic,
    +                                            1, &next);
    +
    +    if (sc == SL_STATUS_OK) {
    +      log_info(APP_INSTANCE_PREFIX
    +               "[PROXIMITY] >>> NOTIFY sent: %s (0x%02X) handle=%u <<<" NL,
    +               conn_handle, state_str, next, gattdb_custom_characteristic);
    +    } else {
    +      log_error(APP_INSTANCE_PREFIX
    +                "[PROXIMITY] send_notification FAILED: %s (0x%02X) sc=0x%04lx" NL,
    +                conn_handle, state_str, next, (unsigned long)sc);
        }
      }
    
    
    
    @@ -943,26 +1076,68 @@    
        break;
    }
    -   case sl_bt_evt_connection_parameters_id:
    -     sc = get_instance_number(evt->data.evt_connection_parameters.connection, &instance_num);
    
    
    
    
    - // Initiator instance not created yet
    + case sl_bt_evt_connection_parameters_id: {
    +   uint8_t connection = evt->data.evt_connection_parameters.connection;
    +
    +   sc = get_instance_number(connection, &instance_num);
    +   // Wait until peer manager has reserved a connection slot via save_connection().
    +   if (sc != SL_STATUS_OK) {
    -     if (evt->data.evt_connection_parameters.security_mode != sl_bt_connection_model_level1) {
    -       sc = sl_bt_cs_read_remote_supported_capabilities(evt->data.evt_connection_parameters.connection);
    -       app_assert_status(sc);
    +     break;
    +   }
    +#if CS_INITIATOR_PREFER_CS_CONFIG_BEFORE_SECURITY
    +   // Pixel-friendly ordering: read CS capabilities BEFORE bringing up SM security.
    +   if (!cs_initiator_instances[instance_num].read_remote_capabilities) {
    +     log_info(APP_INSTANCE_PREFIX "Reading CS remote capabilities (interop order)..." NL,
    +              connection);
    +     sc = sl_bt_cs_read_remote_supported_capabilities(connection);
    +     if (sc == SL_STATUS_OK || sc == SL_STATUS_BUSY) {
    +       cs_initiator_instances[instance_num].read_remote_capabilities = true;
    +     } else {
    -       sc = sl_bt_sm_increase_security(evt->data.evt_connection_parameters.connection);
    -       app_assert_status(sc);
    +       log_error(APP_INSTANCE_PREFIX "Failed to read CS capabilities, sc=0x%lx" NL,
    +                 connection, (unsigned long)sc);
            }
    -     }
    -   break;
    
    

Set Up the EFR32xG24 Development Kit as a Door Lock#

  1. After applying all required modifications to the CS Initiator project, build and flash the firmware to the EFR32xG24 Development Kit (BRD2606A).

  2. Power on the board.

  3. Verify that the device advertises with the name Silabs Example.

  4. Verify that the firmware exposes the following GATT attributes:

    • Digital Key Service (UUID: 0xAABB)

    • Lock State Characteristic (UUID: 0xBBCC)

The Digital Key service enables the mobile app to subscribe to lock-state notifications and display the current lock status.

Set Up the Simplicity Connect Mobile App as a Digital Key#

  1. Open the Simplicity Connect mobile app on your Pixel smartphone.

  2. Tap the Demo icon in the navigation bar.

  3. Locate the Channel Sounding demo tile and tap it.

    mobile app screenshotmobile app screenshot

  4. In the mode selection dialog, select Phone as Reflector.

    • Phone as Initiator – The mobile app measures the distance to a paired CS Reflector.

    • Phone as Reflector – The EFR32xG24 Development Kit functions as a door lock, measures the distance to the smartphone, and unlocks when the smartphone is within range.

    mobile app screenshotmobile app screenshot

  5. In the device selection window, select the door lock device advertising as Silabs Example.

    mobile app screenshotmobile app screenshot

    Note: If no devices appear, ensure that the development kit is powered on, running the CS Initiator firmware, and within Bluetooth range. Tap Refresh Scan to search again.

  6. In the Pairing with Door Lock dialog, complete the pairing process.

    • Device Found – The app connects to the selected door lock.

    • Pairing & Bonding – Security keys are exchanged and the devices are bonded.

    • Channel Sounding Setup – The smartphone is configured as the CS Reflector.

    • Digital Key Ready – GATT notifications are enabled for lock-state updates.

    When the system Bluetooth pairing dialog appears, tap Pair to complete bonding.

    mobile app screenshotmobile app screenshot

  7. When all setup steps complete successfully and the status displays Digital Key Ready!, tap Go to Digital Key Dashboard.

    The app starts a foreground service so that the Digital Key session remains active while you use other screens or lock the phone.

    mobile app screenshotmobile app screenshot

Use the Digital Key Dashboard#

The Digital Key Dashboard displays the connection status of the paired door lock and the current lock state reported by the firmware through GATT notifications. The padlock icon indicates whether the door lock considers the Digital Key to be within range.

View the Lock Status#

Move toward the door lock to enter the unlock zone. When Channel Sounding determines that the smartphone is within the configured range, the door lock reports an unlocked state (0x00) and the dashboard displays an unlocked padlock.

mobile app screenshotmobile app screenshot

Move away from the door lock to leave the unlock zone. When the smartphone moves outside the configured range, the door lock reports a locked state (0x01) and the dashboard displays a locked padlock.

mobile app screenshotmobile app screenshot

Run the Digital Key in the Background#

After pairing completes, a foreground service maintains the GATT connection and Digital Key notifications in the background. A persistent notification displays the current lock state.

Leaving the dashboard does not end the Digital Key session. You can return to the Channel Sounding demo and reopen the dashboard while the bond remains valid.

mobile app screenshotmobile app screenshot

Handle Connection Loss#

If the door lock is reset, the pairing is removed from the phone's Bluetooth settings, or the session is otherwise permanently lost, the app displays a Connection Lost dialog and returns you to the demo screen. Repeat the pairing procedure to re-establish the Digital Key session.

mobile app screenshotmobile app screenshot

Troubleshoot Digital Key Issues#

Issue Resolution
No door locks found during scan Verify that the development kit is flashed with the CS Initiator firmware, powered on, and advertising as Silabs Example. Move closer and tap Refresh Scan.
Bonding or pairing fails Remove the device from the phone's Bluetooth settings, power-cycle the development kit, and repeat the pairing process.
Digital Key GATT not found (0xAABB / 0xBBCC) Verify that the firmware exposes the Digital Key service and Lock State characteristic. Stay near the door lock and retry from the dashboard.
Dashboard shows disconnected Move back within range. The app automatically reconnects when the device becomes available.
Lock state remains unknown Wait for service discovery, notification configuration, and Channel Sounding setup to complete. Retry if setup fails.
Session lost dialog appears Re-pair the smartphone and door lock after resetting the board or removing the bond.