Calibration tutorial (#687)

This commit is contained in:
Uriel
2023-05-19 19:19:09 +02:00
committed by GitHub
parent 8bc8be9b90
commit d652c3eb9b
11 changed files with 297 additions and 12 deletions

View File

@@ -147,6 +147,9 @@ tracker-infos-custom_name = Custom Name
tracker-infos-url = Tracker URL
tracker-infos-version = Firmware Version
tracker-infos-hardware_rev = Hardware Revision
tracker-infos-hardware_identifier = Hardware ID
tracker-infos-imu = IMU Sensor
tracker-infos-board_type = Main board
## Tracker settings
tracker-settings-back = Go back to trackers list
@@ -523,6 +526,16 @@ onboarding-connect_tracker-connected_trackers = { $amount ->
} connected
onboarding-connect_tracker-next = I connected all my trackers
## Tracker calibration tutorial
onboarding-calibration_tutorial = IMU Calibration Tutorial
onboarding-calibration_tutorial-subtitle = This will help reduce tracker drifting!
onboarding-calibration_tutorial-description = Every time you turn on your trackers, they need to rest for a moment on a flat surface to calibrate. Let's do the same thing by clicking the "Calibrate" button, <b>do not move them!</b>
onboarding-calibration_tutorial-calibrate = I placed my trackers on the table
onboarding-calibration_tutorial-status-waiting = Waiting for you
onboarding-calibration_tutorial-status-calibrating = Calibrating
onboarding-calibration_tutorial-status-success = Nice!
onboarding-calibration_tutorial-status-error = The tracker was moved
## Tracker assignment setup
onboarding-assign_trackers-back = Go Back to Wi-Fi Credentials
onboarding-assign_trackers-title = Assign trackers

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -43,6 +43,7 @@ import { VMCSettings } from './components/settings/pages/VMCSettings';
import { MountingChoose } from './components/onboarding/pages/mounting/MountingChoose';
import { ProportionsChoose } from './components/onboarding/pages/body-proportions/ProportionsChoose';
import { LogicalSize, appWindow } from '@tauri-apps/api/window';
import { CalibrationTutorialPage } from './components/onboarding/pages/CalibrationTutorial';
function Layout() {
const { loading } = useConfig();
@@ -93,6 +94,10 @@ function Layout() {
<Route path="home" element={<HomePage />} />
<Route path="wifi-creds" element={<WifiCredsPage />} />
<Route path="connect-trackers" element={<ConnectTrackersPage />} />
<Route
path="calibration-tutorial"
element={<CalibrationTutorialPage />}
/>
<Route path="trackers-assign" element={<TrackersAssignPage />} />
<Route path="enter-vr" element={<EnterVRPage />} />
<Route path="mounting/choose" element={<MountingChoose />}></Route>

View File

@@ -0,0 +1,165 @@
import { Localized, useLocalization } from '@fluent/react';
import { useOnboarding } from '../../../hooks/onboarding';
import { Button } from '../../commons/Button';
import { Typography } from '../../commons/Typography';
import { useMemo, useState } from 'react';
import { SkipSetupWarningModal } from '../SkipSetupWarningModal';
import { SkipSetupButton } from '../SkipSetupButton';
import { ProgressBar } from '../../commons/ProgressBar';
import { LoaderIcon, SlimeState } from '../../commons/icon/LoaderIcon';
import { useCountdown } from '../../../hooks/countdown';
import classNames from 'classnames';
export enum CalibrationStatus {
SUCCESS,
CALIBRATING,
WAITING,
ERROR,
}
export const IMU_CALIBRATION_TIME = 4;
export function CalibrationTutorialPage() {
const { l10n } = useLocalization();
const { applyProgress, skipSetup } = useOnboarding();
const [skipWarning, setSkipWarning] = useState(false);
const [calibrationStatus, setCalibrationStatus] = useState(
CalibrationStatus.WAITING
);
const { timer, isCounting, startCountdown } = useCountdown({
duration: IMU_CALIBRATION_TIME,
onCountdownEnd: () => setCalibrationStatus(CalibrationStatus.SUCCESS),
});
const progressBarClass = useMemo(() => {
switch (calibrationStatus) {
case CalibrationStatus.ERROR:
return 'bg-status-critical';
case CalibrationStatus.SUCCESS:
return 'bg-status-success';
}
}, [calibrationStatus]);
const slimeStatus = useMemo(() => {
switch (calibrationStatus) {
case CalibrationStatus.CALIBRATING:
return SlimeState.JUMPY;
case CalibrationStatus.ERROR:
return SlimeState.SAD;
default:
return SlimeState.HAPPY;
}
}, [calibrationStatus]);
const progressText = useMemo(() => {
switch (calibrationStatus) {
case CalibrationStatus.CALIBRATING:
return l10n.getString(
'onboarding-calibration_tutorial-status-calibrating'
);
case CalibrationStatus.ERROR:
return l10n.getString('onboarding-calibration_tutorial-status-error');
case CalibrationStatus.SUCCESS:
return l10n.getString('onboarding-calibration_tutorial-status-success');
case CalibrationStatus.WAITING:
return l10n.getString('onboarding-calibration_tutorial-status-waiting');
}
}, [calibrationStatus, l10n]);
applyProgress(0.45);
return (
<>
<div className="flex flex-col gap-5 h-full items-center w-full justify-center relative">
<SkipSetupButton
visible={true}
modalVisible={skipWarning}
onClick={() => setSkipWarning(true)}
></SkipSetupButton>
<div className="flex w-full h-full justify-center px-20 gap-14">
<div className="flex gap-8 self-center">
<div className="flex flex-col max-w-md gap-3">
<div>
<Typography variant="main-title">
{l10n.getString('onboarding-calibration_tutorial')}
</Typography>
<Typography variant="vr-accessible" italic>
{l10n.getString('onboarding-calibration_tutorial-subtitle')}
</Typography>
</div>
<Localized
id="onboarding-calibration_tutorial-description"
elems={{ b: <b></b> }}
>
<Typography color="secondary">
Description on calibration of IMU
</Typography>
</Localized>
<div>
<div className="flex justify-center">
<LoaderIcon slimeState={slimeStatus}></LoaderIcon>
</div>
<ProgressBar
progress={
isCounting
? (IMU_CALIBRATION_TIME - timer) / IMU_CALIBRATION_TIME
: calibrationStatus === CalibrationStatus.SUCCESS
? 1
: 0
}
height={14}
animated={true}
colorClass={progressBarClass}
></ProgressBar>
</div>
<div className="flex justify-center">
<Typography variant="section-title">{progressText}</Typography>
</div>
<div className="flex gap-3">
<Button
variant="secondary"
to="/onboarding/wifi-creds"
className="mr-auto"
>
{l10n.getString('onboarding-previous_step')}
</Button>
<Button
variant="primary"
onClick={() => {
setCalibrationStatus(CalibrationStatus.CALIBRATING);
startCountdown();
}}
disabled={isCounting}
hidden={CalibrationStatus.SUCCESS === calibrationStatus}
className="ml-auto"
>
{l10n.getString('onboarding-calibration_tutorial-calibrate')}
</Button>
<Button
variant="primary"
to="/onboarding/trackers-assign"
className={classNames(
'ml-auto',
CalibrationStatus.SUCCESS !== calibrationStatus && 'hidden'
)}
>
{l10n.getString('onboarding-continue')}
</Button>
</div>
</div>
</div>
<div className="flex self-center w-[32rem]">
<div>
<img src="/images/taybol.png"></img>
</div>
</div>
</div>
<SkipSetupWarningModal
accept={skipSetup}
onClose={() => setSkipWarning(false)}
isOpen={skipWarning}
></SkipSetupWarningModal>
</div>
</>
);
}

View File

@@ -22,6 +22,7 @@ import { Typography } from '../../commons/Typography';
import { TrackerCard } from '../../tracker/TrackerCard';
import { SkipSetupWarningModal } from '../SkipSetupWarningModal';
import { SkipSetupButton } from '../SkipSetupButton';
import { useBnoExists } from '../../../hooks/imu-logic';
const BOTTOM_HEIGHT = 80;
@@ -70,6 +71,8 @@ export function ConnectTrackersPage() {
const connectedTrackers = useConnectedTrackers();
const bnoExists = useBnoExists(connectedTrackers);
useEffect(() => {
if (!state.wifi) {
navigate('/onboarding/wifi-creds');
@@ -209,7 +212,13 @@ export function ConnectTrackersPage() {
</Button>
<Button
variant="primary"
to={state.alonePage ? '/' : '/onboarding/trackers-assign'}
to={
state.alonePage
? '/'
: bnoExists
? '/onboarding/calibration-tutorial'
: '/onboarding/trackers-assign'
}
className="ml-auto"
>
{l10n.getString('onboarding-connect_tracker-next')}

View File

@@ -8,15 +8,21 @@ import { useState } from 'react';
import { SkipSetupWarningModal } from '../SkipSetupWarningModal';
import { SkipSetupButton } from '../SkipSetupButton';
import classNames from 'classnames';
import { useTrackers } from '../../../hooks/tracker';
import { useBnoExists } from '../../../hooks/imu-logic';
export function WifiCredsPage() {
const { l10n } = useLocalization();
const { applyProgress, skipSetup, state } = useOnboarding();
const { control, handleSubmit, submitWifiCreds, formState } = useWifiForm();
const { useConnectedTrackers } = useTrackers();
const [skipWarning, setSkipWarning] = useState(false);
const connectedTrackers = useConnectedTrackers();
applyProgress(0.2);
const bnoExists = useBnoExists(connectedTrackers);
return (
<form
className="flex flex-col w-full h-full"
@@ -92,7 +98,11 @@ export function WifiCredsPage() {
<Button
variant="secondary"
className={state.alonePage ? 'opacity-0' : ''}
to="/onboarding/trackers-assign"
to={
bnoExists
? '/onboarding/calibration-tutorial'
: '/onboarding/trackers-assign'
}
>
{l10n.getString('onboarding-wifi_creds-skip')}
</Button>

View File

@@ -28,6 +28,7 @@ import { NeckWarningModal } from '../../NeckWarningModal';
import { TrackerSelectionMenu } from './TrackerSelectionMenu';
import { SkipSetupWarningModal } from '../../SkipSetupWarningModal';
import { SkipSetupButton } from '../../SkipSetupButton';
import { useBnoExists } from '../../../../hooks/imu-logic';
import { useConfig } from '../../../../hooks/config';
import { playTapSetupSound } from '../../../../sounds/sounds';
@@ -45,7 +46,7 @@ interface FlatDeviceTrackerDummy {
export function TrackersAssignPage() {
const { l10n } = useLocalization();
const { useAssignedTrackers, trackers } = useTrackers();
const { useAssignedTrackers, trackers, useConnectedTrackers } = useTrackers();
const { applyProgress, skipSetup, state } = useOnboarding();
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
@@ -56,6 +57,9 @@ export function TrackersAssignPage() {
const [selectedRole, setSelectRole] = useState<BodyPart>(BodyPart.NONE);
const assignedTrackers = useAssignedTrackers();
const [skipWarning, setSkipWarning] = useState(false);
const connectedTrackers = useConnectedTrackers();
const bnoExists = useBnoExists(connectedTrackers);
const { config } = useConfig();
const [tapDetectionSettings, setTapDetectionSettings] = useState<Omit<
TapDetectionSettingsT,
@@ -289,7 +293,14 @@ export function TrackersAssignPage() {
<div className="flex flex-row mt-auto">
{!state.alonePage && (
<>
<Button variant="secondary" to="/onboarding/wifi-creds">
<Button
variant="secondary"
to={
bnoExists
? '/onboarding/calibration-tutorial'
: '/onboarding/wifi-creds'
}
>
{l10n.getString('onboarding-previous_step')}
</Button>
<Button

View File

@@ -4,7 +4,12 @@ import { IPv4 } from 'ip-num/IPNumber';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { AssignTrackerRequestT, BodyPart, RpcMessage } from 'solarxr-protocol';
import {
AssignTrackerRequestT,
BodyPart,
ImuType,
RpcMessage,
} from 'solarxr-protocol';
import { useDebouncedEffect } from '../../hooks/timeout';
import { useTrackerFromId } from '../../hooks/tracker';
import { useWebsocketAPI } from '../../hooks/websocket-api';
@@ -185,7 +190,7 @@ export function TrackerSettingsPage() {
Update now
</Button>
</div> */}
<div className="flex flex-col bg-background-70 p-3 rounded-lg gap-2">
<div className="flex flex-col bg-background-70 p-3 rounded-lg gap-2 overflow-x-auto">
<div className="flex justify-between">
<Typography color="secondary">
{l10n.getString('tracker-infos-manufacturer')}
@@ -227,13 +232,39 @@ export function TrackerSettingsPage() {
{tracker?.device?.hardwareInfo?.firmwareVersion || '--'}
</Typography>
</div>
<div className="flex justify-between">
{/* <div className="flex justify-between">
<Typography color="secondary">
{l10n.getString('tracker-infos-hardware_rev')}
</Typography>
<Typography>
{tracker?.device?.hardwareInfo?.hardwareRevision || '--'}
</Typography>
</div> */}
<div className="flex justify-between">
<Typography color="secondary">
{l10n.getString('tracker-infos-hardware_identifier')}
</Typography>
<Typography>
{tracker?.device?.hardwareInfo?.hardwareIdentifier || '--'}
</Typography>
</div>
<div className="flex justify-between">
<Typography color="secondary">
{l10n.getString('tracker-infos-imu')}
</Typography>
<Typography>
{tracker?.tracker.info?.imuType
? ImuType[tracker?.tracker.info?.imuType]
: '--'}
</Typography>
</div>
<div className="flex justify-between">
<Typography color="secondary">
{l10n.getString('tracker-infos-board_type')}
</Typography>
<Typography>
{tracker?.device?.hardwareInfo?.boardType || '--'}
</Typography>
</div>
</div>
{tracker?.tracker && (

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useRef, useState } from 'react';
export function useCountdown({
duration = 3,
@@ -11,24 +11,41 @@ export function useCountdown({
}) {
const [isCounting, setIsCounting] = useState(false);
const [timer, setDisplayTimer] = useState(0);
const countdownTimer = useRef<NodeJS.Timer>();
const counter = useRef(0);
const startCountdown = () => {
setIsCounting(true);
setDisplayTimer(duration);
for (let i = 1; i < duration; i++) {
setTimeout(() => setDisplayTimer(duration - i), i * 1000);
}
setTimeout(resetEnd, duration * 1000);
counter.current = 0;
countdownTimer.current = setInterval(
() => {
counter.current++;
setDisplayTimer(duration - counter.current);
if (counter.current >= duration) {
clearInterval(countdownTimer.current);
resetEnd();
}
},
duration > 1 ? 1000 : 500
);
};
const resetEnd = () => {
setIsCounting(false);
clearInterval(countdownTimer.current);
onCountdownEnd();
};
const abortCountdown = () => {
setIsCounting(false);
clearInterval(countdownTimer.current);
};
return {
timer,
isCounting,
startCountdown,
abortCountdown,
};
}

View File

@@ -0,0 +1,19 @@
import { useMemo } from 'react';
import { FlatDeviceTracker } from './app';
import { ImuType } from 'solarxr-protocol';
export function useBnoExists(connectedTrackers: FlatDeviceTracker[]): boolean {
const bnoExists = useMemo(
() =>
connectedTrackers.some(
(tracker) =>
tracker.tracker.info?.imuType &&
[ImuType.BNO055, ImuType.BNO080, ImuType.BNO085].includes(
tracker.tracker.info?.imuType
)
),
[connectedTrackers]
);
return bnoExists;
}

View File

@@ -49,3 +49,8 @@ export function QuaternionToEulerDegrees(q?: QuatObject | null) {
const a = new Euler().setFromQuaternion(new Quaternion(q.x, q.y, q.z, q.w));
return { x: a.x * RAD_TO_DEG, y: a.y * RAD_TO_DEG, z: a.z * RAD_TO_DEG };
}
export function compareQuatT(a: QuatT | null, b: QuatT | null): boolean {
if (!a || !b) return false;
return a.w === b.w && a.x === b.x && a.y === b.y && a.z === b.z;
}