Skip to content

Commit

Permalink
Added companion device on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosinigaglia committed Apr 8, 2024
1 parent 7d68dd7 commit 62ac622
Show file tree
Hide file tree
Showing 20 changed files with 6,441 additions and 16,006 deletions.
25 changes: 16 additions & 9 deletions android/src/main/java/it/innove/CompanionScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class CompanionScanner {
public static final String LOG_TAG = "CompationScanManager";
private static final int SELECT_DEVICE_REQUEST_CODE = 540;

private static Callback scanCallback = null;

private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
Expand All @@ -53,11 +55,15 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
ScanResult result = intent.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
Peripheral peripheral = bleManager.savePeripheral(result.getDevice());

bleManager.sendEvent("BleManagerCompanionPeripheral", peripheral.asWritableMap());
if (scanCallback != null) {
scanCallback.invoke(null, peripheral.asWritableMap());
}
} else {
// No device, user cancelled?
Log.d(LOG_TAG, "Non-ok activity result");
bleManager.sendEvent("BleManagerCompanionPeripheral", null);
if (scanCallback != null) {
scanCallback.invoke(null, null);
}
}
}
};
Expand Down Expand Up @@ -86,14 +92,17 @@ public void scan(ReadableArray serviceUUIDs, ReadableMap options, Callback callb
}

AssociationRequest pairingRequest = builder.build();
if (scanCallback != null) {
scanCallback.invoke("New scan called", null);
}
scanCallback = callback;

bleManager.getCompanionDeviceManager().associate(pairingRequest, new CompanionDeviceManager.Callback() {
@Override
public void onFailure(@Nullable CharSequence charSequence) {
Log.d(LOG_TAG, "companion failure: " + charSequence);
WritableMap map = Arguments.createMap();
map.putString("error", charSequence.toString());
bleManager.sendEvent("BleManagerCompanionFailure", map);
scanCallback.invoke("Companion association failed: " + charSequence.toString());
scanCallback = null;
}

@Override
Expand All @@ -106,13 +115,11 @@ public void onDeviceFound(@NonNull IntentSender intentSender) {
} catch (IntentSender.SendIntentException e) {
Log.e(LOG_TAG, "Failed to send intent: " + e.toString());
String msg = "Failed to send intent: " + e.toString();
WritableMap map = Arguments.createMap();
map.putString("error", msg);
bleManager.sendEvent("BleManagerCompanionFailure", map);
scanCallback.invoke(msg, null);
scanCallback = null;
}
}
}, null);

callback.invoke();
}
}
13 changes: 0 additions & 13 deletions docs/events.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,3 @@ The peripheral received a request to start or stop providing notifications for a
- `code` - `Number` - [iOS only] error code

---

## BleManagerCompanionPeripheral [Android only]

Associate callback received a failure or failed to start the intent to
pick the device to associate.

---

## BleManagerCompanionFailure [Android only]

User picked a device to associate with.

