Skip to content

Commit

Permalink
Allow multiple servers in the same network (#900)
Browse files Browse the repository at this point in the history
Co-authored-by: Erimel <[email protected]>
  • Loading branch information
ImUrX and Erimelowo authored Mar 15, 2024
1 parent a855747 commit f402b22
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 13 deletions.
10 changes: 10 additions & 0 deletions gui/public/i18n/en/translation.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ tracker-settings-drift_compensation_section-edit = Allow drift compensation
tracker-settings-name_section = Tracker name
tracker-settings-name_section-description = Give it a cute nickname :)
tracker-settings-name_section-placeholder = NightyBeast's left leg
tracker-settings-forget = Forget tracker
tracker-settings-forget-description = Removes the tracker from the SlimeVR Server and prevent it from connecting to it until the server is restarted. The configuration of the tracker won't be lost.
tracker-settings-forget-label = Forget tracker
## Tracker part card info
tracker-part_card-no_name = No name
Expand Down Expand Up @@ -889,3 +892,10 @@ tray_or_exit_modal-radio-exit = Exit on close
tray_or_exit_modal-radio-tray = Minimize to system tray
tray_or_exit_modal-submit = Save
tray_or_exit_modal-cancel = Cancel
## Unknown device modal
unknown_device-modal-title = A new tracker was found!
unknown_device-modal-description = There is a new tracker with MAC address <b>{$deviceId}</b>.
Do you want to connect it to SlimeVR?
unknown_device-modal-confirm = Sure!
unknown_device-modal-forget = Ignore it
2 changes: 2 additions & 0 deletions gui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { InterfaceSettings } from './components/settings/pages/InterfaceSettings
import { error, log } from './utils/logging';
import { AppLayout } from './AppLayout';
import { Preload } from './components/Preload';
import { UnknownDeviceModal } from './components/UnknownDeviceModal';

export const GH_REPO = 'SlimeVR/SlimeVR-Server';
export const VersionContext = createContext('');
Expand All @@ -66,6 +67,7 @@ function Layout() {
<>
<SerialDetectionModal></SerialDetectionModal>
<VersionUpdateModal></VersionUpdateModal>
<UnknownDeviceModal></UnknownDeviceModal>
<Routes>
<Route element={<AppLayout />}>
<Route
Expand Down
104 changes: 104 additions & 0 deletions gui/src/components/UnknownDeviceModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useState } from 'react';
import { BaseModal } from './commons/BaseModal';
import { Typography } from './commons/Typography';
import { Button } from './commons/Button';
import { Localized, useLocalization } from '@fluent/react';
import { useWebsocketAPI } from '@/hooks/websocket-api';
import { useLocation } from 'react-router-dom';
import {
AddUnknownDeviceRequestT,
RpcMessage,
UnknownDeviceHandshakeNotificationT,
} from 'solarxr-protocol';
import { useDebouncedEffect } from '@/hooks/timeout';
import { useAppContext } from '@/hooks/app';

export function UnknownDeviceModal() {
const { l10n } = useLocalization();
const [open, setOpen] = useState(0);
const { pathname } = useLocation();
const { state, dispatch } = useAppContext();
const [currentTracker, setCurrentTracker] = useState<string | null>(null);
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();

useRPCPacket(
RpcMessage.UnknownDeviceHandshakeNotification,
({ macAddress }: UnknownDeviceHandshakeNotificationT) => {
if (
['/onboarding/connect-trackers'].includes(pathname) ||
state.ignoredTrackers.has(macAddress as string) ||
(currentTracker !== null && currentTracker !== macAddress)
)
return;

setCurrentTracker(macAddress as string);
setOpen((old) => old + 1);
}
);

useDebouncedEffect(
() => {
setOpen(0);
setCurrentTracker(null);
},
[open],
3000
);

const closeModal = () => {
setCurrentTracker(null);
setOpen(0);
};

return (
<BaseModal isOpen={open !== 0}>
<div className="flex flex-col gap-3">
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
<div className="flex flex-col items-center gap-2">
<Typography variant="main-title">
{l10n.getString('unknown_device-modal-title')}
</Typography>
<Localized
id="unknown_device-modal-description"
elems={{ b: <b></b> }}
vars={{ deviceId: currentTracker ?? 'ERROR' }}
>
<Typography
variant="standard"
textAlign="text-center"
whitespace="whitespace-pre-line"
>
There is a new device in here!
</Typography>
</Localized>
</div>
</div>

<Button
variant="primary"
onClick={() => {
sendRPCPacket(
RpcMessage.AddUnknownDeviceRequest,
new AddUnknownDeviceRequestT(currentTracker)
);
closeModal();
}}
>
{l10n.getString('unknown_device-modal-confirm')}
</Button>
<Button
variant="tertiary"
onClick={() => {
dispatch({
type: 'ignoreTracker',
value: currentTracker as string,
});
closeModal();
}}
>
{l10n.getString('unknown_device-modal-forget')}
</Button>
</div>
</BaseModal>
);
}
11 changes: 11 additions & 0 deletions gui/src/components/onboarding/pages/ConnectTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
AddUnknownDeviceRequestT,
RpcMessage,
StartWifiProvisioningRequestT,
StopWifiProvisioningRequestT,
UnknownDeviceHandshakeNotificationT,
WifiProvisioningStatus,
WifiProvisioningStatusResponseT,
} from 'solarxr-protocol';
Expand Down Expand Up @@ -97,6 +99,15 @@ export function ConnectTrackersPage() {
}
);

