-
Notifications
You must be signed in to change notification settings - Fork 552
Description
Prerequisites
- I checked the documentation and FAQ without finding a solution
- I checked to make sure that this issue has not already been filed
Expected Behavior
The application crashes on an Android 11 device (L-shape tablet) when performing Bluetooth Low Energy (BLE) operations such as monitoring characteristics or reading data.
The issue does not occur on other devices, and the same flow works correctly elsewhere.
Current Behavior
The application crashes on an Android 11 device (L-shape tablet) when performing Bluetooth Low Energy (BLE) operations such as monitoring characteristics or reading data.
The issue does not occur on other devices, and the same flow works correctly elsewhere.
Library version
3.5.0
Device
RX-001v
Environment info
Connect to BLE device
Start monitoring characteristics (monitorCharacteristicForService)
Perform continuous reads (e.g., battery / sensor data)
Turn Bluetooth OFF/ON or wait for device disconnect
App crashesSteps to reproduce
- …
- …
App crashes on Android 11 device
Crash occurs after BLE disconnect or Bluetooth toggle
Native BLE stack throws errors
Formatted code sample or link to a repository
export const monitorDeviceSensorData = (
device: Device,
onLiveData?: (data: any) => void,
timeout?: number
): { promise: Promise<any[]>; stop: () => void } => {
const SERVICE_UUID = FINGER_SERVICE;
const UUID_FINGER_TEMP = IMU_UUIDS.FINGER_TEMP;
const UUID_ROOM_TEMP = IMU_UUIDS.ROOM_TEMP;
const UUID_IMU = IMU_UUIDS.RAW_IMU_DATA;
const sensorDataArray: any[] = [];
let latestData: any = {
fingerTemp: null,
roomTemp: null,
imu: null,
imuEvent: null as "significant_motion" | "wrist_tilt" | "wake_up" | null,
timestamp: null,
};
let isStopped = false;
const imuEventSubscriptions: { remove: () => void }[] = [];
let imuDataPollIntervalId: ReturnType<typeof setInterval> | null = null;
let imuEventPollIntervalId: ReturnType<typeof setInterval> | null = null;
const pushAndEmit = () => {
const snapshot = { ...latestData };
sensorDataArray.push(snapshot);
onLiveData?.(snapshot);
};
const onIMUEvent = (event: "significant_motion" | "wrist_tilt" | "wake_up") => {
if (isStopped) return;
latestData.imuEvent = event;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU event:", event);
pushAndEmit();
setTimeout(() => {
if (!isStopped) latestData.imuEvent = null;
}, 2000);
};
let fingerTempSubscription: { remove: () => void } | null = null;
let roomTempSubscription: { remove: () => void } | null = null;
try {
fingerTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_FINGER_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.fingerTemp = parseFingerTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] finger temp monitor failed:", (e as Error)?.message ?? e);
}
try {
roomTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_ROOM_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.roomTemp = parseRoomTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
if (__DEV__) console.log("[BLE] roomTemp delivered", latestData.roomTemp);
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] room temp monitor failed:", (e as Error)?.message ?? e);
}
let imuSubscription: { remove: () => void } | null = null;
// IMU: discover service. For c600 use polling only (no notify). Else try subscribe then config + event polling.
const IMU_DATA_POLL_MS = 150;
const IMU_POLL_READ_TIMEOUT_MS = 4000;
let imuDataPollingStarted = false;
let imuC600NotifyFallbackDone = false;
let imuDiscoveryStarted = false;
let imuPollReadFailLogged = false;
let imuPollEmptyLogged = false;
let imuPollTimeoutLogged = false;
const startIMUDataPolling = (svc: string, charUUID: string) => {
if (isStopped || imuDataPollIntervalId || imuDataPollingStarted) return;
imuDataPollingStarted = true;
const devId = (device?.id ?? "").slice(-8);
console.log("[BLE] IMU data: polling", charUUID, "on", svc, devId ? `[${devId}]` : "");
imuDataPollIntervalId = setInterval(async () => {
if (isStopped) return;
try {
const readPromise = device.readCharacteristicForService(svc, charUUID);
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("timeout")), IMU_POLL_READ_TIMEOUT_MS)
);
const c = await Promise.race([readPromise, timeoutPromise]);
if (isStopped) return;
if (!c?.value) {
if (!imuPollEmptyLogged) {
imuPollEmptyLogged = true;
console.log("[BLE] IMU data: read ok but value empty (char may be write-only or not streaming)");
}
return;
}
const parsed = parseIMUData(c.value);
latestData.imu = parsed;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU parsed", parsed);
pushAndEmit();
} catch (e) {
const msg = (e as Error)?.message ?? String(e);
if (msg === "timeout") {
if (!imuPollTimeoutLogged) {
imuPollTimeoutLogged = true;
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
console.log("[BLE] IMU (c604) read not available, stopped");
}
} else if (!imuPollReadFailLogged) {
imuPollReadFailLogged = true;
console.log("[BLE] IMU data: poll read failed:", msg);
}
}
}, IMU_DATA_POLL_MS);
};
const startIMUWithDiscoveredService = () => {
if (imuDiscoveryStarted) return;
imuDiscoveryStarted = true;
findIMUServiceUUID(device)
.then((imuServiceUUID) => {
if (isStopped) return;
return configureIMUBuiltInFunctions(device, imuServiceUUID).then((res) => ({ imuServiceUUID, res }));
})
.then((out) => {
if (isStopped || !out) return;
const svc = out.imuServiceUUID;
if (svc) {
console.log("[BLE] IMU starting event monitors on", svc.slice(-4));
startIMUEventMonitoring(svc);
}
})
.catch((e) => {
console.log("[BLE] IMU setup error:", (e as Error)?.message ?? e);
});
};
startIMUWithDiscoveredService();
// IMU events per BuiltInIMU.py / process_received_data: poll FUNC_SRC1, FUNC_SRC2, WAKE_UP_SRC every 500ms and parse (data&0x40 sm, data&0x1 wt, data&0x08 wu).
const startIMUEventMonitoring = (imuServiceUUID: string) => {
if (isStopped || imuEventSubscriptions.length > 0) return;
const R = IMU_UUIDS;
const DEBOUNCE_MS = 1500;
const lastFired: Record<string, number> = {};
const checkByteAndFire = (which: "sm" | "wt" | "wu", data: number, event: "significant_motion" | "wrist_tilt" | "wake_up", mask: number) => {
if (!(data & mask)) return;
const now = Date.now();
const key = which;
if ((now - (lastFired[key] ?? 0)) <= DEBOUNCE_MS) return;
lastFired[key] = now;
onIMUEvent(event);
};
const onEventData = (which: "sm" | "wt" | "wu") => (error: any, characteristic: any) => {
if (isStopped || error || !characteristic?.value) return;
const raw = atob(characteristic.value);
const data = raw.length > 0 ? raw.charCodeAt(0) : 0;
if (which === "sm") checkByteAndFire("sm", data, "significant_motion", 0x40);
if (which === "wt") checkByteAndFire("wt", data, "wrist_tilt", 0x01);
if (which === "wu") checkByteAndFire("wu", data, "wake_up", 0x08);
};
try {
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.FUNC_SRC1, onEventData("sm")));
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.FUNC_SRC2, onEventData("wt")));
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.WAKE_UP_SRC, onEventData("wu")));
console.log("[BLE] IMU event monitors subscribed (c653,c654,c61b)", imuServiceUUID);
} catch (e) {
console.log("[BLE] IMU event monitor subscribe failed:", (e as Error)?.message ?? e);
}
// Polling mode per Python: read result registers every 500ms (IMU_REG_UUID_FUNC_SRC1, FUNC_SRC2, WAKE_UP_SRC).
const IMU_EVENT_POLL_MS = 500;
const READ_TIMEOUT_MS = 2000;
const readReg = (charUUID: string): Promise<number> =>
Promise.race([
device.readCharacteristicForService(imuServiceUUID, charUUID).then((c: any) => {
if (!c?.value) return 0;
const raw = atob(c.value);
return raw.length > 0 ? raw.charCodeAt(0) : 0;
}),
delay(READ_TIMEOUT_MS).then(() => 0),
]).catch(() => 0);
if (!imuEventPollIntervalId) {
imuEventPollIntervalId = setInterval(async () => {
if (isStopped) return;
try {
const [v1, v2, v3] = await Promise.all([
readReg(R.FUNC_SRC1),
readReg(R.FUNC_SRC2),
readReg(R.WAKE_UP_SRC),
]);
if (isStopped) return;
checkByteAndFire("sm", v1, "significant_motion", 0x40);
checkByteAndFire("wt", v2, "wrist_tilt", 0x01);
checkByteAndFire("wu", v3, "wake_up", 0x08);
} catch (_) {}
}, IMU_EVENT_POLL_MS);
console.log("[BLE] IMU event polling 500ms (FUNC_SRC1,FUNC_SRC2,WAKE_UP_SRC) per BuiltInIMU.py");
}
};
const stop = () => {
isStopped = true;
imuEventSubscriptions.forEach((sub) => {
try {
sub.remove();
} catch (_) {}
});
imuEventSubscriptions.length = 0;
if (imuEventPollIntervalId) {
clearInterval(imuEventPollIntervalId);
imuEventPollIntervalId = null;
}
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
try {
fingerTempSubscription?.remove?.();
} catch (_) {}
try {
roomTempSubscription?.remove?.();
} catch (_) {}
try {
imuSubscription?.remove();
} catch (_) {}
console.log("🛑 Monitoring manually stopped.");
};
const promise = new Promise<any[]>((resolve) => {
setTimeout(() => {
stop(); // Auto stop after timeout
resolve(sensorDataArray);
}, timeout ?? TIMEOUT_15_MINUTES);
});
return { promise, stop };
};Relevant log output
E BtGatt.ContextMap: Context not found for ID 8
E BtGatt.ContextMap: Context not found for ID 7
E BtGatt.ContextMap: Context not found for ID 6
W BluetoothEventManager: AclStateChangedHandler: activeDevice is nullAdditional information
export const monitorDeviceSensorData = (
device: Device,
onLiveData?: (data: any) => void,
timeout?: number
): { promise: Promise<any[]>; stop: () => void } => {
const SERVICE_UUID = FINGER_SERVICE;
const UUID_FINGER_TEMP = IMU_UUIDS.FINGER_TEMP;
const UUID_ROOM_TEMP = IMU_UUIDS.ROOM_TEMP;
const UUID_IMU = IMU_UUIDS.RAW_IMU_DATA;
const sensorDataArray: any[] = [];
let latestData: any = {
fingerTemp: null,
roomTemp: null,
imu: null,
imuEvent: null as "significant_motion" | "wrist_tilt" | "wake_up" | null,
timestamp: null,
};
let isStopped = false;
const imuEventSubscriptions: { remove: () => void }[] = [];
let imuDataPollIntervalId: ReturnType | null = null;
let imuEventPollIntervalId: ReturnType | null = null;
const pushAndEmit = () => {
const snapshot = { ...latestData };
sensorDataArray.push(snapshot);
onLiveData?.(snapshot);
};
const onIMUEvent = (event: "significant_motion" | "wrist_tilt" | "wake_up") => {
if (isStopped) return;
latestData.imuEvent = event;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU event:", event);
pushAndEmit();
setTimeout(() => {
if (!isStopped) latestData.imuEvent = null;
}, 2000);
};
let fingerTempSubscription: { remove: () => void } | null = null;
let roomTempSubscription: { remove: () => void } | null = null;
try {
fingerTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_FINGER_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.fingerTemp = parseFingerTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] finger temp monitor failed:", (e as Error)?.message ?? e);
}
try {
roomTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_ROOM_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.roomTemp = parseRoomTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
if (DEV) console.log("[BLE] roomTemp delivered", latestData.roomTemp);
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] room temp monitor failed:", (e as Error)?.message ?? e);
}
let imuSubscription: { remove: () => void } | null = null;
// IMU: discover service. For c600 use polling only (no notify). Else try subscribe then config + event polling.
const IMU_DATA_POLL_MS = 150;
const IMU_POLL_READ_TIMEOUT_MS = 4000;
let imuDataPollingStarted = false;
let imuC600NotifyFallbackDone = false;
let imuDiscoveryStarted = false;
let imuPollReadFailLogged = false;
let imuPollEmptyLogged = false;
let imuPollTimeoutLogged = false;
const startIMUDataPolling = (svc: string, charUUID: string) => {
if (isStopped || imuDataPollIntervalId || imuDataPollingStarted) return;
imuDataPollingStarted = true;
const devId = (device?.id ?? "").slice(-8);
console.log("[BLE] IMU data: polling", charUUID, "on", svc, devId ? [${devId}] : "");
imuDataPollIntervalId = setInterval(async () => {
if (isStopped) return;
try {
const readPromise = device.readCharacteristicForService(svc, charUUID);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), IMU_POLL_READ_TIMEOUT_MS)
);
const c = await Promise.race([readPromise, timeoutPromise]);
if (isStopped) return;
if (!c?.value) {
if (!imuPollEmptyLogged) {
imuPollEmptyLogged = true;
console.log("[BLE] IMU data: read ok but value empty (char may be write-only or not streaming)");
}
return;
}
const parsed = parseIMUData(c.value);
latestData.imu = parsed;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU parsed", parsed);
pushAndEmit();
} catch (e) {
const msg = (e as Error)?.message ?? String(e);
if (msg === "timeout") {
if (!imuPollTimeoutLogged) {
imuPollTimeoutLogged = true;
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
console.log("[BLE] IMU (c604) read not available, stopped");
}
} else if (!imuPollReadFailLogged) {
imuPollReadFailLogged = true;
console.log("[BLE] IMU data: poll read failed:", msg);
}
}
}, IMU_DATA_POLL_MS);
};
const startIMUWithDiscoveredService = () => {
if (imuDiscoveryStarted) return;
imuDiscoveryStarted = true;
findIMUServiceUUID(device)
.then((imuServiceUUID) => {
if (isStopped) return;
return configureIMUBuiltInFunctions(device, imuServiceUUID).then((res) => ({ imuServiceUUID, res }));
})
.then((out) => {
if (isStopped || !out) return;
const svc = out.imuServiceUUID;
if (svc) {
console.log("[BLE] IMU starting event monitors on", svc.slice(-4));
startIMUEventMonitoring(svc);
}
})
.catch((e) => {
console.log("[BLE] IMU setup error:", (e as Error)?.message ?? e);
});
};
startIMUWithDiscoveredService();
// IMU events per BuiltInIMU.py / process_received_data: poll FUNC_SRC1, FUNC_SRC2, WAKE_UP_SRC every 500ms and parse (data&0x40 sm, data&0x1 wt, data&0x08 wu).
const startIMUEventMonitoring = (imuServiceUUID: string) => {
if (isStopped || imuEventSubscriptions.length > 0) return;
const R = IMU_UUIDS;
const DEBOUNCE_MS = 1500;
const lastFired: Record<string, number> = {};
const checkByteAndFire = (which: "sm" | "wt" | "wu", data: number, event: "significant_motion" | "wrist_tilt" | "wake_up", mask: number) => {
if (!(data & mask)) return;
const now = Date.now();
const key = which;
if ((now - (lastFired[key] ?? 0)) <= DEBOUNCE_MS) return;
lastFired[key] = now;
onIMUEvent(event);
};
const onEventData = (which: "sm" | "wt" | "wu") => (error: any, characteristic: any) => {
if (isStopped || error || !characteristic?.value) return;
const raw = atob(characteristic.value);
const data = raw.length > 0 ? raw.charCodeAt(0) : 0;
if (which === "sm") checkByteAndFire("sm", data, "significant_motion", 0x40);
if (which === "wt") checkByteAndFire("wt", data, "wrist_tilt", 0x01);
if (which === "wu") checkByteAndFire("wu", data, "wake_up", 0x08);
};
try {
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.FUNC_SRC1, onEventData("sm")));
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.FUNC_SRC2, onEventData("wt")));
imuEventSubscriptions.push(device.monitorCharacteristicForService(imuServiceUUID, R.WAKE_UP_SRC, onEventData("wu")));
console.log("[BLE] IMU event monitors subscribed (c653,c654,c61b)", imuServiceUUID);
} catch (e) {
console.log("[BLE] IMU event monitor subscribe failed:", (e as Error)?.message ?? e);
}
// Polling mode per Python: read result registers every 500ms (IMU_REG_UUID_FUNC_SRC1, FUNC_SRC2, WAKE_UP_SRC).
const IMU_EVENT_POLL_MS = 500;
const READ_TIMEOUT_MS = 2000;
const readReg = (charUUID: string): Promise<number> =>
Promise.race([
device.readCharacteristicForService(imuServiceUUID, charUUID).then((c: any) => {
if (!c?.value) return 0;
const raw = atob(c.value);
return raw.length > 0 ? raw.charCodeAt(0) : 0;
}),
delay(READ_TIMEOUT_MS).then(() => 0),
]).catch(() => 0);
if (!imuEventPollIntervalId) {
imuEventPollIntervalId = setInterval(async () => {
if (isStopped) return;
try {
const [v1, v2, v3] = await Promise.all([
readReg(R.FUNC_SRC1),
readReg(R.FUNC_SRC2),
readReg(R.WAKE_UP_SRC),
]);
if (isStopped) return;
checkByteAndFire("sm", v1, "significant_motion", 0x40);
checkByteAndFire("wt", v2, "wrist_tilt", 0x01);
checkByteAndFire("wu", v3, "wake_up", 0x08);
} catch (_) {}
}, IMU_EVENT_POLL_MS);
console.log("[BLE] IMU event polling 500ms (FUNC_SRC1,FUNC_SRC2,WAKE_UP_SRC) per BuiltInIMU.py");
}
};
const stop = () => {
isStopped = true;
imuEventSubscriptions.forEach((sub) => {
try {
sub.remove();
} catch () {}
});
imuEventSubscriptions.length = 0;
if (imuEventPollIntervalId) {
clearInterval(imuEventPollIntervalId);
imuEventPollIntervalId = null;
}
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
try {
fingerTempSubscription?.remove?.();
} catch () {}
try {
roomTempSubscription?.remove?.();
} catch () {}
try {
imuSubscription?.remove();
} catch () {}
console.log("🛑 Monitoring manually stopped.");
};
const promise = new Promise<any[]>((resolve) => {
setTimeout(() => {
stop(); // Auto stop after timeout
resolve(sensorDataArray);
}, timeout ?? TIMEOUT_15_MINUTES);
});
return { promise, stop };
};