Skip to content

Support for BLE scanning #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
53abe34
feat: add new android native wifi scan receiver
agonper May 6, 2022
60a58f6
feat: add wifi fingerprint model
agonper May 6, 2022
2e2ab2b
feat: add wifi scan android adapter
agonper May 6, 2022
4295ec6
feat: add wifi scan provider
agonper May 6, 2022
27fbfc3
feat(wifi): update plugin api
agonper May 6, 2022
f3be21c
chore: add missing demo permissions
agonper May 6, 2022
8f5cf01
chore: update demo to use wifi scanning apis
agonper May 6, 2022
3d74abb
fix: avoid performing unneeded timeout
agonper May 6, 2022
68a9c5f
fix: wrong default options filling condition
agonper May 6, 2022
d3021f2
chore: use more conservative options for wifi scan demo
agonper May 6, 2022
432884d
feat: fingerprints obtained after successful scans are marked as new
agonper May 7, 2022
5dbe65e
fix(wifi): ensure no result propagation after stream unsubscription
agonper May 7, 2022
8946810
chore: define better step execution order
agonper May 7, 2022
096037a
docs(wifi): update docs to describe the new API
agonper May 7, 2022
33e9a5b
chore(demo): add bluetooth-specific permissions
agonper May 9, 2022
bc6f5c6
chore(demo): add optional bluetooth scanner dependency
agonper May 9, 2022
2157d3f
feat(ble): add native scanner library typings
agonper May 9, 2022
be99f30
feat(ble): add basic scan result type
agonper May 9, 2022
c5dc8fb
feat(ble): add android native results parsers
agonper May 9, 2022
bb7e49a
feat(ble): add android bluetooth enable helpers
agonper May 9, 2022
9e59293
feat(ble): add android native ble scan adapter
agonper May 9, 2022
358fa09
feat(ble): add ble scan provider
agonper May 9, 2022
aa7ff92
chore: enable @NativeClass processing
agonper May 9, 2022
c316a00
feat(ble): expose ble scanning APIs
agonper May 9, 2022
f77d47d
chore(demo): update to use new ble scanning APIs
agonper May 9, 2022
6e1738f
chore(demo): update imports
agonper May 9, 2022
455a7c2
fix(ble): add a timeout for when a filter has been applied
agonper May 9, 2022
9db3acd
docs(ble): add install instructions and usage information in README
agonper May 9, 2022
21546d3
fix: create byte array as native array
matey97 May 10, 2022
81d7413
fix(ble): make iBeacon UUID filter not sensitive to casing
agonper May 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
636 changes: 510 additions & 126 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ steps:

- script: |
cd src
npm install
npm run setup
npm run ci.tslint
displayName: 'Lint'

- script: |
cd demo
npm install
npm run setup
ns build android --bundle --env.uglify --env.snapshot
displayName: 'Build'

Expand Down
5 changes: 3 additions & 2 deletions demo/app/App_Resources/Android/app.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Add your native dependencies here:

// Uncomment to add recyclerview-v7 dependency
//dependencies {
dependencies {
// implementation 'com.android.support:recyclerview-v7:+'
//}
implementation 'no.nordicsemi.android.support.v18:scanner:1.6.0'
}

// If you want to add something to be applied before applying plugins' include.gradle files
// e.g. project.ext.googlePlayServicesVersion = "15.0.1"
Expand Down
18 changes: 13 additions & 5 deletions demo/app/App_Resources/Android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@
android:largeScreens="true"
android:xlargeScreens="true"/>

<!-- Always include this permission if your app needs location access -->
<!-- This permission is for "approximate" location data -->
<!-- Always include this permission if your app needs location / Wi-Fi scans access. -->
<!-- This permission is for "approximate" location data and required for Wi-Fi scans -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Include only if your app benefits from precise location access. -->
<!-- This permission is for "precise" location data -->
<!-- Include only if your app benefits from precise location / Wi-Fi scans access. -->
<!-- This permission is for "precise" location data and required for Wi-Fi scans -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Required only when requesting background location access on
<!-- Required only when requesting background location / Wi-Fi scans access on
Android 10 (API level 29) and higher. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- The following two permissions are required if your app wants to receive human activity changes -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>

<!-- The following two permissions are required in order to ask and retrieve Wi-Fi scan updates -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

