Skip to content

Commit

Permalink
Merge pull request #1140 from shankari/simulate_ble_scans
Browse files Browse the repository at this point in the history
Simulate BLE scans from the phone UI
  • Loading branch information
shankari authored Mar 29, 2024
2 parents d0ed16b + 7c70840 commit 76839de
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 19 deletions.
46 changes: 42 additions & 4 deletions www/js/bluetooth/BluetoothCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Card, List, useTheme } from 'react-native-paper';
import { Card, List, Text, Button, useTheme } from 'react-native-paper';
import { StyleSheet } from 'react-native';

type Props = any;
Expand All @@ -23,14 +23,52 @@ const BluetoothCard = ({ device, isClassic, isScanningBLE }: Props) => {
bgColor = device.in_range ? `rgba(200,250,200,1)` : `rgba(250,200,200,1)`;
}

async function fakeMonitorCallback() {
// If we don't do this, the results start accumulating in the device object
// first call, we put a result into the device
// second call, the device already has a result, so we put another one in...
const deviceWithoutResult = { ...device };
deviceWithoutResult.monitorResult = undefined;
deviceWithoutResult.rangeResult = undefined;
window['cordova'].plugins.locationManager.getDelegate().didDetermineStateForRegion({
region: deviceWithoutResult,
eventType: 'didDetermineStateForRegion',
state: 'CLRegionStateInside',
});
let timer: ReturnType<typeof setTimeout> = setTimeout(fakeRangeCallback, 500);
}

async function fakeRangeCallback() {
// If we don't do this, the results start accumulating in the device object
// first call, we put a result into the device
// second call, the device already has a result, so we put another one in...
const deviceWithMajorMinor = { ...device, major: 1234, minor: 4567 };
window['cordova'].plugins.locationManager.getDelegate().didRangeBeaconsInRegion({
region: deviceWithMajorMinor,
eventType: 'didRangeBeaconsInRegion',
state: 'CLRegionStateInside',
});
}

