BLE#
The kite ble command provides non-connectable BLE beacon advertising. You can
set a device name, load a custom manufacturer data payload, start advertising,
and update the payload live -- all from the shell. This is a minimal
introduction to Zephyr's Bluetooth API without the complexity of connections,
GATT services, or pairing.
Source: lib/kite/ble/kite_ble.c
Commands#
| Command | Description |
|---|---|
kite ble start [name] |
Start advertising (optional name, max 8 chars, default "kite") |
kite ble stop |
Stop advertising |
kite ble data <bytes...> |
Set manufacturer data payload (hex bytes, max 22) |
Examples:
uart:~$ kite ble start myboard
Advertising as 'myboard'
uart:~$ kite ble data 01 02 03 FF
Updated 4 mfr byte(s), advertising
uart:~$ kite ble stop
Advertising stopped
You can verify the advertisements with any BLE scanner app (e.g. nRF Connect) on your phone. Look for the device name and the manufacturer data field.
Kconfig#
CONFIG_BT=y # enable Bluetooth stack
CONFIG_BT_BROADCASTER=y # enable advertising role
CONFIG_CSSE4011_SHELL_BLE=y
Unlike the other subsystems that use select to pull in drivers, the BLE
Kconfig uses depends on BT and depends on BT_BROADCASTER. This is because
the Bluetooth stack is large and has its own configuration requirements --
force-selecting it could cause unexpected build issues, so the application must
opt in explicitly.
Code Walkthrough#
Advertising Data Structure#
BLE advertising packets are built from bt_data structs, each representing
one AD (Advertising Data) element:
static char dev_name[MAX_NAME_LEN + 1] = "kite";
static uint8_t mfr_buf[2 + MAX_USER_MFR_LEN] = {MFR_COMPANY_ID_LO,
MFR_COMPANY_ID_HI}; // (1)!
static uint8_t mfr_len = 2;
static void build_ad(struct bt_data ad[2])
{
ad[0].type = BT_DATA_NAME_COMPLETE; // (2)!
ad[0].data = dev_name;
ad[0].data_len = strlen(dev_name);
ad[1].type = BT_DATA_MANUFACTURER_DATA; // (3)!
ad[1].data = mfr_buf;
ad[1].data_len = mfr_len;
}
- The manufacturer data field starts with a 2-byte company ID.
0xFFFFis the BLE SIG "test/unassigned" company ID -- safe to use for development. The user's payload bytes are appended after these two bytes. BT_DATA_NAME_COMPLETE-- the device's human-readable name, visible in scanner apps.BT_DATA_MANUFACTURER_DATA-- a free-form payload. This is how beacons typically transmit sensor data, status, or identifiers without establishing a connection.
Lazy Bluetooth Init#
The Bluetooth stack is initialised on first use:
static bool bt_ready;
static int ensure_bt_ready(const struct shell *sh)
{
if (bt_ready) {
return 0;
}
int ret = bt_enable(NULL); // (1)!
if (ret < 0) {
shell_error(sh, "BT init failed: %d", ret);
return ret;
}
bt_ready = true;
return 0;
}
bt_enableinitialises the Bluetooth controller and host stack. This takes a few hundred milliseconds and can only be called once. PassingNULLmakes it synchronous (blocks until ready). You could pass a callback for asynchronous init, but for a shell command blocking is fine.
Starting Advertising#
struct bt_data ad[2];
build_ad(ad);
ret = bt_le_adv_start(BT_LE_ADV_NCONN, ad, ARRAY_SIZE(ad), NULL, 0); // (1)!
BT_LE_ADV_NCONNconfigures non-connectable, non-scannable advertising. The device broadcasts its advertisement packets but rejects connection requests. The last two arguments (NULL, 0) are for scan response data, which we don't use.
Live Payload Updates#
The data command can update the manufacturer payload while advertising is
active:
uint8_t *payload = &mfr_buf[2];
for (uint32_t i = 0; i < num_bytes; i++) {
uint32_t val = shell_strtoul(argv[i + 1], 16, &ret);
...
payload[i] = (uint8_t)val; // (1)!
}
mfr_len = 2 + num_bytes;
if (advertising) {
struct bt_data ad[2];
build_ad(ad);
ret = bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0); // (2)!
...
}
- User bytes are written after the 2-byte company ID prefix in
mfr_buf. bt_le_adv_update_dataswaps in the new advertising data without stopping and restarting the advertiser. The next advertisement packet will contain the updated payload.
If advertising hasn't started yet, the data is stored and will be included
when start is called.
Try It#
- Start advertising:
- Open nRF Connect (or similar) on your phone and scan. You should see "sensor1" in the device list.
- Set some payload data:
- In your scanner app, look at the manufacturer data field -- you should see
FF FF AA BB CC(company ID + your bytes).