<!-- The following two permissions are required in order to ask and retrieve BLE scan updates -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
Expand Down
234 changes: 204 additions & 30 deletions demo/app/home/home-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ a code-behind file. The code-behind is a great place to place your view
logic, and to set up your page’s data binding.
*/

import { NavigatedData, Page, Application } from "@nativescript/core";
import { Application, NavigatedData, Page } from "@nativescript/core";

import { HomeViewModel } from "./home-view-model";

Expand All @@ -13,47 +13,99 @@ import { Resolution } from "nativescript-context-apis/activity-recognition";

import { GeolocationProvider } from "nativescript-context-apis/geolocation";
import { of, Subscription } from "rxjs";
import {
FingerprintGrouping,
WifiScanProvider,
} from "nativescript-context-apis/wifi";
import { BleScanProvider, BleScanMode } from "nativescript-context-apis/ble";

const I_BEACON_UUIDS = [
// Place your iBeacon UUIDs here to just report beacon updates!
];

const activityRecognizers = [Resolution.LOW, Resolution.MEDIUM];

let locationSubscription: Subscription;
let wifiScanSubscription: Subscription;
let bleScanSubscription: Subscription;
export function onNavigatingTo(args: NavigatedData) {
const page = <Page>args.object;

page.bindingContext = new HomeViewModel();

let preparing = true;
let locationSubscription: Subscription;
Application.on(Application.resumeEvent, () => {
if (!preparing) {
printCurrentLocation().catch((err) => {
console.error(`Could not print current location: ${err}`);
}).then(() => printLocationUpdates()
.then((subscription) => (locationSubscription = subscription))
.catch(
(err) =>
`An error occurred while getting location updates: ${err}`)
).then(() => listenToActivityChanges());
showUpdates().catch((err) =>
console.error("Could not show updates. Reason: ", err)
);
}
});

Application.on(Application.suspendEvent, () => {
if (!preparing) {
if (locationSubscription) {
locationSubscription.unsubscribe();
}
locationSubscription?.unsubscribe();
wifiScanSubscription?.unsubscribe();
bleScanSubscription?.unsubscribe();
stopListeningToChanges();
}
});

printCurrentLocation().catch((err) => {
console.error(`Could not print current location: ${err}`);
}).then(() => printLocationUpdates()
.then((subscription) => (locationSubscription = subscription))
.catch(
(err) => `An error occurred while getting location updates: ${err}`
)
).then(() => listenToActivityChanges(true))
.then(() => preparing = false);
showUpdates().then(() => (preparing = false));
}

async function showUpdates(addListeners = false): Promise<void> {
const steps: Array<() => Promise<any>> = [
() => listenToActivityChanges(addListeners),
() =>
printCurrentLocation().catch((err) => {
console.error("Could not print current location. Reason:", err);
}),
() =>
printBleScanResult().catch((err) => {
console.error(
"Could not print current nearby BLE devices. Reason:",
err
);
}),
() =>
printWifiScanResult().catch((err) => {
console.error(
"Could not print current nearby wifi APs. Reason:",
err
);
}),
() =>
printLocationUpdates()
.then((subscription) => (locationSubscription = subscription))
.catch((err) =>
console.error(
"An error occurred while getting location updates. Reason:",
err
)
),
() =>
printBleScanUpdates()
.then((subscription) => (bleScanSubscription = subscription))
.catch((err) =>
console.error(
"An error occurred while getting ble scan updates. Reason:",
err
)
),
() =>
printWifiScanUpdates()
.then((subscription) => (wifiScanSubscription = subscription))
.catch((err) =>
console.error(
"An error occurred while getting wifi scan updates. Reason:",
err
)
),
];
for (const step of steps) {
await step();
}
}

async function printCurrentLocation() {
Expand All @@ -68,6 +120,28 @@ async function printCurrentLocation() {
}
}

async function printWifiScanResult() {
const provider = contextApis.wifiScanProvider;
const ok = await prepareWifiScanProvider(provider);
if (ok) {
const fingerprint = await provider.acquireWifiFingerprint(true);
console.log(`Last wifi scan result: ${JSON.stringify(fingerprint)}`);
}
}

async function printBleScanResult() {
const provider = contextApis.bleScanProvider;
const ok = await prepareBleScanProvider(provider);
if (ok) {
const bleScanResult = await provider.acquireBleScan({
scanTime: 5000,
scanMode: BleScanMode.BALANCED,
iBeaconUuids: I_BEACON_UUIDS,
});
console.log(`Last ble scan result: ${JSON.stringify(bleScanResult)}`);
}
}