return (
<Card style={{ backgroundColor: bgColor, ...cardStyles.card }}>
<Card.Title
title={`Name: ${device.identifier}`}
titleVariant="titleLarge"
subtitle={`UUID: ...${device.uuid.slice(-13)}`} // e.g.,
title={`UUID: ${device.uuid}`}
titleVariant="titleSmall"
subtitle={`Configured major ${device.major} and minor ${device.minor}`} // e.g.,
left={() => <List.Icon icon={device.in_range ? 'access-point' : 'access-point-off'} />}
/>
<Card.Content>
<Text style={{ backgroundColor: colors.primaryContainer }} variant="bodyMedium">
{device.monitorResult}
</Text>
<Text style={{ backgroundColor: colors.secondaryContainer }} variant="bodyMedium">
{device.rangeResult}
</Text>
<Button mode="elevated" onPress={fakeMonitorCallback}>
Fake callback
</Button>
</Card.Content>
</Card>
);
};
Expand Down
95 changes: 81 additions & 14 deletions www/js/bluetooth/BluetoothScanPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StyleSheet, Modal, ScrollView, SafeAreaView, View, Text } from 'react-n
import { gatherBluetoothClassicData } from './bluetoothScanner';
import { logWarn, displayError, displayErrorMsg, logDebug } from '../plugin/logger';
import BluetoothCard from './BluetoothCard';
import { Appbar, useTheme, Button } from 'react-native-paper';
import { Appbar, useTheme, TextInput, Button } from 'react-native-paper';
import {
BLEBeaconDevice,
BLEPluginCallback,
Expand All @@ -21,17 +21,19 @@ import {
*/

const BluetoothScanPage = ({ ...props }: any) => {
const STATIC_ID = 'edu.berkeley.eecs.emission';

const { t } = useTranslation();
const [bluetoothClassicList, setBluetoothClassicList] = useState<BluetoothClassicDevice[]>([]);
const [sampleBLEDevices, setSampleBLEDevices] = useState<BLEDeviceList>({
'426C7565-4368-6172-6D42-6561636F6E74': {
identifier: 'Katie_BLEBeacon',
identifier: STATIC_ID,
minor: 4949,
major: 3838,
in_range: false,
},
'426C7565-4368-6172-6D42-6561636F6E73': {
identifier: 'Louis-Beacon',
identifier: STATIC_ID,
minor: 4949,
major: 3838,
in_range: false,
Expand All @@ -40,6 +42,9 @@ const BluetoothScanPage = ({ ...props }: any) => {
const [isScanningClassic, setIsScanningClassic] = useState(false);
const [isScanningBLE, setIsScanningBLE] = useState(false);
const [isClassic, setIsClassic] = useState(false);
const [newUUID, setNewUUID] = useState<String>(null);
const [newMajor, setNewMajor] = useState<number>(undefined);
const [newMinor, setNewMinor] = useState<number>(undefined);
const { colors } = useTheme();

// Flattens the `sampleBeacons` into an array of BLEBeaconDevices
Expand Down Expand Up @@ -80,16 +85,27 @@ const BluetoothScanPage = ({ ...props }: any) => {
}
}

function setRangeStatus(uuid: string, status: boolean) {
function setMonitorStatus(uuid: string, result: string, status: boolean) {
setSampleBLEDevices((prevDevices) => ({
...prevDevices,
[uuid]: {
...prevDevices[uuid],
monitorResult: result,
in_range: status,
},
}));
}

function setRangeStatus(uuid: string, result: string) {
setSampleBLEDevices((prevDevices) => ({
...prevDevices,
[uuid]: {
...prevDevices[uuid],
rangeResult: result,
},
}));
}

// BLE LOGIC
async function startBeaconScanning() {
setIsScanningBLE(true);
Expand All @@ -99,17 +115,30 @@ const BluetoothScanPage = ({ ...props }: any) => {
delegate.didDetermineStateForRegion = function (pluginResult: BLEPluginCallback) {
// `stateInside`is returned when the user enters the beacon region
// `StateOutside` is either (i) left region, or (ii) started scanner (outside region)
const pluginResultStr = JSON.stringify(pluginResult, null, 2);
if (pluginResult.state == 'CLRegionStateInside') {
// need toUpperCase(), b/c callback returns with only lowercase values...
setRangeStatus(pluginResult.region.uuid.toUpperCase(), true);
setMonitorStatus(pluginResult.region.uuid.toUpperCase(), pluginResultStr, true);
} else if (pluginResult.state == 'CLRegionStateOutside') {
setRangeStatus(pluginResult.region.uuid.toUpperCase(), false);
setMonitorStatus(pluginResult.region.uuid.toUpperCase(), pluginResultStr, false);
}
logDebug('[BLE] didDetermineStateForRegion');
logDebug(JSON.stringify(pluginResult, null, 2));
logDebug(pluginResultStr);
window['cordova'].plugins.locationManager.appendToDeviceLog(
'[DOM] didDetermineStateForRegion: ' + JSON.stringify(pluginResult, null, 2),
'[DOM] didDetermineStateForRegion: ' + pluginResultStr,
);
const beaconRegion = new window['cordova'].plugins.locationManager.BeaconRegion(
STATIC_ID,
pluginResult.region.uuid,
pluginResult.region.major,
pluginResult.region.minor,
);
window['cordova'].plugins.locationManager
.startRangingBeaconsInRegion(beaconRegion)
.fail(function (e) {
logWarn(e);
})
.done();
};

delegate.didStartMonitoringForRegion = function (pluginResult) {
Expand All @@ -120,7 +149,9 @@ const BluetoothScanPage = ({ ...props }: any) => {
delegate.didRangeBeaconsInRegion = function (pluginResult) {
// Not seeing this called...
logDebug('[BLE] didRangeBeaconsInRegion');
logDebug(JSON.stringify(pluginResult));
const pluginResultStr = JSON.stringify(pluginResult, null, 2);
logDebug(pluginResultStr);
setRangeStatus(pluginResult.region.uuid.toUpperCase(), pluginResultStr);
};

window['cordova'].plugins.locationManager.setDelegate(delegate);
Expand All @@ -131,7 +162,7 @@ const BluetoothScanPage = ({ ...props }: any) => {
// Need UUID value on iOS only, not Android (2nd parameter)
// https://stackoverflow.com/questions/38580410/how-to-scan-all-nearby-ibeacons-using-coordova-based-hybrid-application
const beaconRegion = new window['cordova'].plugins.locationManager.BeaconRegion(
sampleBeacon.identifier,
STATIC_ID,
sampleBeacon.uuid,
sampleBeacon.major,
sampleBeacon.minor,
Expand All @@ -149,9 +180,9 @@ const BluetoothScanPage = ({ ...props }: any) => {
setIsScanningBLE(false);

beaconsToArray().forEach((sampleBeacon: BLEBeaconDevice) => {
setRangeStatus(sampleBeacon.uuid, false); // "zero out" the beacons
setMonitorStatus(sampleBeacon.uuid, false); // "zero out" the beacons
const beaconRegion = new window['cordova'].plugins.locationManager.BeaconRegion(
sampleBeacon.identifier,
STATIC_ID,
sampleBeacon.uuid,
sampleBeacon.major,
sampleBeacon.minor,
Expand All @@ -169,14 +200,30 @@ const BluetoothScanPage = ({ ...props }: any) => {
setIsClassic(!isClassic);
};

// Add a beacon with the new UUID to the list of BLE devices to scan
function addNewUUID(newUUID: string, newMajor: number, newMinor: number) {
console.log('Before adding UUID ' + newUUID + ' entries = ' + sampleBLEDevices);
const devicesWithAddition = { ...sampleBLEDevices };
devicesWithAddition[newUUID] = {
identifier: STATIC_ID,
minor: newMajor,
major: newMinor,
in_range: false,
};
setSampleBLEDevices(devicesWithAddition);
setNewUUID(null);
setNewMajor(undefined);
setNewMinor(undefined);
}

const BluetoothCardList = ({ devices }) => {
if (isClassic) {
// When in classic mode, render devices as normal
return (
<div>
{devices.map((device) => {
if (device) {
return <BluetoothCard device={device} isClassic={isClassic} />;
return <BluetoothCard device={device} isClassic={isClassic} key={device.id} />;
}
return null;
})}
Expand All @@ -188,7 +235,9 @@ const BluetoothScanPage = ({ ...props }: any) => {
<div>
{beaconsAsArray.map((beacon) => {
if (beacon) {
return <BluetoothCard device={beacon} isScanningBLE={isScanningBLE} />;
return (
<BluetoothCard device={beacon} isScanningBLE={isScanningBLE} key={beacon.uuid} />
);
}
})}
</div>
Expand Down Expand Up @@ -263,6 +312,24 @@ const BluetoothScanPage = ({ ...props }: any) => {
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ flex: 1 }}>
<BlueScanContent />
</ScrollView>
<TextInput
label="New UUID (mandatory)"
value={newUUID || ''}
onChangeText={(t) => setNewUUID(t.toUpperCase())}
/>
<TextInput
label="Major (optional)"
value={newMajor || ''}
onChangeText={(t) => setNewMajor(t)}
/>
<TextInput
label="Minor (optional)"
value={newMinor || ''}
onChangeText={(t) => setNewMinor(t)}
/>
<Button disabled={!newUUID} onPress={() => addNewUUID(newUUID, newMajor, newMinor)}>
Add New Beacon To Scan
</Button>
</SafeAreaView>
</Modal>
</>
Expand Down
9 changes: 8 additions & 1 deletion www/js/types/BluetoothDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ export type BLEBeaconDevice = {
type_name?: string; // e.g., "BeaconRegion"; used for callback
};
export type BLEDeviceList = {
[key: string]: { identifier: string; minor: number; major: number; in_range: boolean };
[key: string]: {
identifier: string;
minor: number;
major: number;
monitorResult: string;
rangeResult: string;
in_range: boolean;
};
};

export type BLEPluginCallback = {
Expand Down

0 comments on commit 76839de

Please sign in to comment.