UNPKG

react-native-ble-plx

Version:
1,244 lines (1,155 loc) 52.3 kB
// @flow 'use strict' import { Device } from './Device' import { Service } from './Service' import { Characteristic } from './Characteristic' import { Descriptor } from './Descriptor' import { State, LogLevel, ConnectionPriority } from './TypeDefinition' import { BleModule, EventEmitter } from './BleModule' import { parseBleError, BleError, BleErrorCode, BleErrorCodeMessage, BleATTErrorCode, BleAndroidErrorCode, BleIOSErrorCode } from './BleError' import type { NativeDevice, NativeCharacteristic, NativeDescriptor, NativeBleRestoredState } from './BleModule' import type { BleErrorCodeMessageMapping, Subscription, DeviceId, Identifier, UUID, TransactionId, CharacteristicSubscriptionType, Base64, ScanOptions, ConnectionOptions, BleManagerOptions } from './TypeDefinition' import { isIOS } from './Utils' import { Platform } from 'react-native' const enableDisableDeprecatedMessage = 'react-native-ble-plx: The enable and disable feature is no longer supported. In Android SDK 31+ there were major changes in permissions, which may cause problems with these functions, and in SDK 33+ they were completely removed.' /** * * BleManager is an entry point for react-native-ble-plx library. It provides all means to discover and work with * {@link Device} instances. It should be initialized only once with `new` keyword and method * {@link #blemanagerdestroy|destroy()} should be called on its instance when user wants to deallocate all resources. * * In case you want to properly support Background Mode, you should provide `restoreStateIdentifier` and * `restoreStateFunction` in {@link BleManagerOptions}. * * @example * const manager = new BleManager(); * // ... work with BLE manager ... * manager.destroy(); */ export class BleManager { // Scan subscriptions // $FlowIssue[missing-type-arg] _scanEventSubscription: ?EventEmitter // Listening to BleModule events // $FlowIssue[missing-type-arg] _eventEmitter: EventEmitter // Unique identifier used to create internal transactionIds _uniqueId: number // Map of active promises with functions to forcibly cancel them _activePromises: { [id: string]: (error: BleError) => void } // Map of active subscriptions _activeSubscriptions: { [id: string]: Subscription } // Map of error codes to error messages _errorCodesToMessagesMapping: BleErrorCodeMessageMapping static sharedInstance: BleManager | null = null /** * Creates an instance of {@link BleManager}. * It will return already created instance if it was created before. * If you want to create a new instance to for example use different options, you have to call {@link #blemanagerdestroy|destroy()} on the previous one. */ constructor(options: BleManagerOptions = {}) { if (BleManager.sharedInstance !== null) { // $FlowFixMe - Constructor returns shared instance for singleton pattern return BleManager.sharedInstance } this._eventEmitter = new EventEmitter(BleModule) this._uniqueId = 0 this._activePromises = {} this._activeSubscriptions = {} const restoreStateFunction = options.restoreStateFunction if (restoreStateFunction != null && options.restoreStateIdentifier != null) { // $FlowIssue[prop-missing] this._activeSubscriptions[this._nextUniqueID()] = this._eventEmitter.addListener( BleModule.RestoreStateEvent, (nativeRestoredState: NativeBleRestoredState) => { if (nativeRestoredState == null) { restoreStateFunction(null) return } restoreStateFunction({ connectedPeripherals: nativeRestoredState.connectedPeripherals.map( nativeDevice => new Device(nativeDevice, this) ) }) } ) } this._errorCodesToMessagesMapping = options.errorCodesToMessagesMapping ? options.errorCodesToMessagesMapping : BleErrorCodeMessage BleModule.createClient(options.restoreStateIdentifier || null) BleManager.sharedInstance = this } /** * Destroys all promises which are in progress. * @private */ _destroyPromises() { const destroyedError = new BleError( { errorCode: BleErrorCode.BluetoothManagerDestroyed, attErrorCode: (null: ?$Values<typeof BleATTErrorCode>), iosErrorCode: (null: ?$Values<typeof BleIOSErrorCode>), androidErrorCode: (null: ?$Values<typeof BleAndroidErrorCode>), reason: (null: ?string) }, this._errorCodesToMessagesMapping ) for (const id in this._activePromises) { this._activePromises[id](destroyedError) } } /** * Destroys all subscriptions. * @private */ _destroySubscriptions() { for (const id in this._activeSubscriptions) { this._activeSubscriptions[id].remove() } } /** * Destroys {@link BleManager} instance. A new instance needs to be created to continue working with * this library. All operations which were in progress completes with * @returns {Promise<void>} Promise may return an error when the function cannot be called. * {@link #bleerrorcodebluetoothmanagerdestroyed|BluetoothManagerDestroyed} error code. */ async destroy(): Promise<void> { const response = await this._callPromise(BleModule.destroyClient()) // Unsubscribe from any subscriptions if (this._scanEventSubscription != null) { this._scanEventSubscription.remove() this._scanEventSubscription = null } this._destroySubscriptions() if (BleManager.sharedInstance) { BleManager.sharedInstance = null } // Destroy all promises this._destroyPromises() return response } /** * Generates new unique identifier to be used internally. * * @returns {string} New identifier. * @private */ _nextUniqueID(): string { this._uniqueId += 1 return this._uniqueId.toString() } /** * Calls promise and checks if it completed successfully * * @param {Promise<T>} promise Promise to be called * @returns {Promise<T>} Value of called promise. * @private */ async _callPromise<T>(promise: Promise<T>): Promise<T> { const id = this._nextUniqueID() try { const destroyPromise = new Promise((resolve, reject) => { this._activePromises[id] = reject }) const value = await Promise.race([destroyPromise, promise]) delete this._activePromises[id] // $FlowIssue[incompatible-return] return value } catch (error) { delete this._activePromises[id] throw parseBleError(error.message, this._errorCodesToMessagesMapping) } } // Mark: Common ------------------------------------------------------------------------------------------------------ /** * Sets new log level for native module's logging mechanism. * @param {LogLevel} logLevel New log level to be set. * @returns {Promise<LogLevel>} Current log level. */ setLogLevel(logLevel: $Keys<typeof LogLevel>): Promise<$Keys<typeof LogLevel> | void> { return this._callPromise(BleModule.setLogLevel(logLevel)) } /** * Get current log level for native module's logging mechanism. * @returns {Promise<LogLevel>} Current log level. */ logLevel(): Promise<$Keys<typeof LogLevel>> { return this._callPromise(BleModule.logLevel()) } /** * Cancels pending transaction. * * Few operations such as monitoring characteristic's value changes can be cancelled by a user. Basically every API * entry which accepts `transactionId` allows to call `cancelTransaction` function. When cancelled operation is a * promise or a callback which registers errors, {@link #bleerror|BleError} with error code * {@link #bleerrorcodeoperationcancelled|OperationCancelled} will be emitted in that case. Cancelling transaction * which doesn't exist is ignored. * * @example * const transactionId = 'monitor_battery'; * * // Monitor battery notifications * manager.monitorCharacteristicForDevice( * device.id, '180F', '2A19', * (error, characteristic) => { * // Handle battery level changes... * }, transactionId); * * // Cancel after specified amount of time * setTimeout(() => manager.cancelTransaction(transactionId), 2000); * * @param {TransactionId} transactionId Id of pending transactions. * @returns {Promise<void>} */ cancelTransaction(transactionId: TransactionId): Promise<void> { return this._callPromise(BleModule.cancelTransaction(transactionId)) } // Mark: Monitoring state -------------------------------------------------------------------------------------------- /** * Enable Bluetooth. This function blocks until BLE is in PoweredOn state. [Android only] * * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<BleManager>} Promise completes when state transition was successful. */ async enable(transactionId: ?TransactionId): Promise<BleManager> { if (!transactionId) { transactionId = this._nextUniqueID() } await this._callPromise(BleModule.enable(transactionId)) return this } /** * Deprecated * Disable Bluetooth. This function blocks until BLE is in PoweredOff state. [Android only] * * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<BleManager>} Promise completes when state transition was successful. */ async disable(transactionId: ?TransactionId): Promise<BleManager> { console.warn(enableDisableDeprecatedMessage) if (!transactionId) { transactionId = this._nextUniqueID() } await this._callPromise(BleModule.disable(transactionId)) return this } /** * Current, global {@link State} of a {@link BleManager}. All APIs are working only when active state * is "PoweredOn". * * @returns {Promise<State>} Promise which emits current state of BleManager. */ state(): Promise<$Keys<typeof State>> { return this._callPromise(BleModule.state()) } /** * Notifies about {@link State} changes of a {@link BleManager}. * * @example * const subscription = this.manager.onStateChange((state) => { * if (state === 'PoweredOn') { * this.scanAndConnect(); * subscription.remove(); * } * }, true); * * @param {function(newState: State)} listener Callback which emits state changes of BLE Manager. * Look at {@link State} for possible values. * @param {boolean} [emitCurrentState=false] If true, current state will be emitted as well. Defaults to false. * * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. */ onStateChange(listener: (newState: $Keys<typeof State>) => void, emitCurrentState: boolean = false): Subscription { const subscription: Subscription = this._eventEmitter.addListener(BleModule.StateChangeEvent, listener) const id = this._nextUniqueID() var wrappedSubscription: Subscription if (emitCurrentState) { var cancelled = false this._callPromise(this.state()).then(currentState => { if (!cancelled) { listener(currentState) } }) wrappedSubscription = { remove: () => { if (this._activeSubscriptions[id] != null) { cancelled = true delete this._activeSubscriptions[id] subscription.remove() } } } } else { wrappedSubscription = { remove: () => { if (this._activeSubscriptions[id] != null) { delete this._activeSubscriptions[id] subscription.remove() } } } } this._activeSubscriptions[id] = wrappedSubscription return wrappedSubscription } // Mark: Scanning ---------------------------------------------------------------------------------------------------- /** * Starts device scanning. When previous scan is in progress it will be stopped before executing this command. * * @param {?Array<UUID>} UUIDs Array of strings containing {@link UUID}s of {@link Service}s which are registered in * scanned {@link Device}. If `null` is passed, all available {@link Device}s will be scanned. * @param {?ScanOptions} options Optional configuration for scanning operation. * @param {function(error: ?BleError, scannedDevice: ?Device)} listener Function which will be called for every scanned * @returns {Promise<void>} Promise may return an error when the function cannot be called. * {@link Device} (devices may be scanned multiple times). It's first argument is potential {@link Error} which is set * to non `null` value when scanning failed. You have to start scanning process again if that happens. Second argument * is a scanned {@link Device}. * @returns {Promise<void>} the promise may be rejected if the operation is impossible to perform. */ async startDeviceScan( UUIDs: ?Array<UUID>, options: ?ScanOptions, listener: (error: ?BleError, scannedDevice: ?Device) => Promise<void> ): Promise<void> { const scanListener = ([error, nativeDevice]: [?string, ?NativeDevice]) => { listener( error ? parseBleError(error, this._errorCodesToMessagesMapping) : null, nativeDevice ? new Device(nativeDevice, this) : null ) } // $FlowFixMe: Flow cannot deduce EmitterSubscription type. this._scanEventSubscription = this._eventEmitter.addListener(BleModule.ScanEvent, scanListener) return this._callPromise(BleModule.startDeviceScan(UUIDs, options)) } /** * Stops {@link Device} scan if in progress. * @returns {Promise<void>} the promise may be rejected if the operation is impossible to perform. */ stopDeviceScan(): Promise<void> { if (this._scanEventSubscription != null) { this._scanEventSubscription.remove() this._scanEventSubscription = null } return this._callPromise(BleModule.stopDeviceScan()) } /** * Request a connection parameter update. This functions may update connection parameters on Android API level 21 or * above. * * @param {DeviceId} deviceIdentifier Device identifier. * @param {ConnectionPriority} connectionPriority: Connection priority. * @param {?TransactionId} transactionId Transaction handle used to cancel operation. * @returns {Promise<Device>} Connected device. */ async requestConnectionPriorityForDevice( deviceIdentifier: DeviceId, connectionPriority: $Values<typeof ConnectionPriority>, transactionId: ?TransactionId ): Promise<Device> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDevice = await this._callPromise( BleModule.requestConnectionPriorityForDevice(deviceIdentifier, connectionPriority, transactionId) ) return new Device(nativeDevice, this) } /** * Reads RSSI for connected device. * * @param {DeviceId} deviceIdentifier Device identifier. * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<Device>} Connected device with updated RSSI value. */ async readRSSIForDevice(deviceIdentifier: DeviceId, transactionId: ?TransactionId): Promise<Device> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDevice = await this._callPromise(BleModule.readRSSIForDevice(deviceIdentifier, transactionId)) return new Device(nativeDevice, this) } /** * Request new MTU value for this device. This function currently is not doing anything * on iOS platform as MTU exchange is done automatically. Since Android 14, * mtu management has been changed, more information can be found at the link: * https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517 * @param {DeviceId} deviceIdentifier Device identifier. * @param {number} mtu New MTU to negotiate. * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<Device>} Device with updated MTU size. Default value is 23 (517 since Android 14).. */ async requestMTUForDevice(deviceIdentifier: DeviceId, mtu: number, transactionId: ?TransactionId): Promise<Device> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDevice = await this._callPromise(BleModule.requestMTUForDevice(deviceIdentifier, mtu, transactionId)) return new Device(nativeDevice, this) } // Mark: Connection management --------------------------------------------------------------------------------------- /** * Returns a list of known devices by their identifiers. * @param {Array<DeviceId>} deviceIdentifiers List of device identifiers. * @returns {Promise<Array<Device>>} List of known devices by their identifiers. */ async devices(deviceIdentifiers: Array<DeviceId>): Promise<Array<Device>> { const nativeDevices = await this._callPromise(BleModule.devices(deviceIdentifiers)) return nativeDevices.map((nativeDevice: NativeDevice) => { return new Device(nativeDevice, this) }) } /** * Returns a list of the peripherals (containing any of the specified services) currently connected to the system * which have discovered services. Returned devices **may not be connected** to your application. Make sure to check * if that's the case with function {@link #blemanagerisdeviceconnected|isDeviceConnected}. * @param {Array<UUID>} serviceUUIDs List of service UUIDs. Device must contain at least one of them to be listed. * @returns {Promise<Array<Device>>} List of known devices with discovered services as stated in the parameter. */ async connectedDevices(serviceUUIDs: Array<UUID>): Promise<Array<Device>> { const nativeDevices = await this._callPromise(BleModule.connectedDevices(serviceUUIDs)) return nativeDevices.map((nativeDevice: NativeDevice) => { return new Device(nativeDevice, this) }) } // Mark: Connection management --------------------------------------------------------------------------------------- /** * Connects to {@link Device} with provided ID. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {?ConnectionOptions} options Platform specific options for connection establishment. * @returns {Promise<Device>} Connected {@link Device} object if successful. */ async connectToDevice(deviceIdentifier: DeviceId, options: ?ConnectionOptions): Promise<Device> { if (Platform.OS === 'android' && (await this.isDeviceConnected(deviceIdentifier))) { await this.cancelDeviceConnection(deviceIdentifier) } const nativeDevice = await this._callPromise(BleModule.connectToDevice(deviceIdentifier, options)) return new Device(nativeDevice, this) } /** * Disconnects from {@link Device} if it's connected or cancels pending connection. * * @param {DeviceId} deviceIdentifier {@link Device} identifier to be closed. * @returns {Promise<Device>} Returns closed {@link Device} when operation is successful. */ async cancelDeviceConnection(deviceIdentifier: DeviceId): Promise<Device> { const nativeDevice = await this._callPromise(BleModule.cancelDeviceConnection(deviceIdentifier)) return new Device(nativeDevice, this) } /** * Monitors if {@link Device} was disconnected due to any errors or connection problems. * * @param {DeviceId} deviceIdentifier {@link Device} identifier to be monitored. * @param {function(error: ?BleError, device: Device)} listener - callback returning error as a reason of disconnection * if available and {@link Device} object. If an error is null, that means the connection was terminated by * {@link #blemanagercanceldeviceconnection|bleManager.cancelDeviceConnection()} call. * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. */ onDeviceDisconnected(deviceIdentifier: DeviceId, listener: (error: ?BleError, device: Device) => void): Subscription { const disconnectionListener = ([error, nativeDevice]: [?string, NativeDevice]) => { if (deviceIdentifier !== nativeDevice.id) { return } listener(error ? parseBleError(error, this._errorCodesToMessagesMapping) : null, new Device(nativeDevice, this)) } const subscription: Subscription = this._eventEmitter.addListener( BleModule.DisconnectionEvent, disconnectionListener ) const id = this._nextUniqueID() const wrappedSubscription = { remove: () => { if (this._activeSubscriptions[id] != null) { delete this._activeSubscriptions[id] subscription.remove() } } } this._activeSubscriptions[id] = wrappedSubscription return wrappedSubscription } /** * Check connection state of a {@link Device}. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @returns {Promise<boolean>} Promise which emits `true` if device is connected, and `false` otherwise. */ isDeviceConnected(deviceIdentifier: DeviceId): Promise<boolean> { return this._callPromise(BleModule.isDeviceConnected(deviceIdentifier)) } // Mark: Discovery --------------------------------------------------------------------------------------------------- /** * Discovers all {@link Service}s, {@link Characteristic}s and {@link Descriptor}s for {@link Device}. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<Device>} Promise which emits {@link Device} object if all available services and * characteristics have been discovered. */ async discoverAllServicesAndCharacteristicsForDevice( deviceIdentifier: DeviceId, transactionId: ?TransactionId ): Promise<Device> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDevice = await this._callPromise( BleModule.discoverAllServicesAndCharacteristicsForDevice(deviceIdentifier, transactionId) ) const services = await this._callPromise(BleModule.servicesForDevice(deviceIdentifier)) const serviceUUIDs = (services || []).map(service => service.uuid) // $FlowFixMe const device = { ...nativeDevice, serviceUUIDs } return new Device(device, this) } // Mark: Service and characteristic getters -------------------------------------------------------------------------- /** * List of discovered {@link Service}s for {@link Device}. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @returns {Promise<Array<Service>>} Promise which emits array of {@link Service} objects which are discovered for a * {@link Device}. */ async servicesForDevice(deviceIdentifier: DeviceId): Promise<Array<Service>> { const services = await this._callPromise(BleModule.servicesForDevice(deviceIdentifier)) return services.map(nativeService => { return new Service(nativeService, this) }) } /** * List of discovered {@link Characteristic}s for given {@link Device} and {@link Service}. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @returns {Promise<Array<Characteristic>>} Promise which emits array of {@link Characteristic} objects which are * discovered for a {@link Device} in specified {@link Service}. */ characteristicsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID): Promise<Array<Characteristic>> { return this._handleCharacteristics(BleModule.characteristicsForDevice(deviceIdentifier, serviceUUID)) } /** * List of discovered {@link Characteristic}s for unique {@link Service}. * * @param {Identifier} serviceIdentifier {@link Service} ID. * @returns {Promise<Array<Characteristic>>} Promise which emits array of {@link Characteristic} objects which are * discovered in unique {@link Service}. * @private */ _characteristicsForService(serviceIdentifier: Identifier): Promise<Array<Characteristic>> { return this._handleCharacteristics(BleModule.characteristicsForService(serviceIdentifier)) } /** * Common code for handling NativeCharacteristic fetches. * * @param {Promise<Array<NativeCharacteristic>>} characteristicsPromise Native characteristics. * @returns {Promise<Array<Characteristic>>} Promise which emits array of {@link Characteristic} objects which are * discovered in unique {@link Service}. * @private */ async _handleCharacteristics( characteristicsPromise: Promise<Array<NativeCharacteristic>> ): Promise<Array<Characteristic>> { const characteristics = await this._callPromise(characteristicsPromise) return characteristics.map(nativeCharacteristic => { return new Characteristic(nativeCharacteristic, this) }) } /** * List of discovered {@link Descriptor}s for given {@link Device}, {@link Service} and {@link Characteristic}. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @returns {Promise<Array<Descriptor>>} Promise which emits array of {@link Descriptor} objects which are * discovered for a {@link Device}, {@link Service} in specified {@link Characteristic}. */ descriptorsForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID ): Promise<Array<Descriptor>> { return this._handleDescriptors(BleModule.descriptorsForDevice(deviceIdentifier, serviceUUID, characteristicUUID)) } /** * List of discovered {@link Descriptor}s for given {@link Service} and {@link Characteristic}. * * @param {Identifier} serviceIdentifier {@link Service} identifier. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @returns {Promise<Array<Descriptor>>} Promise which emits array of {@link Descriptor} objects which are * discovered for a {@link Service} in specified {@link Characteristic}. * @private */ _descriptorsForService(serviceIdentifier: Identifier, characteristicUUID: UUID): Promise<Array<Descriptor>> { return this._handleDescriptors(BleModule.descriptorsForService(serviceIdentifier, characteristicUUID)) } /** * List of discovered {@link Descriptor}s for given {@link Characteristic}. * * @param {Identifier} characteristicIdentifier {@link Characteristic} identifier. * @returns {Promise<Array<Descriptor>>} Promise which emits array of {@link Descriptor} objects which are * discovered in specified {@link Characteristic}. * @private */ _descriptorsForCharacteristic(characteristicIdentifier: Identifier): Promise<Array<Descriptor>> { return this._handleDescriptors(BleModule.descriptorsForCharacteristic(characteristicIdentifier)) } /** * Common code for handling NativeDescriptor fetches. * @param {Promise<Array<NativeDescriptor>>} descriptorsPromise Native descriptors. * @returns {Promise<Array<Descriptor>>} Promise which emits array of {@link Descriptor} objects which are * discovered in unique {@link Characteristic}. * @private */ async _handleDescriptors(descriptorsPromise: Promise<Array<NativeDescriptor>>): Promise<Array<Descriptor>> { const descriptors = await this._callPromise(descriptorsPromise) return descriptors.map(nativeDescriptor => { return new Descriptor(nativeDescriptor, this) }) } // Mark: Characteristics operations ---------------------------------------------------------------------------------- /** * Read {@link Characteristic} value. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. */ async readCharacteristicForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.readCharacteristicForDevice(deviceIdentifier, serviceUUID, characteristicUUID, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Read {@link Characteristic} value. * * @param {Identifier} serviceIdentifier {@link Service} ID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of {@link Characteristic} will be stored inside returned object. * @private */ async _readCharacteristicForService( serviceIdentifier: Identifier, characteristicUUID: UUID, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.readCharacteristicForService(serviceIdentifier, characteristicUUID, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Read {@link Characteristic} value. * * @param {Identifier} characteristicIdentifier {@link Characteristic} ID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified ID. * Latest value of {@link Characteristic} will be stored inside returned object. * @private */ async _readCharacteristic( characteristicIdentifier: Identifier, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.readCharacteristic(characteristicIdentifier, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value with response. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of characteristic may not be stored inside returned object. */ async writeCharacteristicWithResponseForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristicForDevice( deviceIdentifier, serviceUUID, characteristicUUID, base64Value, true, transactionId ) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value with response. * * @param {Identifier} serviceIdentifier {@link Service} ID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of characteristic may not be stored inside returned object. * @private */ async _writeCharacteristicWithResponseForService( serviceIdentifier: Identifier, characteristicUUID: UUID, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristicForService(serviceIdentifier, characteristicUUID, base64Value, true, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value with response. * * @param {Identifier} characteristicIdentifier {@link Characteristic} ID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified ID. * Latest value of characteristic may not be stored inside returned object. * @private */ async _writeCharacteristicWithResponse( characteristicIdentifier: Identifier, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristic(characteristicIdentifier, base64Value, true, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value without response. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of characteristic may not be stored inside returned object. */ async writeCharacteristicWithoutResponseForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristicForDevice( deviceIdentifier, serviceUUID, characteristicUUID, base64Value, false, transactionId ) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value without response. * * @param {Identifier} serviceIdentifier {@link Service} ID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified * UUID paths. Latest value of characteristic may not be stored inside returned object. * @private */ async _writeCharacteristicWithoutResponseForService( serviceIdentifier: Identifier, characteristicUUID: UUID, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristicForService(serviceIdentifier, characteristicUUID, base64Value, false, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Write {@link Characteristic} value without response. * * @param {Identifier} characteristicIdentifier {@link Characteristic} UUID. * @param {Base64} base64Value Value in Base64 format. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Characteristic>} Promise which emits first {@link Characteristic} object matching specified ID. * Latest value of characteristic may not be stored inside returned object. * @private */ async _writeCharacteristicWithoutResponse( characteristicIdentifier: Identifier, base64Value: Base64, transactionId: ?TransactionId ): Promise<Characteristic> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeCharacteristic = await this._callPromise( BleModule.writeCharacteristic(characteristicIdentifier, base64Value, false, transactionId) ) return new Characteristic(nativeCharacteristic, this) } /** * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used * in favour of indications. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits * {@link Characteristic} objects with modified value for each notification. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. */ monitorCharacteristicForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, listener: (error: ?BleError, characteristic: ?Characteristic) => void, transactionId: ?TransactionId, subscriptionType: ?CharacteristicSubscriptionType ): Subscription { const filledTransactionId = transactionId || this._nextUniqueID() const commonArgs = [deviceIdentifier, serviceUUID, characteristicUUID, filledTransactionId] const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] return this._handleMonitorCharacteristic( BleModule.monitorCharacteristicForDevice(...args), filledTransactionId, listener ) } /** * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used * in favour of indications. * * @param {Identifier} serviceIdentifier {@link Service} ID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits * {@link Characteristic} objects with modified value for each notification. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. * @private */ _monitorCharacteristicForService( serviceIdentifier: Identifier, characteristicUUID: UUID, listener: (error: ?BleError, characteristic: ?Characteristic) => void, transactionId: ?TransactionId, subscriptionType: ?CharacteristicSubscriptionType ): Subscription { const filledTransactionId = transactionId || this._nextUniqueID() const commonArgs = [serviceIdentifier, characteristicUUID, filledTransactionId] const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] return this._handleMonitorCharacteristic( BleModule.monitorCharacteristicForService(...args), filledTransactionId, listener ) } /** * Monitor value changes of a {@link Characteristic}. If notifications are enabled they will be used * in favour of indications. * * @param {Identifier} characteristicIdentifier - {@link Characteristic} ID. * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits * {@link Characteristic} objects with modified value for each notification. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * @param {?CharacteristicSubscriptionType} subscriptionType [android only] subscription type of the characteristic * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. * @private */ _monitorCharacteristic( characteristicIdentifier: Identifier, listener: (error: ?BleError, characteristic: ?Characteristic) => void, transactionId: ?TransactionId, subscriptionType: ?CharacteristicSubscriptionType ): Subscription { const filledTransactionId = transactionId || this._nextUniqueID() const commonArgs = [characteristicIdentifier, filledTransactionId] const args = isIOS ? commonArgs : [...commonArgs, subscriptionType] return this._handleMonitorCharacteristic(BleModule.monitorCharacteristic(...args), filledTransactionId, listener) } /** * Common code to handle characteristic monitoring. * * @param {Promise<void>} monitorPromise Characteristic monitoring promise * @param {TransactionId} transactionId TransactionId of passed promise * @param {function(error: ?BleError, characteristic: ?Characteristic)} listener - callback which emits * {@link Characteristic} objects with modified value for each notification. * @returns {Subscription} Subscription on which `remove()` function can be called to unsubscribe. * @private */ _handleMonitorCharacteristic( monitorPromise: Promise<void>, transactionId: TransactionId, listener: (error: ?BleError, characteristic: ?Characteristic) => void ): Subscription { const monitorListener = ([error, characteristic, msgTransactionId]: [ ?string, NativeCharacteristic, TransactionId ]) => { if (transactionId !== msgTransactionId) { return } if (error) { listener(parseBleError(error, this._errorCodesToMessagesMapping), null) return } listener(null, new Characteristic(characteristic, this)) } const subscription: Subscription = this._eventEmitter.addListener(BleModule.ReadEvent, monitorListener) const id = this._nextUniqueID() const wrappedSubscription: Subscription = { remove: () => { if (this._activeSubscriptions[id] != null) { delete this._activeSubscriptions[id] subscription.remove() } } } this._activeSubscriptions[id] = wrappedSubscription this._callPromise(monitorPromise).then( () => { wrappedSubscription.remove() }, (error: BleError) => { listener(error, null) wrappedSubscription.remove() } ) return { remove: () => { BleModule.cancelTransaction(transactionId) } } } // Mark: Descriptors operations ---------------------------------------------------------------------------------- /** * Read {@link Descriptor} value. * * @param {DeviceId} deviceIdentifier {@link Device} identifier. * @param {UUID} serviceUUID {@link Service} UUID. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {UUID} descriptorUUID {@link Descriptor} UUID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Descriptor>} Promise which emits first {@link Descriptor} object matching specified * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. */ async readDescriptorForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID, transactionId: ?TransactionId ): Promise<Descriptor> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDescriptor = await this._callPromise( BleModule.readDescriptorForDevice( deviceIdentifier, serviceUUID, characteristicUUID, descriptorUUID, transactionId ) ) return new Descriptor(nativeDescriptor, this) } /** * Read {@link Descriptor} value. * * @param {Identifier} serviceIdentifier {@link Service} identifier. * @param {UUID} characteristicUUID {@link Characteristic} UUID. * @param {UUID} descriptorUUID {@link Descriptor} UUID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Descriptor>} Promise which emits first {@link Descriptor} object matching specified * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. * @private */ async _readDescriptorForService( serviceIdentifier: Identifier, characteristicUUID: UUID, descriptorUUID: UUID, transactionId: ?TransactionId ): Promise<Descriptor> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDescriptor = await this._callPromise( BleModule.readDescriptorForService(serviceIdentifier, characteristicUUID, descriptorUUID, transactionId) ) return new Descriptor(nativeDescriptor, this) } /** * Read {@link Descriptor} value. * * @param {Identifier} characteristicIdentifier {@link Characteristic} identifier. * @param {UUID} descriptorUUID {@link Descriptor} UUID. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Descriptor>} Promise which emits first {@link Descriptor} object matching specified * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. * @private */ async _readDescriptorForCharacteristic( characteristicIdentifier: Identifier, descriptorUUID: UUID, transactionId: ?TransactionId ): Promise<Descriptor> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDescriptor = await this._callPromise( BleModule.readDescriptorForCharacteristic(characteristicIdentifier, descriptorUUID, transactionId) ) return new Descriptor(nativeDescriptor, this) } /** * Read {@link Descriptor} value. * * @param {Identifier} descriptorIdentifier {@link Descriptor} identifier. * @param {?TransactionId} transactionId optional `transactionId` which can be used in * {@link #blemanagercanceltransaction|cancelTransaction()} function. * @returns {Promise<Descriptor>} Promise which emits first {@link Descriptor} object matching specified * UUID paths. Latest value of {@link Descriptor} will be stored inside returned object. * @private */ async _readDescriptor(descriptorIdentifier: Identifier, transactionId: ?TransactionId): Promise<Descriptor> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDescriptor = await this._callPromise(BleModule.readDescriptor(descriptorIdentifier, transactionId)) return new Descriptor(nativeDescriptor, this) } /** * Write {@link Descriptor} value. * * @param {DeviceId} deviceIdentifier Connected device identifier * @param {UUID} serviceUUID Service UUID * @param {UUID} characteristicUUID Characteristic UUID * @param {UUID} descriptorUUID Descriptor UUID * @param {Base64} valueBase64 Value to be set coded in Base64 * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<Descriptor>} Descriptor which saved passed value */ async writeDescriptorForDevice( deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID, valueBase64: Base64, transactionId: ?TransactionId ): Promise<Descriptor> { if (!transactionId) { transactionId = this._nextUniqueID() } const nativeDescriptor = await this._callPromise( BleModule.writeDescriptorForDevice( deviceIdentifier, serviceUUID, characteristicUUID, descriptorUUID, valueBase64, transactionId ) ) return new Descriptor(nativeDescriptor, this) } /** * Write {@link Descriptor} value. * * @param {Identifier} serviceIdentifier Service identifier * @param {UUID} characteristicUUID Characteristic UUID * @param {UUID} descriptorUUID Descriptor UUID * @param {Base64} valueBase64 Value to be set coded in Base64 * @param {?TransactionId} transactionId Transaction handle used to cancel operation * @returns {Promise<Descriptor>} Descriptor which saved passed value *