Skip to content

Commit

Permalink
Merge pull request #1128 from the-bay-kay/bluetooth_scanner
Browse files Browse the repository at this point in the history
☰♭  Adding a Bluetooth Scanner to dev options
  • Loading branch information
shankari authored Mar 22, 2024
2 parents 2736da7 + 295b96f commit 78b5da1
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 5 deletions.
8 changes: 6 additions & 2 deletions package.cordovabuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@
"cordova-plugin-androidx-adapter": {},
"phonegap-plugin-barcodescanner": {
"ANDROID_SUPPORT_V4_VERSION": "27.+"
}
},
"cordova-plugin-bluetooth-classic-serial-port": {},
"cordova-custom-config": {}
}
},
"dependencies": {
Expand All @@ -118,7 +120,7 @@
"cordova-plugin-app-version": "0.1.14",
"cordova-plugin-customurlscheme": "5.0.2",
"cordova-plugin-device": "2.1.0",
"cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.8.2",
"cordova-plugin-em-datacollection": "git+https://github.com/e-mission/e-mission-data-collection.git#v1.8.3",
"cordova-plugin-em-opcodeauth": "git+https://github.com/e-mission/cordova-jwt-auth.git#v1.7.2",
"cordova-plugin-em-server-communication": "git+https://github.com/e-mission/cordova-server-communication.git#v1.2.6",
"cordova-plugin-em-serversync": "git+https://github.com/e-mission/cordova-server-sync.git#v1.3.2",
Expand All @@ -132,6 +134,8 @@
"cordova-plugin-ionic-webview": "5.0.0",
"cordova-plugin-local-notification-12": "github:e-mission/cordova-plugin-local-notification-12#v0.1.4-fix-android-action",
"cordova-plugin-x-socialsharing": "6.0.4",
"cordova-plugin-bluetooth-classic-serial-port": "git+https://github.com/louisg1337/cordova-plugin-bluetooth-classic-serial-port.git",
"cordova-custom-config": "^5.1.1",
"core-js": "^2.5.7",
"enketo-core": "^6.1.7",
"enketo-transformer": "^4.0.0",
Expand Down
1 change: 1 addition & 0 deletions setup/autoreload/macos-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const os = require('os');

const nameMap = new Map([
[23, ['Sonoma', '14.3.1']],
[22, ['Ventura', '13']],
[21, ['Monterey', '12']],
[20, ['Big Sur', '11']],
Expand Down
2 changes: 1 addition & 1 deletion setup/setup_shared_native.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ sed -i -e "s|/usr/bin/env node|/usr/bin/env node --unhandled-rejections=strict|"

npx cordova prepare

EXPECTED_COUNT=23
EXPECTED_COUNT=25
INSTALLED_COUNT=`npx cordova plugin list | wc -l`
echo "Found $INSTALLED_COUNT plugins, expected $EXPECTED_COUNT"
if [ $INSTALLED_COUNT -lt $EXPECTED_COUNT ];
Expand Down
14 changes: 13 additions & 1 deletion www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"user-data": "User data",
"erase-data": "Erase data",
"dev-zone": "Developer zone",
"bluetooth-scan": "Scan for Bluetooth",
"refresh": "Refresh",
"end-trip-sync": "End trip + sync",
"check-consent": "Check consent",
Expand Down Expand Up @@ -230,6 +231,16 @@
"list-datepicker-close": "Close",
"list-datepicker-set": "Set",

"bluetooth": {
"scan-debug-title": "Bluetooth Scanner",
"scan-for-bluetooth": "Scan for Devices",
"is-scanning": "Scanning...",
"device-info": {
"id": "ID",
"name": "Name"
}
},

"service": {
"reading-server": "Reading from server...",
"reading-unprocessed-data": "Reading unprocessed data..."
Expand Down Expand Up @@ -409,7 +420,8 @@
"while-repopulating-entry": "While repopulating timeline entry: ",
"while-loading-metrics": "While loading metrics: ",
"while-log-messages": "While getting messages from the log ",
"while-max-index": "While getting max index "
"while-max-index": "While getting max index ",
"while-scanning-bluetooth": "While scanning for Bluetooth Devices: "
},
"consent-text": {
"title": "NREL OPENPATH PRIVACY POLICY/TERMS OF USE",
Expand Down
31 changes: 31 additions & 0 deletions www/js/bluetooth/BluetoothCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { Card, List } from 'react-native-paper';
import { StyleSheet } from 'react-native';

type Props = any;
const BluetoothCard = ({ device }: Props) => {
return (
<Card style={cardStyles.card}>
<Card.Title
title={`Name: ${device.name}`}
titleVariant="titleLarge"
subtitle={`ID: ${device.id}`}
left={() => <List.Icon icon={device.is_paired ? 'bluetooth' : 'bluetooth-off'} />}
/>
</Card>
);
};

export const cardStyles = StyleSheet.create({
card: {
position: 'relative',
alignSelf: 'center',
marginVertical: 10,
},
cardContent: {
flex: 1,
width: '100%',
},
});

export default BluetoothCard;
111 changes: 111 additions & 0 deletions www/js/bluetooth/BluetoothScanPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, Modal, ScrollView, SafeAreaView, View } from 'react-native';
import gatherBluetoothData from './bluetoothScanner';
import { logWarn, displayError, displayErrorMsg } from '../plugin/logger';
import BluetoothCard from './BluetoothCard';
import { Appbar, useTheme, Button } from 'react-native-paper';

/**
* The implementation of this scanner page follows the design of
* `www/js/survey/enketo/EnketoModal.tsx`!
*
* Future work may include refractoring these files to be implementations of a
* single base "pop-up page" component
*/

const BluetoothScanPage = ({ ...props }: any) => {
const { t } = useTranslation();
const [logs, setLogs] = useState<string[]>([]);
const [isScanning, setIsScanning] = useState(false);
const { colors } = useTheme();

// Function to run Bluetooth Classic test and update logs
const runBluetoothTest = async () => {
// Classic not currently supported on iOS
if (window['cordova'].platformId == 'ios') {
displayErrorMsg('Sorry, iOS is not supported!', 'OSError');
return;
}

try {
let response = await window['cordova'].plugins.BEMDataCollection.bluetoothScanPermissions();
if (response != 'OK') {
displayErrorMsg('Please Enable Bluetooth!', 'Insufficient Permissions');
return;
}
} catch (e) {
displayError(e, 'Insufficient Permissions');
return;
}

try {
setIsScanning(true);
const newLogs = await gatherBluetoothData(t);
setLogs(newLogs);
} catch (error) {
logWarn(error);
} finally {
setIsScanning(false);
}
};

const BluetoothCardList = ({ devices }) => (
<div>
{devices.map((device) => {
if (device) {
return <BluetoothCard device={device} />;
}
return null;
})}
</div>
);

const BlueScanContent = () => (
<div style={{ height: '100%' }}>
<Appbar.Header
statusBarHeight={0}
elevated={true}
style={{ height: 46, backgroundColor: colors.surface }}>
<Appbar.BackAction
onPress={() => {
props.onDismiss?.();
}}
/>
<Appbar.Content title={t('bluetooth.scan-debug-title')} titleStyle={{ fontSize: 17 }} />
</Appbar.Header>
<View style={s.btnContainer}>
<Button mode="elevated" onPress={runBluetoothTest} textColor={colors.primary} style={s.btn}>
{isScanning ? t('bluetooth.is-scanning') : t('bluetooth.scan-for-bluetooth')}
</Button>
</View>
<BluetoothCardList devices={logs} />
</div>
);

return (
<>
<Modal {...props} animationType="slide">
<SafeAreaView style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ flex: 1 }}>
<BlueScanContent />
</ScrollView>
</SafeAreaView>
</Modal>
</>
);
};

const s = StyleSheet.create({
btnContainer: {
padding: 16,
justifyContent: 'center',
},
btn: {
height: 38,
fontSize: 11,
margin: 4,
},
});

export default BluetoothScanPage;
54 changes: 54 additions & 0 deletions www/js/bluetooth/bluetoothScanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { logDebug, displayError } from '../plugin/logger';
import { BluetoothClassicDevice } from '../types/bluetoothTypes';

/**
* gatherBluetoothData scans for viewable Bluetooth Classic Devices
* @param t is the i18next translation function
* @returns an array of strings containing device data, formatted ['ID: id Name: name']
*/
export default function gatherBluetoothData(t): Promise<string[]> {
return new Promise((resolve, reject) => {
logDebug('Running bluetooth discovery test!');

// Device List "I/O"
function updatePairingStatus(pairingType: boolean, devices: Array<BluetoothClassicDevice>) {
devices.forEach((device) => {
device.is_paired = pairingType;
});
return devices;
}

// Plugin Calls
const unpairedDevicesPromise = new Promise((res, rej) => {
window['bluetoothClassicSerial'].discoverUnpaired(
(devices: Array<BluetoothClassicDevice>) => {
res(updatePairingStatus(false, devices));
},
(e: Error) => {
displayError(e, 'Error');
rej(e);
},
);
});

const pairedDevicesPromise = new Promise((res, rej) => {
window['bluetoothClassicSerial'].list(
(devices: Array<BluetoothClassicDevice>) => {
res(updatePairingStatus(true, devices));
},
(e: Error) => {
displayError(e, 'Error');
rej(e);
},
);
});

Promise.all([unpairedDevicesPromise, pairedDevicesPromise])
.then((logs: Array<any>) => {
resolve(logs.flat());
})
.catch((e) => {
reject(e);
});
});
}
23 changes: 23 additions & 0 deletions www/js/control/BluetoothScanSettingRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState } from 'react';
import SettingRow from './SettingRow';
import BluetoothScanPage from '../bluetooth/BluetoothScanPage';