async function printLocationUpdates(): Promise<Subscription> {
const provider = contextApis.geolocationProvider;
const ok = await prepareGeolocationProvider(provider);
Expand All @@ -86,7 +160,53 @@ async function printLocationUpdates(): Promise<Subscription> {
next: (location) =>
console.log(`New location acquired!: ${JSON.stringify(location)}`),
error: (error) =>
console.error(`Location updates could not be acquired: ${error}`)
console.error(`Location updates could not be acquired: ${error}`),
});
}

async function printWifiScanUpdates(): Promise<Subscription> {
const provider = contextApis.wifiScanProvider;
const ok = await prepareWifiScanProvider(provider);

await new Promise((resolve) => setTimeout(resolve, 30000));

const stream = ok
? provider.wifiFingerprintStream({
ensureAlwaysNew: true,
grouping: FingerprintGrouping.NONE,
continueOnFailure: true,
})
: of(null);

return stream.subscribe({
next: (fingerprint) =>
console.log(
`New wifi scan result!: ${JSON.stringify(fingerprint)}`
),
error: (error) =>
console.error(`Wifi scan result could not be acquired: ${error}`),
});
}

async function printBleScanUpdates(): Promise<Subscription> {
const provider = contextApis.bleScanProvider;
const ok = await prepareBleScanProvider(provider);

const stream = ok
? provider.bleScanStream({
reportInterval: 2000,
scanMode: BleScanMode.LOW_LATENCY,
iBeaconUuids: I_BEACON_UUIDS,
})
: of(null);

return stream.subscribe({
next: (bleScanResult) =>
console.log(
`New ble scan result!: ${JSON.stringify(bleScanResult)}`
),
error: (error) =>
console.error(`Ble scan result could not be acquired: ${error}`),
});
}

Expand All @@ -96,7 +216,9 @@ export async function listenToActivityChanges(addListener = false) {
await listenToActivityChangesFor(recognizerType, addListener);
} catch (err) {
console.error(
`An error occurred while listening to ${recognizerType} res activity changes: ${JSON.stringify(err)}`
`An error occurred while listening to ${recognizerType} res activity changes: ${JSON.stringify(
err
)}`
);
}
}
Expand Down Expand Up @@ -144,7 +266,7 @@ async function listenToActivityChangesFor(
);
}

let _preparing: Promise<any>;
let _preparingGeoProv: Promise<any>;
async function prepareGeolocationProvider(
provider: GeolocationProvider
): Promise<boolean> {
Expand All @@ -154,15 +276,67 @@ async function prepareGeolocationProvider(
}

try {
if (!_preparing) {
_preparing = provider.prepare();
if (!_preparingGeoProv) {
_preparingGeoProv = provider.prepare();
}
await _preparing;
await _preparingGeoProv;
return true;
} catch (e) {
console.error(`GeolocationProvider couldn't be prepared: ${JSON.stringify(e)}`);
console.error(
`GeolocationProvider couldn't be prepared: ${JSON.stringify(e)}`
);
return false;
} finally {
_preparingGeoProv = null;
}
}

let _preparingWifiProv: Promise<any>;
async function prepareWifiScanProvider(
provider: WifiScanProvider
): Promise<boolean> {
const isReady = await provider.isReady();
if (isReady) {
return true;
}

try {
if (!_preparingWifiProv) {
_preparingWifiProv = provider.prepare();
}
await _preparingWifiProv;
return true;
} catch (e) {
console.error(
`WifiScanProvider couldn't be prepared: ${JSON.stringify(e)}`
);
return false;
} finally {
_preparingWifiProv = null;
}
}

let _preparingBleProv: Promise<any>;
async function prepareBleScanProvider(
provider: BleScanProvider
): Promise<boolean> {
const isReady = await provider.isReady();
if (isReady) {
return true;
}

try {
if (!_preparingBleProv) {
_preparingBleProv = provider.prepare();
}
await _preparingBleProv;
return true;
} catch (e) {
console.error(
`BleScanProvider couldn't be prepared: ${JSON.stringify(e)}`
);
return false;
} finally {
_preparing = null;
_preparingBleProv = null;
}
}
Loading