Null if the request was cancelled by the user.
31 changes: 8 additions & 23 deletions docs/methods.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,12 @@ BleManager.stopScan().then(() => {

Scan for companion devices.

If companion device manger is not supported on this (android) device,
rejects. Otherwise resolves once the scan has started.
If companion device manger is not supported on this (android) device rejects.

There is no way to "stop" companion scanning. Once its started, it will
eventually emit `BleManagerCompanionPeripheral` event with either:
1. peripheral if user selects one
2. null if user "cancels" (i.e. doesn't select anything)
The promise it will eventually resolve with either:

Emits `BleManagerCompanionPeripheralFailure` on failure.

Unlike with `BleManager.scan()`, timeouts must be handled manually.

See `BleManagerCompanionPeripheral` and `BleManagerCompanionPeripheralFailure` events.
1. peripheral if user selects one
2. null if user "cancels" (i.e. doesn't select anything)

See `BleManager.supportsCompanion`.

Expand All @@ -113,24 +106,16 @@ See: https://developer.android.com/develop/connectivity/bluetooth/companion-devi
**Arguments**

- `serviceUUIDs` - `String[]` - List of service UUIDs to use as a filter
- `options` - `JSON` - Additional options
- `options` - `JSON` - Additional options

- `single` - `String?` - Scan only for single peripheral. See Android's `AssocationRequest.Builder.setSingleDevice`.

**Examples**

```js
const emitter = new NativeEventEmitter(NativeModules.BleManager);

emitter.addListener('BleManagerCompanionPeripheral', peripheral => {
if (peripheral === null) {
// User didn't select any peripheral.
} else {
// User selected a peripheral.
}
BleManager.compationScan().then(peripheral => {
console.log('Associated peripheral', peripheral);
});

BleManager.compationScan();
```

---
Expand Down Expand Up @@ -543,7 +528,7 @@ BleManager.writeDescriptor(
[1, 2]
)
.then(() => {
// Success code
// Success code
})
.catch((error) => {
// Failure code
Expand Down
6 changes: 5 additions & 1 deletion example/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module.exports = {
root: true,
extends: '@react-native-community',
extends: ['universe/native'],
rules: {
// Ensures props and state inside functions are always up-to-date
'react-hooks/exhaustive-deps': 'warn',
},
};
1 change: 0 additions & 1 deletion example/app.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"name": "example",
"displayName": "example",
"icon": "./icon.png"
}
192 changes: 102 additions & 90 deletions example/components/PeripheralDetailsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { Peripheral, PeripheralInfo } from 'react-native-ble-manager';
import {View, Text, StyleSheet, ScrollView} from 'react-native';
import {Peripheral, PeripheralInfo} from 'react-native-ble-manager';

// Define interfaces for your peripheral's properties
interface Characteristic {
Expand All @@ -14,7 +14,6 @@ interface Service {
// Add any other service properties you need
}


// Props expected by PeripheralDetails component
interface PeripheralDetailsProps {
route: {
Expand All @@ -24,93 +23,106 @@ interface PeripheralDetailsProps {
};
}

const PeripheralDetailsScreen = ({ route }: PeripheralDetailsProps) => {
const peripheralData = route.params.peripheralData;
console.log('peripheralData:', JSON.stringify(peripheralData, null, 2));

// Function to render characteristics for a given service
const renderCharacteristicsForService = (serviceUUID: string) => {
const characteristics = peripheralData.characteristics ?? [];
return characteristics
.filter((char) => char.service === serviceUUID)
.map((char, index) => (
<View key={index} style={styles.characteristicContainer}>
<Text style={styles.characteristicTitle}>Characteristic: {char.characteristic}</Text>
<Text>Properties: {Object.values(char.properties).join(', ')}</Text>
</View>
));
};
const PeripheralDetailsScreen = ({route}: PeripheralDetailsProps) => {
const peripheralData = route.params.peripheralData;
console.log('peripheralData:', JSON.stringify(peripheralData, null, 2));

// Function to render characteristics for a given service
const renderCharacteristicsForService = (serviceUUID: string) => {
const characteristics = peripheralData.characteristics ?? [];
return characteristics
.filter(char => char.service === serviceUUID)
.map((char, index) => (
<View key={index} style={styles.characteristicContainer}>
<Text style={styles.characteristicTitle}>
Characteristic: {char.characteristic}
</Text>
<Text>Properties: {Object.values(char.properties).join(', ')}</Text>
</View>
));
};

return (
<ScrollView style={styles.scrollViewStyle} contentContainerStyle={styles.contentContainer}>
<Text style={styles.title}>Peripheral Details</Text>
<Text style={styles.detail}>name: {peripheralData.name}</Text>
<Text style={styles.detail}>id: {peripheralData.id}</Text>
<Text style={styles.detail}>rssi: {peripheralData.rssi}</Text>
return (
<ScrollView
style={styles.scrollViewStyle}
contentContainerStyle={styles.contentContainer}>
<Text style={styles.title}>Peripheral Details</Text>
<Text style={styles.detail}>name: {peripheralData.name}</Text>
<Text style={styles.detail}>id: {peripheralData.id}</Text>
<Text style={styles.detail}>rssi: {peripheralData.rssi}</Text>

<Text style={[styles.title, styles.titleWithMargin]}>Advertising</Text>
<Text style={styles.detail}>localName: {peripheralData.advertising.localName}</Text>
<Text style={styles.detail}>txPowerLevel: {peripheralData.advertising.txPowerLevel}</Text>
<Text style={styles.detail}>isConnectable: {peripheralData.advertising.isConnectable ? 'true' : 'false'}</Text>
<Text style={styles.detail}>serviceUUIDs: {peripheralData.advertising.serviceUUIDs}</Text>

<Text style={[styles.title, styles.titleWithMargin]}>Services && Characteristics</Text>
{peripheralData.services?.map((service, index) => (
<View key={index} style={styles.serviceContainer}>
<Text style={styles.serviceTitle}>Service: {service.uuid}</Text>
{renderCharacteristicsForService(service.uuid)}
</View>
))}
</ScrollView>
);
};

// Add some basic styling
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
titleWithMargin: {
marginTop: 20, // Adjust this value as needed
},
detail: {
marginTop: 5,
fontSize: 16,
},
serviceContainer: {
marginTop: 15,
},
serviceTitle: {
fontSize: 18,
fontWeight: 'bold',
},
characteristic: {
fontSize: 16,
},
scrollViewStyle: {
flex: 1,
},
contentContainer: {
padding: 20,
},
characteristicContainer: {
marginTop: 10,
},
characteristicTitle: {
fontSize: 16,
fontWeight: '500',
},
propertyText: {
fontSize: 14,
marginLeft: 10,
},
});

export default PeripheralDetailsScreen;

<Text style={[styles.title, styles.titleWithMargin]}>Advertising</Text>
<Text style={styles.detail}>
localName: {peripheralData.advertising.localName}
</Text>
<Text style={styles.detail}>
txPowerLevel: {peripheralData.advertising.txPowerLevel}
</Text>
<Text style={styles.detail}>
isConnectable:{' '}
{peripheralData.advertising.isConnectable ? 'true' : 'false'}
</Text>
<Text style={styles.detail}>
serviceUUIDs: {peripheralData.advertising.serviceUUIDs}
</Text>

<Text style={[styles.title, styles.titleWithMargin]}>
Services && Characteristics
</Text>
{peripheralData.services?.map((service, index) => (
<View key={index} style={styles.serviceContainer}>
<Text style={styles.serviceTitle}>Service: {service.uuid}</Text>
{renderCharacteristicsForService(service.uuid)}
</View>
))}
</ScrollView>
);
};

// Add some basic styling
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
titleWithMargin: {
marginTop: 20, // Adjust this value as needed
},
detail: {
marginTop: 5,
fontSize: 16,
},
serviceContainer: {
marginTop: 15,
},
serviceTitle: {
fontSize: 18,
fontWeight: 'bold',
},
characteristic: {
fontSize: 16,
},
scrollViewStyle: {
flex: 1,
},
contentContainer: {
padding: 20,
},
characteristicContainer: {
marginTop: 10,
},
characteristicTitle: {
fontSize: 16,
fontWeight: '500',
},
propertyText: {
fontSize: 14,
marginLeft: 10,
},
});

export default PeripheralDetailsScreen;
Loading

0 comments on commit 62ac622

Please sign in to comment.