UNPKG

witmotion-react-native

Version:

React Native SDK for WitMotion BLE IMU sensors (WT901, WT901BLECL, etc.)

204 lines (203 loc) 7.92 kB
"use strict"; /** * WitMotion BLE Manager for React Native * * Provides: * - Bluetooth permission handling * - Device scanning * - Connection management * - Subscription to notifications * - Command sending to WitMotion devices * * Uses react-native-ble-plx for BLE operations. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.WitCmd = exports.manager = void 0; exports.ensureBlePermissions = ensureBlePermissions; exports.scanForDevices = scanForDevices; exports.connectById = connectById; const react_native_ble_plx_1 = require("react-native-ble-plx"); const react_native_1 = require("react-native"); const Base64 = __importStar(require("base64-js")); const witParser_1 = require("./witParser"); exports.manager = new react_native_ble_plx_1.BleManager(); /** * Ensure required BLE permissions are granted. * - Android 12+ requires BLUETOOTH_SCAN + BLUETOOTH_CONNECT * - Android <12 requires ACCESS_FINE_LOCATION */ async function ensureBlePermissions() { if (react_native_1.Platform.OS === 'android') { if (react_native_1.Platform.Version >= 31) { const res = await react_native_1.PermissionsAndroid.requestMultiple([ react_native_1.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, react_native_1.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, ]); const ok = res[react_native_1.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] === react_native_1.PermissionsAndroid.RESULTS.GRANTED && res[react_native_1.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] === react_native_1.PermissionsAndroid.RESULTS.GRANTED; if (!ok) throw new Error('BLE permissions denied'); } else { const ok = await react_native_1.PermissionsAndroid.request(react_native_1.PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION); if (ok !== react_native_1.PermissionsAndroid.RESULTS.GRANTED) throw new Error('Location permission denied'); } } } /** * Ensure BLE state is powered on before use. */ async function ensureBleReady() { const current = await exports.manager.state(); if (current === 'PoweredOn') return; await new Promise((resolve, reject) => { const sub = exports.manager.onStateChange((state) => { if (state === 'PoweredOn') { sub.remove(); resolve(); } if (state === 'Unsupported' || state === 'Unauthorized') { sub.remove(); reject(new Error(`Bluetooth ${state}`)); } }, true); setTimeout(() => { sub.remove(); reject(new Error('Bluetooth not ready')); }, 10000); }); } /** * Scan for nearby devices. * @param onDevice callback fired for each device found * @param ms duration of scan in milliseconds */ async function scanForDevices(onDevice, ms = 8000) { await ensureBlePermissions(); await ensureBleReady(); const uuids = react_native_1.Platform.OS === 'ios' ? [] : null; return new Promise((resolve) => { exports.manager.startDeviceScan(uuids, { allowDuplicates: true }, (err, dev) => { if (err) { console.warn('Scan error:', err.message); return; } if (!dev) return; onDevice({ id: dev.id, name: dev.name, rssi: dev.rssi }); }); setTimeout(() => { exports.manager.stopDeviceScan(); resolve(); }, ms); }); } /** * Find the first writable characteristic of a device. */ async function findWrite(device) { await device.discoverAllServicesAndCharacteristics(); const services = await device.services(); for (const s of services) { const chars = await device.characteristicsForService(s.uuid); for (const c of chars) { if (c.isWritableWithResponse || c.isWritableWithoutResponse) return { serviceUUID: s.uuid, characteristicUUID: c.uuid }; } } return null; } /** * Subscribe to all notifiable characteristics of a device. * Incoming data is streamed through WitStreamParser. */ async function subscribeAll(device, onData) { const services = await device.services(); const parser = new witParser_1.WitStreamParser(); parser.onData = onData; const subs = []; for (const s of services) { const chars = await device.characteristicsForService(s.uuid); for (const c of chars) { if (!c.isNotifiable) continue; const sub = device.monitorCharacteristicForService(s.uuid, c.uuid, (error, ch) => { if (error) { if (__DEV__) console.log('Notify error', c.uuid, error.message); return; } if (!ch?.value) return; const bytes = Base64.toByteArray(ch.value); parser.push(bytes); }); subs.push(sub); } } if (!subs.length) throw new Error('No notifiable characteristics found'); return subs; } /** * Connect to a device by ID, subscribe to data, and get send() helper. * @param deviceId target BLE device id * @param onData callback invoked with parsed WitData * @returns device instance, subscriptions, and send() helper */ async function connectById(deviceId, onData) { await ensureBlePermissions(); await ensureBleReady(); const device = await exports.manager.connectToDevice(deviceId, { timeout: 8000 }) .catch(async () => exports.manager.connectToDevice(deviceId)); await device.discoverAllServicesAndCharacteristics(); const subs = await subscribeAll(device, onData); const write = await findWrite(device); const send = write ? async (bytes) => { const base64 = Base64.fromByteArray(Uint8Array.from(bytes)); await device.writeCharacteristicWithResponseForService(write.serviceUUID, write.characteristicUUID, base64); } : undefined; return { device, subs, send }; } /** * Predefined WitMotion commands (mirroring the official SDK). */ exports.WitCmd = { /** Start magnetometer calibration */ startMagCalib: [0xFF, 0xAA, 0x01, 0x07, 0x00], /** Save current settings */ save: [0xFF, 0xAA, 0x00, 0x00, 0x00], /** Reset yaw angle */ resetYaw: [0xFF, 0xAA, 0x01, 0x04, 0x00], /** Set output rate in Hz */ setRateHz: (hz) => [0xFF, 0xAA, 0x03, hz & 0xFF, (hz >> 8) & 0xFF], };