-
Notifications
You must be signed in to change notification settings - Fork 28
Description
When multiple badges are nearby, they currently advertise with identical name/service/manufacturer data. Apps then “pick the first/strongest,” which is unreliable. We need a per-device unique ID that is readable and writable from the app, persists in NVM, and is visible in advertising for pre-connect filtering.
Requirements
- Unique Device ID
- Stored in non-volatile memory (flash/NVS).
- Default: derived from MCU unique ID (hashed/shortened) if unset.
- App can read and change the ID over GATT.
- Length: 6–16 bytes recommended (printable base32/base16 string or raw bytes).
- Advertising
-
Include the Device ID in either:
- Manufacturer Specific Data (MSD), or
- Service Data for the custom service (preferred).
-
Keep total ADV payload within 31 bytes (legacy ADV) or use extended ADV if available.
- Security (recommended)
- Writing Device ID should require an ownership token or at least an encrypted link (LE Secure Connections).
- Optional: simple provisioning step to set owner token once.
Proposed GATT Layout (Custom Service)
Custom Service UUID (128-bit):
b0ad0001-1a2b-4c3d-9e5f-112233445566
(example; generate final UUIDs in repo)
Characteristics:
-
Device ID Characteristic
- UUID:
b0ad0002-1a2b-4c3d-9e5f-112233445566
- Properties:
READ | WRITE
(optionallyWRITE_WITHOUT_RESPONSE
) - Max length: 16 bytes
- Format: bytes or ASCII (e.g., Base32 “BM-ABCDE1”)
- Persistence: write updates NVM; read returns current value
- Security: require encryption and/or validated ownership token to WRITE
- UUID:
-
Ownership Token (optional but recommended)
- UUID:
b0ad0003-1a2b-4c3d-9e5f-112233445566
- Properties:
WRITE
- Usage: app writes a pre-shared or first-time provisioned token; firmware validates before allowing Device ID writes (token stored securely in NVM)
- UUID:
-
Device Capabilities / Version (optional)
-
UUID:
b0ad0004-1a2b-4c3d-9e5f-112233445566
-
Properties:
READ
-
Payload example (TLV):
0x01
FW major.minor.patch0x02
features bitmask (e.g., LED, e-paper, UART, etc.)
-
Note: Standard Device Information Service (0x180A) is read-only and not suitable for a writable Device ID; keep ours in a custom service.
Advertising Payload (example)
Option A – Service Data (recommended):
-
AD Type: Service UUID (16/128) + Service Data
-
Service UUID:
b0ad0001-1a2b-4c3d-9e5f-112233445566
-
Service Data (up to ~24 bytes):
- Byte 0: Version (e.g.,
0x01
) - Byte 1: Length of Device ID (N)
- Bytes 2..(1+N): Device ID bytes (e.g., 8 bytes)
- Optional: TxPower or flags
- Byte 0: Version (e.g.,
Option B – Manufacturer Specific Data:
- Company ID: TBD (0xFFFF placeholder if none)
- Layout:
[ver][len][device_id…]
Keep device GAP name short (e.g., “BadgeMagic”) to save ADV space.
Example App Flow
-
First run (provisioning):
- Scan for ADV with our service UUID.
- Show list with Device ID suffix and RSSI.
- User selects device → connect → write Ownership Token (if used) → write friendly Device ID (e.g., “BM-LAB-01”).
- Save peripheral identifier (addr/UUID) for fast reconnect.
-
Next runs:
- Try direct reconnect using saved identifier; fallback to filtered scan by service UUID + Device ID in ADV/Service Data.
Acceptance Criteria
- Device advertises Service UUID and includes Device ID in Service Data or MSD.
- GATT service with readable/writable Device ID characteristic implemented.
- Writes persist in NVM and survive reset.
- (If enabled) Ownership token gating write access is implemented and documented.
- Sample code/API documented for Android, iOS, and Python (bleak).
- Backward-compat: if no ID in NVM, firmware generates default from MCU UID.
Reference Snippets (pseudo-code)
Firmware (C-like pseudo for read/write + NVM):
#define DEV_ID_MAX 16
static uint8_t dev_id[DEV_ID_MAX];
static uint8_t dev_id_len;
void load_dev_id_from_nvm(void) {
if (!nvm_read("dev_id", dev_id, &dev_id_len) || dev_id_len == 0) {
// derive from MCU UID (hash/truncate)
dev_id_len = 8;
hw_read_unique_id(dev_id, dev_id_len);
nvm_write("dev_id", dev_id, dev_id_len);
}
}
int on_dev_id_read(uint8_t *out, uint8_t *out_len) {
memcpy(out, dev_id, dev_id_len);
*out_len = dev_id_len;
return 0;
}
int on_dev_id_write(const uint8_t *in, uint8_t in_len) {
if (!is_authorized()) return ERR_UNAUTHORIZED;
if (in_len == 0 || in_len > DEV_ID_MAX) return ERR_LENGTH;
memcpy(dev_id, in, in_len);
dev_id_len = in_len;
nvm_write("dev_id", dev_id, dev_id_len);
update_adv_payload_with_dev_id(dev_id, dev_id_len);
return 0;
}
Android (filter by service + parse Service Data ID)
// After scanning, read Service Data for our custom service UUID and match Device ID.
// If multiple matches, show list with last 4 bytes of ID + RSSI.
iOS (retrieve by saved identifier; fallback to filtered scan)
// Use CBPeripheral.identifier for reconnect; if not found, scan for service UUID
// and verify Device ID by reading Service Data or GATT characteristic.
Python (bleak)
# Discover -> filter by service uuid -> match Device ID from advertisement metadata
# Connect -> read/write Device ID characteristic as needed.
Open Questions
- Finalize UUIDs (service and characteristics).
- Choose ID format (raw bytes vs printable string).
- Security level for writes: encryption only vs token-based auth.
- Company ID for MSD (if we standardize on MSD).
Impact
This enables reliable multi-device operation at workshops and events, improves UX (clear identity/personalization), and lays groundwork for secure ownership and provisioning.