const BluetoothScanSettingRow = ({}) => {
const [bluePageVisible, setBluePageVisible] = useState<boolean>(false);

async function openPopover() {
setBluePageVisible(true);
}

return (
<>
<SettingRow
textKey="control.bluetooth-scan"
iconName="bluetooth-settings"
action={openPopover}></SettingRow>
<BluetoothScanPage visible={bluePageVisible} onDismiss={() => setBluePageVisible(false)} />
</>
);
};

export default BluetoothScanSettingRow;
3 changes: 2 additions & 1 deletion www/js/control/ProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ExpansionSection from './ExpandMenu';
import SettingRow from './SettingRow';
import ControlDataTable from './ControlDataTable';
import DemographicsSettingRow from './DemographicsSettingRow';
import BluetoothScanSettingRow from './BluetoothScanSettingRow';
import PopOpCode from './PopOpCode';
import ReminderTime from './ReminderTime';
import useAppConfig from '../useAppConfig';
Expand Down Expand Up @@ -433,8 +434,8 @@ const ProfileSettings = () => {
textKey="control.email-log"
iconName="email"
action={() => sendEmail('loggerDB')}></SettingRow>

<ExpansionSection sectionTitle="control.dev-zone">
<BluetoothScanSettingRow />
<SettingRow
textKey="control.refresh"
iconName="refresh"
Expand Down
8 changes: 8 additions & 0 deletions www/js/types/bluetoothTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Device data, as defined in BluetoothClassicSerial's docs
export type BluetoothClassicDevice = {
class: number;
id: string;
address: string;
name: string;
is_paired?: boolean; // We keep track of this, BCS doesn't
};

0 comments on commit 78b5da1

Please sign in to comment.