Skip to content

Commit

Permalink
Merge remote-tracking branch 'other/refactoring_timelinecontext' into…
Browse files Browse the repository at this point in the history
… configurable-app-dashboard
  • Loading branch information
jiji14 committed Apr 18, 2024
2 parents 21adb20 + 91af26c commit 84f8a51
Show file tree
Hide file tree
Showing 19 changed files with 515 additions and 188 deletions.
7 changes: 2 additions & 5 deletions www/__tests__/timelineHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ afterAll(() => {

describe('useGeojsonForTrip', () => {
it('work with an empty input', () => {
const testVal = useGeojsonForTrip({} as any, {} as any);
const testVal = useGeojsonForTrip({} as any);
expect(testVal).toBeFalsy;
});

Expand All @@ -43,10 +43,7 @@ describe('useGeojsonForTrip', () => {
};

it('works without labelMode flag', () => {
const testValue = useGeojsonForTrip(
mockTLH.mockCompDataTwo.phone_data[1].data,
mockTLH.mockLabelOptions,
) as GeoJSONData;
const testValue = useGeojsonForTrip(mockTLH.mockCompDataTwo.phone_data[1].data) as GeoJSONData;
expect(testValue).toBeTruthy;
checkGeojson(testValue);
expect(testValue.data.features.length).toBe(3);
Expand Down
6 changes: 5 additions & 1 deletion www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@
"reminders-time-of-day": "Time of Day for Reminders ({{time}})",
"upcoming-notifications": "Upcoming Notifications",
"dummy-notification": "Dummy Notification in 5 Seconds",
"log-out": "Log Out"
"log-out": "Log Out",
"refresh-app-config": "Refresh App Configuration",
"current-version": "Current version: {{version}}",
"refreshing-app-config": "Refreshing app configuration, please wait...",
"already-up-to-date": "Already up to date!"
},

"general-settings": {
Expand Down
39 changes: 37 additions & 2 deletions www/js/TimelineContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ import {
readUnprocessedTrips,
unprocessedLabels,
unprocessedNotes,
updateUnprocessedBleScans,
unprocessedBleScans,
updateAllUnprocessedInputs,
updateLocalUnprocessedInputs,
isoDateRangeToTsRange,
} from './diary/timelineHelper';
import { getPipelineRangeTs } from './services/commHelper';
import { getNotDeletedCandidates, mapInputsToTimelineEntries } from './survey/inputMatcher';
import {
getNotDeletedCandidates,
mapBleScansToTimelineEntries,
mapInputsToTimelineEntries,
} from './survey/inputMatcher';
import { publish } from './customEventHandler';
import { EnketoUserInputEntry } from './survey/enketo/enketoHelper';
import { VehicleIdentity } from './types/appConfigTypes';

// initial query range is the past 7 days, including today
const today = DateTime.now().toISODate().substring(0, 10);
Expand All @@ -32,7 +39,11 @@ type ContextProps = {
timelineLabelMap: TimelineLabelMap | null;
userInputFor: (tlEntry: TimelineEntry) => UserInputMap | undefined;
notesFor: (tlEntry: TimelineEntry) => UserInputEntry[] | undefined;
labelFor: (tlEntry: TimelineEntry, labelType: MultilabelKey) => LabelOption | undefined;
labelFor: (
tlEntry: TimelineEntry,
labelType: MultilabelKey,
) => VehicleIdentity | LabelOption | undefined;
confirmedModeFor: (tlEntry: TimelineEntry) => LabelOption | undefined;
addUserInputToEntry: (oid: string, userInput: any, inputType: 'label' | 'note') => void;
pipelineRange: TimestampRange | null;
queriedDateRange: [string, string] | null; // YYYY-MM-DD format
Expand Down Expand Up @@ -60,6 +71,7 @@ export const useTimelineContext = (): ContextProps => {
const [timelineIsLoading, setTimelineIsLoading] = useState<string | false>('replace');
const [timelineLabelMap, setTimelineLabelMap] = useState<TimelineLabelMap | null>(null);
const [timelineNotesMap, setTimelineNotesMap] = useState<TimelineNotesMap | null>(null);
const [timelineBleMap, setTimelineBleMap] = useState<any>(null);
const [refreshTime, setRefreshTime] = useState<Date | null>(null);

// initialization, once the appConfig is loaded
Expand Down Expand Up @@ -128,6 +140,11 @@ export const useTimelineContext = (): ContextProps => {
setTimelineLabelMap(newTimelineLabelMap);
setTimelineNotesMap(newTimelineNotesMap);

if (appConfig.vehicle_identities?.length) {
const newTimelineBleMap = mapBleScansToTimelineEntries(allEntries, appConfig);
setTimelineBleMap(newTimelineBleMap);
}

publish('applyLabelTabFilters', {
timelineMap,
timelineLabelMap: newTimelineLabelMap,
Expand All @@ -142,6 +159,15 @@ export const useTimelineContext = (): ContextProps => {
logDebug(`Timeline: After updating unprocessedInputs,
unprocessedLabels = ${JSON.stringify(unprocessedLabels)};
unprocessedNotes = ${JSON.stringify(unprocessedNotes)}`);
if (appConfig.vehicle_identities?.length) {
await updateUnprocessedBleScans({
start_ts: pipelineRange.start_ts,
end_ts: Date.now() / 1000,
});
logDebug(`Timeline: After updating unprocessedBleScans,
unprocessedBleScans = ${JSON.stringify(unprocessedBleScans)};
`);
}
setPipelineRange(pipelineRange);
} catch (e) {
displayError(e, t('errors.while-loading-pipeline-range'));
Expand Down Expand Up @@ -272,6 +298,14 @@ export const useTimelineContext = (): ContextProps => {
return chosenLabel ? labelOptionByValue(chosenLabel, labelType) : undefined;
};

/**
* @param tlEntry The trip or place object to get the confirmed mode for
* @returns Confirmed mode, which could be a vehicle identity as determined by Bluetooth scans,
* or the label option from a user-given 'MODE' label, or undefined if neither exists.
*/
const confirmedModeFor = (tlEntry: TimelineEntry) =>
timelineBleMap?.[tlEntry._id.$oid] || labelFor(tlEntry, 'MODE');

function addUserInputToEntry(oid: string, userInput: any, inputType: 'label' | 'note') {
const tlEntry = timelineMap?.get(oid);
if (!pipelineRange || !tlEntry)
Expand Down Expand Up @@ -329,6 +363,7 @@ export const useTimelineContext = (): ContextProps => {
userInputFor,
labelFor,
notesFor,
confirmedModeFor,
addUserInputToEntry,
};
};
Expand Down
46 changes: 35 additions & 11 deletions www/js/bluetooth/BluetoothCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const BluetoothCard = ({ device, isClassic, isScanningBLE }: Props) => {
bgColor = device.in_range ? `rgba(200,250,200,1)` : `rgba(250,200,200,1)`;
}

async function fakeMonitorCallback() {
async function fakeMonitorCallback(state: String) {
// 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...
Expand All @@ -33,18 +33,29 @@ const BluetoothCard = ({ device, isClassic, isScanningBLE }: Props) => {
window['cordova'].plugins.locationManager.getDelegate().didDetermineStateForRegion({
region: deviceWithoutResult,
eventType: 'didDetermineStateForRegion',
state: 'CLRegionStateInside',
state: state,
});
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 };
const deviceWithBeacons = { ...device };
deviceWithBeacons.monitorResult = undefined;
deviceWithBeacons.rangeResult = undefined;
const beacons = [
{
uuid: device.uuid,
major: device.major | 4567,
minor: device.minor | 1945,
proximity: 'ProximityNear',
accuracy: Math.random() * 1.33,
rssi: Math.random() * -62,
},
];
deviceWithBeacons.minor = device.minor | 4567;
deviceWithBeacons.minor = device.minor | 4567;
window['cordova'].plugins.locationManager.getDelegate().didRangeBeaconsInRegion({
region: deviceWithMajorMinor,
region: deviceWithBeacons,
beacons: beacons,
eventType: 'didRangeBeaconsInRegion',
state: 'CLRegionStateInside',
});
Expand All @@ -65,9 +76,22 @@ const BluetoothCard = ({ device, isClassic, isScanningBLE }: Props) => {
<Text style={{ backgroundColor: colors.secondaryContainer }} variant="bodyMedium">
{device.rangeResult}
</Text>
<Button mode="elevated" onPress={fakeMonitorCallback}>
Fake callback
</Button>
<Text
style={{ backgroundColor: colors.danger, color: colors.background }}
variant="bodyLarge">
Simulate by sending UI transitions
</Text>
<Card.Actions style={{ backgroundColor: colors.danger, color: colors.background }}>
<Button mode="elevated" onPress={() => fakeMonitorCallback('CLRegionStateInside')}>
Region Enter
</Button>
<Button mode="elevated" onPress={fakeRangeCallback}>
Range
</Button>
<Button mode="elevated" onPress={() => fakeMonitorCallback('CLRegionStateOutside')}>
Region Exit
</Button>
</Card.Actions>
</Card.Content>
</Card>
);
Expand Down
138 changes: 115 additions & 23 deletions www/js/bluetooth/BluetoothScanPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DateTime } from 'luxon';
import { StyleSheet, Modal, ScrollView, SafeAreaView, View, Text } from 'react-native';
import { gatherBluetoothClassicData } from './bluetoothScanner';
import { logWarn, displayError, displayErrorMsg, logDebug } from '../plugin/logger';
Expand All @@ -11,6 +12,7 @@ import {
BluetoothClassicDevice,
BLEDeviceList,
} from '../types/bluetoothDevices';
import { forceTransition } from '../control/ControlCollectionHelper';

/**
* The implementation of this scanner page follows the design of
Expand Down Expand Up @@ -90,10 +92,22 @@ const BluetoothScanPage = ({ ...props }: any) => {
...prevDevices,
[uuid]: {
...prevDevices[uuid],
monitorResult: result,
monitorResult: status ? result : undefined,
rangeResult: undefined,
in_range: status,
},
}));
let { monitorResult: _, in_range: _, ...noResultDevice } = sampleBLEDevices[uuid];
window['cordova']?.plugins?.BEMDataCollection.mockBLEObjects(
status ? 'REGION_ENTER' : 'REGION_EXIT',
uuid,
undefined,
undefined,
1,
);
if (!status) {
forceTransition('BLE_BEACON_LOST');
}
}

function setRangeStatus(uuid: string, result: string) {
Expand All @@ -104,6 +118,57 @@ const BluetoothScanPage = ({ ...props }: any) => {
rangeResult: result,
},
}));
// we don't want to exclude monitorResult and rangeResult from the values
// that we save because they are the current or previous result, just
// in a different format
// https://stackoverflow.com/a/34710102
let {
monitorResult: _,
rangeResult: _,
in_range: _,
...noResultDevice
} = sampleBLEDevices[uuid];
let parsedResult = JSON.parse(result);
parsedResult.beacons.forEach((beacon) => {
window['cordova']?.plugins?.BEMDataCollection.mockBLEObjects(
'RANGE_UPDATE',
uuid,
beacon.major,
beacon.minor,
5,
);
});
// we only check for the transition on "real" callbacks to avoid excessive
// spurious callbacks on android
if (parsedResult.beacons.length > 0) {
// if we have received 3 range responses for the same beacon in the
// last 5 minutes, we generate the transition. we read without metadata
// (last param)
let nowSec = DateTime.now().toUnixInteger();
let tq = { key: 'write_ts', startTs: nowSec - 5 * 60, endTs: nowSec };
let readBLEReadingsPromise = window[
'cordova'
]?.plugins?.BEMUserCache.getSensorDataForInterval('background/bluetooth_ble', tq, false);
readBLEReadingsPromise.then((bleResponses) => {
// we add 5 entries at a time, so if we want 3 button presses,
// we really want 15 entries
let lastFifteenResponses = bleResponses.slice(0, 15);
if (!lastFifteenResponses.every((x) => x.eventType == 'RANGE_UPDATE')) {
console.log(
'Last three entries ' +
lastFifteenResponses.map((x) => x.eventType) +
' are not all RANGE_UPDATE, skipping transition',
);
return;
}

forceTransition('BLE_BEACON_FOUND');
});
}
}

async function simulateLocation(state: String) {
forceTransition(state);
}

// BLE LOGIC
Expand All @@ -127,18 +192,21 @@ const BluetoothScanPage = ({ ...props }: any) => {
window['cordova'].plugins.locationManager.appendToDeviceLog(
'[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();
if (pluginResult.state == 'CLRegionStateInside') {
const beaconRegion = new window['cordova'].plugins.locationManager.BeaconRegion(
STATIC_ID,
pluginResult.region.uuid,
pluginResult.region.major,
pluginResult.region.minor,
);
console.log('About to start ranging beacons for region ', beaconRegion);
window['cordova'].plugins.locationManager
.startRangingBeaconsInRegion(beaconRegion)
.fail(function (e) {
logWarn(e);
})
.done();
}
};

delegate.didStartMonitoringForRegion = function (pluginResult) {
Expand Down Expand Up @@ -317,19 +385,43 @@ const BluetoothScanPage = ({ ...props }: any) => {
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)}
/>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TextInput
label="Major (optional)"
value={newMajor || ''}
onChangeText={(t) => setNewMajor(t)}
/>
<TextInput
label="Minor (optional)"
value={newMinor || ''}
onChangeText={(t) => setNewMinor(t)}
/>
</View>
<Button disabled={!newUUID} onPress={() => addNewUUID(newUUID, newMajor, newMinor)}>
Add New Beacon To Scan
</Button>
<View
style={{
flexDirection: 'column',
alignItems: 'center',
backgroundColor: colors.danger,
color: colors.background,
}}>
<Text
style={{ backgroundColor: colors.danger, color: colors.background }}
variant="bodyLarge">
Simulate by sending UI transitions
</Text>

<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Button mode="elevated" onPress={() => simulateLocation('EXITED_GEOFENCE')}>
Geofence exit
</Button>
<Button mode="elevated" onPress={() => simulateLocation('STOPPED_MOVING')}>
Stopped Moving
</Button>
</View>
</View>
</SafeAreaView>
</Modal>
</>
Expand Down
2 changes: 1 addition & 1 deletion www/js/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const NavBar = ({ children, isLoading }: NavBarProps) => {
{children}
<View style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}>
<ProgressBar
visible={isLoading}
visible={Boolean(isLoading)}
indeterminate={true}
color={colors.primary}
style={{ height: 2 }}
Expand Down
Loading

0 comments on commit 84f8a51

Please sign in to comment.