useRPCPacket(
RpcMessage.UnknownDeviceHandshakeNotification,
({ macAddress }: UnknownDeviceHandshakeNotificationT) =>
sendRPCPacket(
RpcMessage.AddUnknownDeviceRequest,
new AddUnknownDeviceRequestT(macAddress)
)
);

const isError =
provisioningStatus === WifiProvisioningStatus.CONNECTION_ERROR ||
provisioningStatus === WifiProvisioningStatus.COULD_NOT_FIND_SERVER;
Expand Down
48 changes: 40 additions & 8 deletions gui/src/components/tracker/TrackerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useParams } from 'react-router-dom';
import {
AssignTrackerRequestT,
BodyPart,
ForgetDeviceRequestT,
ImuType,
RpcMessage,
} from 'solarxr-protocol';
Expand All @@ -31,6 +32,7 @@ import { IMUVisualizerWidget } from '@/components/widgets/IMUVisualizerWidget';
import { SingleTrackerBodyAssignmentMenu } from './SingleTrackerBodyAssignmentMenu';
import { TrackerCard } from './TrackerCard';
import { Quaternion } from 'three';
import { useAppContext } from '@/hooks/app';

const rotationsLabels: [Quaternion, string][] = [
[rotationToQuatMap.BACK, 'tracker-rotation-back'],
Expand Down Expand Up @@ -64,6 +66,7 @@ export function TrackerSettingsPage() {
},
reValidateMode: 'onSubmit',
});
const { dispatch } = useAppContext();
const { trackerName, allowDriftCompensation } = watch();

const tracker = useTrackerFromId(trackernum, deviceid);
Expand Down Expand Up @@ -124,13 +127,7 @@ export function TrackerSettingsPage() {
updateTrackerSettings();
};

useDebouncedEffect(
() => {
updateTrackerSettings();
},
[trackerName],
1000
);
useDebouncedEffect(() => updateTrackerSettings(), [trackerName], 1000);

useEffect(() => {
updateTrackerSettings();
Expand All @@ -149,6 +146,18 @@ export function TrackerSettingsPage() {
}
}, [firstLoad]);

const macAddress = useMemo(() => {
if (
/(?:[a-zA-Z\d]{2}:){5}[a-zA-Z\d]{2}/.test(
(tracker?.device?.hardwareInfo?.hardwareIdentifier as string | null) ??
''
)
) {
return tracker?.device?.hardwareInfo?.hardwareIdentifier as string;
}
return null;
}, [tracker?.device?.hardwareInfo?.hardwareIdentifier]);

