Knocklace Collection — Brand Context, BLE Beacon Protocol & User Flow
Deep-dive into the Sprite/Coca-Cola Knocklace Collection: three physical locket models, RSSI-based distance estimation, BLE beacon protocol, and the full user flow from pairing to alarm.
Knocklace Collection — Brand Context, BLE Beacon Protocol & User Flow
Source: Reverse-engineering of
/Users/alefita/workdir/sprite-mobile/Version: 4.24.0 (JS) / 4.24.1 (native module)
1. Brand Context
1.1 Sprite and Coca-Cola
Knocklace Collection is a BLE wearable product line under the Sprite brand, owned by The Coca-Cola Company (TCCC). Sprite's global marketing identity centers on refreshment, youth culture, and irreverent humor — the brand's green (#40c965) is as iconic as Coca-Cola's red.
The app uses Coca-Cola's proprietary TCCC Unity typeface family across seven weights:
| Weight | Usage |
|---|---|
TCCC-UnityHeadline-Black | Primary headings, modal titles |
TCCC-UnityHeadline-Bold | Button labels, navigation |
TCCC-UnityHeadline-Medium | Secondary text |
TCCC-UnityHeadline-Regular | Body text |
TCCC-UnityHeadline-Light | Subtle labels |
TCCC-UnityCondensed-Bold | Compact UI elements |
TCCC-UnityCondensed-Medium | Dense information areas |
All UI text renders in uppercase, consistent with Sprite's bold visual identity. The color palette follows strict Sprite brand guidelines:
- Primary green:
#40c965 - Dark green:
#0d2814,#04240D,#133c1e - Background:
#000(black) - Text:
#fff(white)
1.2 The "Knocklace" Name
The name is a portmanteau: knock (proximity detection / knocking) + necklace (the wearable form factor) + collection (the product line). The concept is literal: when someone wearing a Knocklace "knocks" into your proximity radius, you get alerted.
1.3 The Social Use Case
The app functions as a proximity privacy alarm with tongue-in-cheek marketing. The notification copy reveals the intent:
EN: "Cease any potentially embarrassing activities immediately" PT: "Interromper imediatamente quaisquer atividades constrangedoras"
This is a party/social privacy tool. When someone wearing the Knocklace approaches your phone within the configured distance, the app warns you to close whatever you are looking at. The emergency action — opening a YouTube URL — provides plausible denuity ("I was just watching a video").
2. The Three Locket Models
2.1 Physical Variants
The remote config and codebase reveal three distinct physical locket models, each containing a BLE beacon:
| Model ID | Shape | Visual Asset | Remote Config Name | Description |
|---|---|---|---|---|
0 | Circle | animated-jewel-1.gif | "CIRCLE" | Classic round locket |
1 | Square | animated-jewel-3.gif | "SQUARE" | Geometric square locket |
2 | Oval | animated-jewel-2.gif | "OVAL" / "OVAL 2" | Elongated oval locket |
Each model has its own animated GIF asset rendered by the AnimatedLocket component:
interface AnimatedJewelComponentProps {
model?: 'circle' | 'square' | 'oval' | number; // 0=circle, 1=square, 2=oval
}
The mapping from model ID to asset:
const ASSET_MAP = {
0: require('./assets/animated-jewel-1.gif'), // circle
1: require('./assets/animated-jewel-3.gif'), // square
2: require('./assets/animated-jewel-2.gif'), // oval
};
2.2 Per-Device Calibration
Each physical locket broadcasts at slightly different power levels. The remote config compensates with per-device calibration:
{
"customConfigPerDevice": [
{
"customConfigEnabled": true,
"customName": "OVAL 2",
"jewelModel": 2,
"knownAs": "sprite6",
"rssiAt1Meter": -80,
"rssiPow": 0.8
}
]
}
| Parameter | Meaning | Default |
|---|---|---|
rssiAt1Meter | RSSI reading at exactly 1 meter (calibration point) | -79 dBm |
rssiPow | Path loss exponent (environment-dependent) | 1.0 |
knownAs | BLE identifier (iOS: device name; Android: MAC address) | — |
customName | Display name shown to user | — |
jewelModel | Physical shape (0/1/2) | — |
2.3 Development/Test Devices
The bundled fallback configs contain development device names that reveal the naming convention:
SPRITE8,SPRITE9,SPRITE10— numbered Sprite test deviceslucas-iot,maya-iot— developer-named IoT prototypesPAY4BRAIN— unknown, possibly a marketing stunt device
3. BLE Beacon Protocol
3.1 Scanning Configuration
The app uses a locally forked react-native-ble-plx (v3.1.2, from dotintent) with aggressive scanning parameters:
BleManager.startDeviceScan(null, {
allowDuplicates: true, // receive repeated advertisements
legacyScan: true, // Android legacy scan API
scanMode: ScanMode.LowLatency, // max scan frequency
callbackType: ScanCallbackType.AllMatches // report every match
});
allowDuplicates: true is critical — it means the BLE stack reports every advertisement from every device, not just the first discovery. This enables continuous RSSI polling without establishing a GATT connection.
3.2 Device Identification
The identification strategy differs by platform:
| Platform | Identification Method | Reason |
|---|---|---|
| iOS | device.name or device.localName | CoreBluetooth exposes BLE advertisement names reliably |
| Android | device.id (MAC address) | Android BLE does not reliably expose device names in scan results |
The DeviceCompanionClass singleton checks each scanned device against knownTags from the remote config:
// Pseudocode
isKnownDevice(device): boolean {
if (Platform.OS === 'ios') {
return knownTags.includes(device.name) || knownTags.includes(device.localName);
} else {
return knownTags.includes(device.id);
}
}
3.3 No GATT Connection
A key architectural distinction: the Knocklace app never establishes a GATT connection to the locket. It reads RSSI values purely from advertisement broadcasts. This means:
- No service discovery
- No characteristic read/write
- No pairing/bonding at the BLE level
- Lower power consumption on both sides
- The locket is a simple beacon, not a connected peripheral
3.4 Background Scanning
Background BLE scanning is handled natively on each platform:
| Platform | Mechanism | Implementation |
|---|---|---|
| iOS | BGTaskScheduler | Task ID: com.knocklace.BgTask.ScanningForDevices |
| Android | HeadlessJsTaskService | Foreground service with persistent notification: "Alerta Sprite / Knocklace Collection is running in background" |
The BleManager is initialized with restoreStateIdentifier: 'com.knocklace.ble' to survive app termination on iOS.
4. RSSI-Based Distance Estimation
4.1 The Path Loss Model
Distance is estimated from RSSI using the log-distance path loss model, a standard radio propagation model:
RSSI(d) = RSSI(d0) - 10 * n * log10(d / d0)
Solving for distance d given RSSI measurement and reference RSSI at d0 = 1m:
function calculateDistance(rssi: number, device: DeviceReference): number {
const txPower = device.rssiAt1Meter ?? -79; // RSSI at 1 meter
const ratio = txPower - rssi; // signal attenuation
const ratiof = Math.pow(10, (ratio * (device.rssiPow ?? 1)) / 10); // path loss
const rawVal = Math.sqrt(ratiof); // distance in meters
return rawVal;
}
| Parameter | Symbol | Source | Typical Value |
|---|---|---|---|
rssiAt1Meter | RSSI(d0) | Remote config per-device | -79 to -80 dBm |
rssiPow | n | Remote config per-device | 0.8 to 1.0 |
| Measured RSSI | RSSI(d) | BLE scan callback | -40 to -90 dBm |
The path loss exponent n controls how quickly signal attenuates:
n = 1.0: Free-space propagation (open field)n = 0.8: Less attenuation than free-space (unusual, suggests calibration offset)n = 2.0: Typical indoor environment
4.2 Signal Filtering Pipeline
Raw RSSI is noisy. The app applies a multi-stage filtering pipeline:
BLE Scan (raw RSSI)
|
v
ApplicationService.emit(OnRssiUpdate, rssi)
|
v
calculateDistance(rssi) --> raw distance in meters
|
v
AppModule.filter(Math.ceil(rawVal), kalmanVariation)
|
+-- iOS: Kalman Filter (matrix-based, Accelerate.framework)
| + sliding window mean of last N unique values
|
+-- Android: Sliding window mean of last N unique values only
|
v
Redux dispatch: updatePairedDeviceApproximatedDistance(Math.floor(dist))
|
v
OnDistanceUpdate(dist) --> alert logic
4.3 The Kalman Filter (iOS, Dormant)
The iOS native module contains a full matrix-based Kalman filter implementation across four Swift files:
| File | Purpose |
|---|---|
KalmanFilter.swift | Core filter logic |
Matrix.swift | Matrix operations using Accelerate.framework |
KalmanFilterType.swift | Filter type definitions |
DoubleExtension.swift | Numeric extensions |
The matrix implementation uses Apple's high-performance linear algebra:
vDSPfor vectorized operationscblas_dgemmfor matrix multiplicationdgetrf_/dgetri_(LAPACK) for matrix inversion
However, the actual filter() method in AppModule.swift uses a simpler sliding window mean over the last N unique distance values (where N = radarSteps from remote config, typically 3-7). The Kalman filter code is present but not invoked in the current code path — it is dormant dead code.
4.4 The Active Filter (Both Platforms)
The active filtering algorithm on both platforms is identical:
// Pseudocode for AppModule.filter()
func filter(rssi: Int, variation: Int) -> Int {
positionsArray.append(rssi)
if positionsArray.count > variation {
positionsArray.removeFirst()
}
let uniqueValues = Set(positionsArray)
let mean = uniqueValues.reduce(0, +) / uniqueValues.count
return mean
}
The resetPositionsArray() method clears the sliding window, called when the alert fires or the device disconnects.
4.5 Distance Update Lifecycle
| Event | Behavior |
|---|---|
| RSSI received | Calculate distance, filter, update Redux |
| Signal lost | After timeoutSecs (10s iOS / 3s Android), distance resets to undefined (displays "---") |
| Device in range | OnDistanceUpdate fires, checks against emergencyRange |
| Alert threshold crossed | Notification + URL open (once per crossing, tracked by notificationAlreadyShownForRange) |
5. User Flow: Pairing to Alarm
5.1 Complete Flow Diagram
flowchart TD
A[App Launch] --> B{Android Overlay Permission?}
B -->|Not Granted| C[OverlayModal]
C --> C1[Request SYSTEM_ALERT_WINDOW]
C1 --> C2[Poll every 1s until granted]
C2 --> D
B -->|Granted| D{Bluetooth Enabled?}
D -->|Off| E[BluetoothStateModal]
E --> E1[User enables Bluetooth]
E1 --> F
D -->|On| F{Onboarding Complete?}
F -->|No| G[OnboardingScreen]
G --> G1[Start BLE Scan]
G1 --> G2{Known Device Found?}
G2 -->|Yes| G3[Show 'Found Device!' pill]
G3 --> G4[Auto-pair & store in Redux]
G4 --> G5[Mark onboarding complete]
G5 --> H
G2 -->|No| G6[Show spinner, keep scanning]
G6 --> G2
F -->|Yes| H[HomeScreen]
H --> H1[Animated background + 2 buttons]
H1 --> I[Radar Button]
H1 --> J[Config Button]
I --> K[RadarScreen]
K --> K1[Live distance readout]
K --> K2[Rotating radar sweep animation]
K --> K3[Locket image + unpair option]
J --> L[ConfigScreen]
L --> L1[Alert On/Off toggle]
L --> L2[Distance slider 3-7m]
L --> L3[Emergency URL input]
5.2 First Run — Onboarding
- Permission cascade: Location (Android) -> Bluetooth (Android) -> Notifications (both), each with 1-second delays between requests
- BLE scan starts with
startDeviceScan(null, { allowDuplicates: true }) - Device matching: Each scanned advertisement is checked against
knownTagsfrom remote config - Auto-pair: When a known device is found, the
SearchingDeviceIndicatorturns green ("Found Device!") and the device is stored in Redux (setPairedDevice) - Onboarding complete:
setOnboardingState(true)persists to AsyncStorage
5.3 Home Screen
The home screen is a visual hub with:
- Animated background: Platform-specific (iOS: static PNG; Android: Skia-rendered animated GIF)
- Radar button: Animated with random 90-degree pulses every 1.4 seconds (
infinityPulse) - Config button: Animated with continuous 360-degree rotation (
infinityRotate)
Both buttons use spring-physics width animation from 0 to 70% of screen width on mount.
5.4 Configuration
Three controls on the Config screen:
| Control | Action | Redux Action |
|---|---|---|
| Alert toggle | Enable/disable proximity alerts | setAlertState(boolean) |
| Distance slider | Set threshold (3-7m, step 1) | setEmergencyRange(number) |
| URL input | Set emergency URL | setEmergencyUrl(string) |
The slider's animated label tracks the thumb position in real-time. Default emergency range: 3 meters. Default emergency URL: from remote config's defaultVideo field.
5.5 Radar Screen
The radar screen displays:
- Distance readout: Live meter reading (e.g., "5m") or "---" when signal is lost
- Radar visualization: Static base image with continuously rotating sweep overlay (5-second rotation cycle via
react-native-reanimated) - Device info:
ConnectionStateManagerViewshows the paired device's custom name in an animated pill, with a locket image. Long-pressing the locket triggers an unpair confirmation dialog
5.6 Alert Triggering
When the filtered distance falls below emergencyRange:
- Deduplication check:
notificationAlreadyShownForRangetracks which thresholds have already fired - One-shot guard:
isAlertAlreadyShownprevents re-firing the same alert - Actions:
- Critical push notification via
@notifee/react-native - Open emergency URL in browser (via
Linking.openURL)
- Critical push notification via
- Reset:
resetPositionsArray()clears the filter buffer;resetAlertShownState()re-arms for next approach
5.7 Remote Config
The app fetches configuration from a Google Cloud Run endpoint (API_URL from .env). If the fetch fails, it falls back to bundled JSON files:
| Platform | Fallback Path |
|---|---|
| iOS | modules/app-module/src/remoteConfig.json |
| Android | modules/app-module/src/remoteConfigAndroid.json |
The remote config controls:
- Device identification (
knownTags,customConfigPerDevice) - BLE calibration (
rssiAt1Meter,rssiPowper device) - Filter behavior (
radarSteps,timeoutSecs) - UI features (
enableDot) - Emergency defaults (
defaultVideo) - Notification thresholds (
rangesConfig)
6. Comparison with CAMDOM
Both the Knocklace app and camdom share a BLE foundation but serve fundamentally different purposes:
| Aspect | Knocklace Collection | CAMDOM |
|---|---|---|
| Purpose | Proximity detection + party alarm | Digital consent protection |
| BLE pattern | One-to-one (phone to single locket) | Many-to-many BLE mesh |
| Data over BLE | RSSI only (no GATT) | Full state machine via GATT characteristics |
| Distance method | RSSI path loss model + sliding window | Presence-based (not distance) |
| Native module | Kalman filter + background scan | JSI C++ state machine |
| Background mode | BGTaskScheduler / HeadlessJsTaskService | BLE peripheral mode |
| Alert mechanism | Push notification + open URL | Camera/mic lockout via /dev/null |
| Innovation | Calibrated path loss estimation | Race condition exploitation for QUIC/TCP ACK multiplexing |
The Knocklace app is a straightforward BLE beacon consumer. CAMDOM is a cross-platform BLE mesh networking system with a C++ JSI state machine. The technical complexity is not comparable — but the Knocklace app's calibration system (per-device rssiAt1Meter and rssiPow) shows thoughtful engineering for real-world BLE noise.
Cross-References
- sprite-mobile-redux — Redux store architecture that manages pairing state and distance updates
- sprite-mobile-code-review — Bug analysis including the BLE toggle no-op and permissions reducer issue
- camdom — Alefita's BLE mesh networking system (Cannes Lions, Epica Grand Prix)
- preferencias-tecnicas — Alefita's technical preferences (React Native ecosystem)