return (
<form
className="h-full overflow-y-auto"
Expand All @@ -165,7 +174,7 @@ export function TrackerSettingsPage() {
onClose={() => setSelectRotation(false)}
onDirectionSelected={onDirectionSelected}
></MountingSelectionMenu>
<div className="flex gap-2 md:h-full max-md:flex-wrap md:flex-row xs:flex-col mobile:flex-col">
<div className="flex gap-2 max-md:flex-wrap md:flex-row xs:flex-col mobile:flex-col">
<div className="flex flex-col w-full md:max-w-xs gap-2">
{tracker && (
<TrackerCard
Expand Down Expand Up @@ -404,6 +413,29 @@ export function TrackerSettingsPage() {
label="Tracker name"
></Input>
</div>
{macAddress && (
<div className="flex flex-col gap-2 w-full mt-3">
<Typography variant="section-title">
{l10n.getString('tracker-settings-forget')}
</Typography>
<Typography color="secondary">
{l10n.getString('tracker-settings-forget-description')}
</Typography>
<Button
variant="secondary"
className="!bg-status-critical self-start"
onClick={() => {
sendRPCPacket(
RpcMessage.ForgetDeviceRequest,
new ForgetDeviceRequestT(macAddress)
);
dispatch({ type: 'ignoreTracker', value: macAddress });
}}
>
{l10n.getString('tracker-settings-forget-label')}
</Button>
</div>
)}
</div>
</div>
</form>
Expand Down
13 changes: 11 additions & 2 deletions gui/src/hooks/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ export interface FlatDeviceTracker {
tracker: TrackerDataT;
}

type AppStateAction = { type: 'datafeed'; value: DataFeedUpdateT };
export type AppStateAction =
| { type: 'datafeed'; value: DataFeedUpdateT }
| { type: 'ignoreTracker'; value: string };

export interface AppState {
datafeed?: DataFeedUpdateT;
ignoredTrackers: Set<string>;
}

export interface AppContext {
Expand All @@ -47,8 +50,13 @@ export function reducer(state: AppState, action: AppStateAction) {
switch (action.type) {
case 'datafeed':
return { ...state, datafeed: action.value };
case 'ignoreTracker':
return {
...state,
ignoredTrackers: new Set([...state.ignoredTrackers, action.value]),
};
default:
throw new Error(`unhandled state action ${action.type}`);
throw new Error(`unhandled state action ${(action as AppStateAction).type}`);
}
}

Expand All @@ -59,6 +67,7 @@ export function useProvideAppContext(): AppContext {
const { dataFeedConfig } = useDataFeedConfig();
const [state, dispatch] = useReducer<Reducer<AppState, AppStateAction>>(reducer, {
datafeed: new DataFeedUpdateT(),
ignoredTrackers: new Set(),
});

useEffect(() => {
Expand Down
5 changes: 4 additions & 1 deletion server/core/src/main/java/dev/slimevr/VRServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import dev.slimevr.reset.ResetHandler
import dev.slimevr.serial.ProvisioningHandler
import dev.slimevr.serial.SerialHandler
import dev.slimevr.serial.SerialHandlerStub
import dev.slimevr.setup.HandshakeHandler
import dev.slimevr.setup.TapSetupHandler
import dev.slimevr.status.StatusSystem
import dev.slimevr.tracking.processor.HumanPoseManager
Expand Down Expand Up @@ -48,7 +49,6 @@ class VRServer @JvmOverloads constructor(
driverBridgeProvider: SteamBridgeProvider = { _, _ -> null },
feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null },
serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() },
// configPath is used by VRWorkout, do not remove!
configPath: String,
) : Thread("VRServer") {
@JvmField
Expand Down Expand Up @@ -97,6 +97,9 @@ class VRServer @JvmOverloads constructor(
@JvmField
val statusSystem = StatusSystem()

@JvmField
val handshakeHandler = HandshakeHandler()

init {
// UwU
configManager = ConfigManager(configPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.eiren.util.logging.LogManager;

import java.util.Map;
import java.util.regex.Pattern;


public class CurrentVRConfigConverter implements VersionedModelConverter {
Expand Down Expand Up @@ -285,6 +286,24 @@ public ObjectNode convert(
}
}
}

if (version < 13) {
ObjectNode oldTrackersNode = (ObjectNode) modelData.get("trackers");
if (oldTrackersNode != null) {
var fieldNamesIter = oldTrackersNode.fieldNames();
String trackerId;
final String macAddressRegex = "udp://((?:[a-zA-Z\\d]{2}:){5}[a-zA-Z\\d]{2})/0";
final Pattern pattern = Pattern.compile(macAddressRegex);
while (fieldNamesIter.hasNext()) {
trackerId = fieldNamesIter.next();
var matcher = pattern.matcher(trackerId);
if (!matcher.find())
continue;

modelData.withArray("knownDevices").add(matcher.group(1));
}
}
}
} catch (Exception e) {
LogManager.severe("Error during config migration: " + e);
}
Expand Down
Loading

0 comments on commit f402b22

Please sign in to comment.