mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Serial Selection and detection (#312)
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start": "vite --force",
|
||||
"build": "vite build",
|
||||
"dev": "tauri dev",
|
||||
"tauri": "tauri",
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
import {
|
||||
useProvideWebsocketApi,
|
||||
WebSocketApiContext,
|
||||
} from './hooks/websocket-api';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Routes,
|
||||
Route,
|
||||
Outlet,
|
||||
Route,
|
||||
Routes
|
||||
} from 'react-router-dom';
|
||||
import { Home } from './components/home/Home';
|
||||
import { AppContextProvider } from './components/providers/AppContext';
|
||||
import { useEffect } from 'react';
|
||||
import { MainLayoutRoute } from './components/MainLayout';
|
||||
import { SettingsLayoutRoute } from './components/settings/SettingsLayout';
|
||||
import { AppContextProvider } from './components/providers/AppContext';
|
||||
import { GeneralSettings } from './components/settings/pages/GeneralSettings';
|
||||
import { Serial } from './components/settings/pages/Serial';
|
||||
import { SettingsLayoutRoute } from './components/settings/SettingsLayout';
|
||||
import {
|
||||
useProvideWebsocketApi,
|
||||
WebSocketApiContext
|
||||
} from './hooks/websocket-api';
|
||||
|
||||
import { Event, listen } from '@tauri-apps/api/event';
|
||||
import { TopBar } from './components/TopBar';
|
||||
import { ConfigContextProvider } from './components/providers/ConfigContext';
|
||||
import { OnboardingLayout } from './components/onboarding/OnboardingLayout';
|
||||
import { HomePage } from './components/onboarding/pages/Home';
|
||||
import { WifiCredsPage } from './components/onboarding/pages/WifiCreds';
|
||||
import { ConnectTrackersPage } from './components/onboarding/pages/ConnectTracker';
|
||||
import { OnboardingContextProvider } from './components/onboarding/OnboardingContextProvicer';
|
||||
import { TrackersAssignPage } from './components/onboarding/pages/trackers-assign/TrackerAssignment';
|
||||
import { OnboardingLayout } from './components/onboarding/OnboardingLayout';
|
||||
import { AutomaticProportionsPage } from './components/onboarding/pages/body-proportions/AutomaticProportions';
|
||||
import { ManualProportionsPage } from './components/onboarding/pages/body-proportions/ManualProportions';
|
||||
import { ConnectTrackersPage } from './components/onboarding/pages/ConnectTracker';
|
||||
import { DonePage } from './components/onboarding/pages/Done';
|
||||
import { EnterVRPage } from './components/onboarding/pages/EnterVR';
|
||||
import { HomePage } from './components/onboarding/pages/Home';
|
||||
import { AutomaticMountingPage } from './components/onboarding/pages/mounting/AutomaticMounting';
|
||||
import { ManualMountingPage } from './components/onboarding/pages/mounting/ManualMounting';
|
||||
import { ResetTutorialPage } from './components/onboarding/pages/ResetTutorial';
|
||||
import { AutomaticProportionsPage } from './components/onboarding/pages/body-proportions/AutomaticProportions';
|
||||
import { ManualProportionsPage } from './components/onboarding/pages/body-proportions/ManualProportions';
|
||||
import { TrackerSettingsPage } from './components/tracker/TrackerSettings';
|
||||
import { DonePage } from './components/onboarding/pages/Done';
|
||||
import { TrackersAssignPage } from './components/onboarding/pages/trackers-assign/TrackerAssignment';
|
||||
import { WifiCredsPage } from './components/onboarding/pages/WifiCreds';
|
||||
import { ConfigContextProvider } from './components/providers/ConfigContext';
|
||||
import { SerialDetectionModal } from './components/SerialDetectionModal';
|
||||
import { OSCSettings } from './components/settings/pages/OSCSettings';
|
||||
import { TopBar } from './components/TopBar';
|
||||
import { TrackerSettingsPage } from './components/tracker/TrackerSettings';
|
||||
import { useConfig } from './hooks/config';
|
||||
|
||||
function Layout() {
|
||||
const { loading } = useConfig();
|
||||
if (loading) return (<></>);
|
||||
if (loading) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SerialDetectionModal></SerialDetectionModal>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
import { ResetType } from 'solarxr-protocol';
|
||||
import { useLayout } from '../hooks/layout';
|
||||
import { BVHButton } from './BVHButton';
|
||||
import { Navbar } from './Navbar';
|
||||
import { ResetButton } from './home/ResetButton';
|
||||
import { Navbar } from './Navbar';
|
||||
import { TopBar } from './TopBar';
|
||||
import classNames from 'classnames';
|
||||
import { OverlayWidget } from './widgets/OverlayWidget';
|
||||
|
||||
export function MainLayoutRoute({
|
||||
|
||||
199
gui/src/components/SerialDetectionModal.tsx
Normal file
199
gui/src/components/SerialDetectionModal.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
RpcMessage,
|
||||
SerialDevicesRequestT,
|
||||
SerialDevicesResponseT,
|
||||
SerialDeviceT
|
||||
} from 'solarxr-protocol';
|
||||
import { useConfig } from '../hooks/config';
|
||||
import { usePrevious } from '../hooks/previous';
|
||||
import { useWebsocketAPI } from '../hooks/websocket-api';
|
||||
import { useWifiForm, WifiFormData } from '../hooks/wifi-form';
|
||||
import { BaseModal } from './commons/BaseModal';
|
||||
import { Button } from './commons/Button';
|
||||
import { BulbIcon } from './commons/icon/BulbIcon';
|
||||
import { USBIcon } from './commons/icon/UsbIcon';
|
||||
import { Typography } from './commons/Typography';
|
||||
|
||||
const mapItems = <T,>(items: T[], getKey: (item: T) => string) => {
|
||||
const map: any = {};
|
||||
for (const item of items) {
|
||||
const key = getKey(item);
|
||||
map[key] = item;
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const detectChanges = <T,>(
|
||||
prevItems: T[],
|
||||
nextItems: T[],
|
||||
getKey: (item: T) => string,
|
||||
compareItems: (a: T, b: T) => boolean
|
||||
) => {
|
||||
const mappedItems = mapItems(prevItems, getKey);
|
||||
const addedItems = [];
|
||||
const updatedItems = [];
|
||||
const removedItems = [];
|
||||
const unchangedItems = [];
|
||||
for (const nextItem of nextItems) {
|
||||
const itemKey = getKey(nextItem);
|
||||
if (itemKey in mappedItems) {
|
||||
const prevItem = mappedItems[itemKey];
|
||||
if (delete mappedItems[itemKey] && compareItems(prevItem, nextItem)) {
|
||||
unchangedItems.push(nextItem);
|
||||
} else {
|
||||
updatedItems.push(nextItem);
|
||||
}
|
||||
} else {
|
||||
addedItems.push(nextItem);
|
||||
}
|
||||
}
|
||||
for (const itemKey in mappedItems) {
|
||||
if (itemKey in mappedItems) {
|
||||
removedItems.push(mappedItems[itemKey]);
|
||||
}
|
||||
}
|
||||
return { addedItems, updatedItems, removedItems, unchangedItems };
|
||||
};
|
||||
|
||||
export function SerialDetectionModal() {
|
||||
const { config } = useConfig();
|
||||
const nav = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const [currentDevices, setCurrentDevices] = useState<SerialDeviceT[] | null>(
|
||||
null
|
||||
);
|
||||
const prevDevices = usePrevious<SerialDeviceT[] | null>(currentDevices);
|
||||
const [isOpen, setOpen] = useState<SerialDeviceT | null>(null);
|
||||
const [showWifiForm, setShowWifiForm] = useState(false);
|
||||
|
||||
const { WifiForm, handleSubmit, submitWifiCreds, formState, hasWifiCreds } =
|
||||
useWifiForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (prevDevices == null) return;
|
||||
|
||||
const changes = detectChanges(
|
||||
prevDevices || [],
|
||||
currentDevices || [],
|
||||
(item) => item.port?.toString() || 'error',
|
||||
(a, b) => a.port == b.port && a.name == b.name
|
||||
);
|
||||
if (changes.addedItems.length === 1) {
|
||||
setOpen(changes.addedItems[0]);
|
||||
}
|
||||
}, [prevDevices, currentDevices]);
|
||||
|
||||
useEffect(() => {
|
||||
let timerId: NodeJS.Timer;
|
||||
|
||||
if (
|
||||
config?.watchNewDevices &&
|
||||
!['/settings/serial', '/onboarding/connect-trackers'].includes(pathname)
|
||||
) {
|
||||
timerId = setInterval(() => {
|
||||
sendRPCPacket(
|
||||
RpcMessage.SerialDevicesRequest,
|
||||
new SerialDevicesRequestT()
|
||||
);
|
||||
}, 3000);
|
||||
}
|
||||
return () => {
|
||||
clearInterval(timerId);
|
||||
};
|
||||
}, [config, sendRPCPacket, pathname]);
|
||||
|
||||
const openSerial = () => {
|
||||
nav('/settings/serial', { state: { serialPort: isOpen?.port } });
|
||||
setOpen(null);
|
||||
};
|
||||
|
||||
const openWifi = () => {
|
||||
if (!hasWifiCreds) {
|
||||
setShowWifiForm(true);
|
||||
} else {
|
||||
setOpen(null);
|
||||
nav('/onboarding/connect-trackers', { state: { alonePage: true } });
|
||||
}
|
||||
};
|
||||
|
||||
const modalWifiSubmit = (form: WifiFormData) => {
|
||||
submitWifiCreds(form);
|
||||
setOpen(null);
|
||||
nav('/onboarding/connect-trackers', { state: { alonePage: true } });
|
||||
};
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.SerialDevicesResponse,
|
||||
(val: SerialDevicesResponseT) => {
|
||||
setCurrentDevices(val.devices);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseModal isOpen={!!isOpen} onRequestClose={() => setOpen(null)}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{!showWifiForm && (
|
||||
<>
|
||||
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
|
||||
<USBIcon></USBIcon>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Typography variant="main-title">
|
||||
New serial device detected!
|
||||
</Typography>
|
||||
<Typography variant="section-title">
|
||||
{isOpen?.name || 'unknown'}
|
||||
</Typography>
|
||||
<Typography variant="standard">
|
||||
Please select what you want to do with it
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="primary" onClick={openWifi}>
|
||||
Connect to WiFi
|
||||
</Button>
|
||||
<Button variant="tiertiary" onClick={openSerial}>
|
||||
Open Serial Console
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => setOpen(null)}>
|
||||
Close
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{showWifiForm && (
|
||||
<form
|
||||
onSubmit={handleSubmit(modalWifiSubmit)}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<BulbIcon></BulbIcon>
|
||||
<Typography variant="main-title">
|
||||
New serial device detected!
|
||||
</Typography>
|
||||
<Typography variant="standard">
|
||||
Enter your wifi credentials!
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 rounded-xl max-w-sm">
|
||||
<WifiForm></WifiForm>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={!formState.isValid}
|
||||
>
|
||||
Submit!
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => setOpen(null)}>
|
||||
Close
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export function ArrowLink({
|
||||
to: string;
|
||||
children: ReactChild;
|
||||
direction?: 'left' | 'right';
|
||||
variant?: 'flat' | 'boxed';
|
||||
variant?: 'flat' | 'boxed' | 'boxed-2';
|
||||
}) {
|
||||
const classes = useMemo(() => {
|
||||
const variantsMap = {
|
||||
@@ -20,6 +20,9 @@ export function ArrowLink({
|
||||
boxed: classNames(
|
||||
'justify-between bg-background-70 rounded-md hover:bg-background-60 p-3'
|
||||
),
|
||||
'boxed-2': classNames(
|
||||
'justify-between bg-background-60 rounded-md hover:bg-background-50 p-3'
|
||||
),
|
||||
};
|
||||
return classNames(
|
||||
variantsMap[variant],
|
||||
|
||||
28
gui/src/components/commons/BaseModal.tsx
Normal file
28
gui/src/components/commons/BaseModal.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
export function BaseModal({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
children: ReactNode;
|
||||
} & ReactModal.Props) {
|
||||
return (
|
||||
<ReactModal
|
||||
{...props}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
overlayClassName={classNames(
|
||||
'fixed top-0 right-0 left-0 bottom-0 flex flex-col justify-center items-center w-full h-full bg-background-90 bg-opacity-60 z-20'
|
||||
)}
|
||||
className={classNames(
|
||||
props.className as string,
|
||||
'items-center focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent outline-none bg-background-60 p-6 rounded-lg text-white'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactChild, useMemo } from 'react';
|
||||
import React, { ReactChild, ReactNode, useMemo } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { LoaderIcon } from './icon/LoaderIcon';
|
||||
|
||||
@@ -47,9 +47,9 @@ export function Button({
|
||||
rounded = false,
|
||||
...props
|
||||
}: {
|
||||
children: ReactChild;
|
||||
icon?: ReactChild;
|
||||
variant: 'primary' | 'secondary' | 'tierciary' | 'quaternary';
|
||||
children: ReactNode;
|
||||
icon?: ReactNode;
|
||||
variant: 'primary' | 'secondary' | 'tiertiary' | 'quaternary';
|
||||
to?: string;
|
||||
loading?: boolean;
|
||||
rounded?: boolean;
|
||||
@@ -69,7 +69,7 @@ export function Button({
|
||||
'bg-background-60 hover:bg-background-60 cursor-not-allowed text-background-40':
|
||||
disabled,
|
||||
}),
|
||||
tierciary: classNames({
|
||||
tiertiary: classNames({
|
||||
'bg-background-50 hover:bg-background-40 text-standard text-background-10':
|
||||
!disabled,
|
||||
'bg-background-50 hover:bg-background-50 cursor-not-allowed text-background-40':
|
||||
|
||||
112
gui/src/components/commons/Dropdown.tsx
Normal file
112
gui/src/components/commons/Dropdown.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
export interface DropdownItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function Dropdown({
|
||||
direction = 'up',
|
||||
variant = 'primary',
|
||||
placeholder,
|
||||
control,
|
||||
name,
|
||||
items = [],
|
||||
}: {
|
||||
direction?: 'up' | 'down';
|
||||
variant?: 'primary' | 'secondary';
|
||||
placeholder: string;
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
items: DropdownItem[];
|
||||
}) {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<div className="w-full">
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute top-0 left-0 w-full h-full bg-transparent"
|
||||
onClick={() => setOpen(false)}
|
||||
></div>
|
||||
)}
|
||||
<div className="relative w-fit">
|
||||
<div
|
||||
className={classNames(
|
||||
'min-h-[35px] text-white px-5 py-2.5 rounded-md focus:ring-4 text-center flex',
|
||||
variant == 'primary' &&
|
||||
'bg-background-60 hover:bg-background-50',
|
||||
variant == 'secondary' &&
|
||||
'bg-background-70 hover:bg-background-60'
|
||||
)}
|
||||
onClick={() => setOpen((open) => !open)}
|
||||
>
|
||||
<div className="flex-grow">
|
||||
{items.find((i) => i.value == value)?.label || placeholder}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'ml-2',
|
||||
direction == 'up' && 'rotate-180',
|
||||
direction == 'down' && 'rotate-0'
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="justify-end w-4 h-4 "
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute z-10 rounded shadow right-0',
|
||||
direction === 'up' && 'bottom-[45px]',
|
||||
direction === 'down' && 'top-[45px]',
|
||||
variant == 'primary' && 'bg-background-60',
|
||||
variant == 'secondary' && 'bg-background-70'
|
||||
)}
|
||||
>
|
||||
<ul className="py-1 text-sm text-gray-200 flex flex-col ">
|
||||
{items.map((item) => (
|
||||
<li
|
||||
className={classNames(
|
||||
'py-2 px-4 hover:text-white min-w-max',
|
||||
variant == 'primary' && 'hover:bg-background-50',
|
||||
variant == 'secondary' && 'hover:bg-background-60'
|
||||
)}
|
||||
onClick={() => {
|
||||
onChange(item.value);
|
||||
setOpen(false);
|
||||
}}
|
||||
key={item.value}
|
||||
>
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,8 @@ import {
|
||||
forwardRef,
|
||||
HTMLInputTypeAttribute,
|
||||
MouseEvent,
|
||||
useState,
|
||||
useMemo,
|
||||
useState
|
||||
} from 'react';
|
||||
import { EyeIcon } from './icon/EyeIcon';
|
||||
|
||||
@@ -12,10 +13,11 @@ export interface InputProps {
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
autocomplete?: boolean;
|
||||
variant?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(function AppInput(
|
||||
{ type, placeholder, label, autocomplete, ...props },
|
||||
{ type, placeholder, label, autocomplete, variant = 'primary', ...props },
|
||||
ref
|
||||
) {
|
||||
const [forceText, setForceText] = useState(false);
|
||||
@@ -25,6 +27,18 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function AppInput(
|
||||
setForceText(!forceText);
|
||||
};
|
||||
|
||||
const classes = useMemo(() => {
|
||||
const variantsMap = {
|
||||
primary: classNames('bg-background-60 border-background-60'),
|
||||
secondary: classNames('bg-background-50 border-background-50'),
|
||||
};
|
||||
|
||||
return classNames(
|
||||
variantsMap[variant],
|
||||
'w-full focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent rounded-md bg-background-60 border-background-60 focus:border-accent-background-40 placeholder:text-background-30 text-standard relative'
|
||||
);
|
||||
}, [variant]);
|
||||
|
||||
return (
|
||||
<label className="flex flex-col gap-1">
|
||||
{label}
|
||||
@@ -32,10 +46,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function AppInput(
|
||||
<input
|
||||
type={forceText ? 'text' : type}
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'w-full focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent rounded-md bg-background-60 border-background-60 focus:border-accent-background-40 placeholder:text-background-30 text-standard relative',
|
||||
{ 'pr-10': type === 'password' }
|
||||
)}
|
||||
className={classNames(classes, { 'pr-10': type === 'password' })}
|
||||
placeholder={placeholder}
|
||||
autoComplete={autocomplete ? 'off' : 'on'}
|
||||
{...props}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function NumberSelector({
|
||||
<div className="flex gap-2 bg-background-60 p-2 rounded-lg">
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="tierciary"
|
||||
variant="tiertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, false))}
|
||||
disabled={stepFn(value, false) < min}
|
||||
@@ -48,7 +48,7 @@ export function NumberSelector({
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="tierciary"
|
||||
variant="tiertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, true))}
|
||||
disabled={stepFn(value, true) > max}
|
||||
|
||||
7
gui/src/components/commons/icon/UsbIcon.tsx
Normal file
7
gui/src/components/commons/icon/UsbIcon.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function USBIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48">
|
||||
<path d="M24 44q-1.6 0-2.6-1t-1-2.6q0-1.1.55-2 .55-.9 1.55-1.45V31.4h-7.4q-1.2 0-2.1-.9-.9-.9-.9-2.1v-5.45q-1-.45-1.55-1.325T10 19.6q0-1.6 1-2.6t2.6-1q1.6 0 2.6 1t1 2.6q0 1.15-.55 2.025T15.1 22.95v5.45h7.4v-17h-4L24 3.95l5.5 7.45h-4v17h7.4v-5.2h-2.1V16H38v7.2h-2.1v5.2q0 1.2-.9 2.1-.9.9-2.1.9h-7.4v5.55q1 .55 1.55 1.5t.55 1.95q0 1.6-1 2.6T24 44Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
OpenSerialRequestT,
|
||||
RpcMessage,
|
||||
SerialUpdateResponseT,
|
||||
SetWifiRequestT,
|
||||
SetWifiRequestT
|
||||
} from 'solarxr-protocol';
|
||||
import { useLayout } from '../../../hooks/layout';
|
||||
import { useOnboarding } from '../../../hooks/onboarding';
|
||||
@@ -49,12 +49,19 @@ export function ConnectTrackersPage() {
|
||||
|
||||
const connectedTrackers = useConnectedTrackers();
|
||||
|
||||
const openSerial = () => {
|
||||
const req = new OpenSerialRequestT();
|
||||
req.auto = true;
|
||||
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, req);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.wifi) {
|
||||
navigate('/onboarding/wifi-creds');
|
||||
}
|
||||
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
openSerial();
|
||||
return () => {
|
||||
sendRPCPacket(RpcMessage.CloseSerialRequest, new CloseSerialRequestT());
|
||||
};
|
||||
@@ -67,7 +74,7 @@ export function ConnectTrackersPage() {
|
||||
setSerialOpen(false);
|
||||
setConnectionStatus('START-CONNECTING');
|
||||
setTimeout(() => {
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
openSerial();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@@ -110,8 +117,7 @@ export function ConnectTrackersPage() {
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
if (!isSerialOpen)
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
if (!isSerialOpen) openSerial();
|
||||
else clearInterval(id);
|
||||
}, 1000);
|
||||
|
||||
@@ -124,9 +130,11 @@ export function ConnectTrackersPage() {
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex gap-10 w-full max-w-7xl ">
|
||||
<div className="flex flex-col w-full max-w-sm">
|
||||
<ArrowLink to="/onboarding/wifi-creds">
|
||||
Go Back to WiFi credentials
|
||||
</ArrowLink>
|
||||
{!state.alonePage && (
|
||||
<ArrowLink to="/onboarding/wifi-creds">
|
||||
Go Back to WiFi credentials
|
||||
</ArrowLink>
|
||||
)}
|
||||
<Typography variant="main-title">Connect trackers</Typography>
|
||||
<Typography color="secondary">
|
||||
Now onto the fun part, connecting all the trackers!
|
||||
@@ -142,7 +150,11 @@ export function ConnectTrackersPage() {
|
||||
>
|
||||
I have other types of trackers
|
||||
</ArrowLink> */}
|
||||
<ArrowLink to="/settings/serial" direction="right" variant="boxed">
|
||||
<ArrowLink
|
||||
to="/settings/serial"
|
||||
direction="right"
|
||||
variant={state.alonePage ? 'boxed-2' : 'boxed'}
|
||||
>
|
||||
I'm having trouble connecting!
|
||||
</ArrowLink>
|
||||
</div>
|
||||
@@ -153,7 +165,8 @@ export function ConnectTrackersPage() {
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-xl bg-background-70 h-16 flex gap-2 p-3 lg:w-full mt-4',
|
||||
'rounded-xl h-16 flex gap-2 p-3 lg:w-full mt-4',
|
||||
state.alonePage ? 'bg-background-60' : 'bg-background-70',
|
||||
connectionStatus === 'ERROR' && 'border-2 border-status-critical'
|
||||
)}
|
||||
>
|
||||
@@ -187,7 +200,14 @@ export function ConnectTrackersPage() {
|
||||
}).map((tracker, index) => (
|
||||
<div key={index}>
|
||||
{!tracker && (
|
||||
<div className="rounded-xl bg-background-70 h-16"></div>
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-xl h-16',
|
||||
state.alonePage
|
||||
? 'bg-background-80'
|
||||
: 'bg-background-70'
|
||||
)}
|
||||
></div>
|
||||
)}
|
||||
{tracker && (
|
||||
<TrackerCard
|
||||
@@ -208,14 +228,23 @@ export function ConnectTrackersPage() {
|
||||
>
|
||||
<div className="w-full flex">
|
||||
<div className="flex flex-grow">
|
||||
<Button variant="secondary" to="/" onClick={skipSetup}>
|
||||
Skip setup
|
||||
</Button>
|
||||
{!state.alonePage && (
|
||||
<Button variant="secondary" to="/" onClick={skipSetup}>
|
||||
Skip setup
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="primary" to="/onboarding/trackers-assign">
|
||||
I connected all my trackers
|
||||
</Button>
|
||||
{!state.alonePage && (
|
||||
<Button variant="primary" to="/onboarding/trackers-assign">
|
||||
I connected all my trackers
|
||||
</Button>
|
||||
)}
|
||||
{state.alonePage && (
|
||||
<Button variant="primary" to="/">
|
||||
I connected all my trackers
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { BodyPart } from 'solarxr-protocol';
|
||||
import { useOnboarding } from '../../../hooks/onboarding';
|
||||
import { useTrackers } from '../../../hooks/tracker';
|
||||
import { ArrowLink } from '../../commons/ArrowLink';
|
||||
import { Button } from '../../commons/Button';
|
||||
import { Typography } from '../../commons/Typography';
|
||||
|
||||
@@ -1,42 +1,14 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useOnboarding } from '../../../hooks/onboarding';
|
||||
import { useWifiForm } from '../../../hooks/wifi-form';
|
||||
import { ArrowLink } from '../../commons/ArrowLink';
|
||||
import { Button } from '../../commons/Button';
|
||||
import { Input } from '../../commons/Input';
|
||||
import { Typography } from '../../commons/Typography';
|
||||
|
||||
export interface WifiForm {
|
||||
ssid: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function WifiCredsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { applyProgress, state, setWifiCredentials, skipSetup } =
|
||||
useOnboarding();
|
||||
const { register, reset, handleSubmit, formState } = useForm<WifiForm>({
|
||||
defaultValues: {},
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const { applyProgress, skipSetup } = useOnboarding();
|
||||
const { WifiForm, handleSubmit, submitWifiCreds, formState } = useWifiForm();
|
||||
applyProgress(0.2);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.wifi) {
|
||||
reset({
|
||||
ssid: state.wifi.ssid,
|
||||
password: state.wifi.password,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const submitWifiCreds = (value: WifiForm) => {
|
||||
setWifiCredentials(value.ssid, value.password);
|
||||
navigate('/onboarding/connect-trackers');
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col w-full h-full"
|
||||
@@ -57,18 +29,7 @@ export function WifiCredsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col bg-background-70 gap-3 p-10 rounded-xl max-w-sm">
|
||||
<Input
|
||||
{...register('ssid', { required: true })}
|
||||
type="text"
|
||||
label="SSID"
|
||||
placeholder="Enter SSID"
|
||||
/>
|
||||
<Input
|
||||
{...register('password')}
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Enter password"
|
||||
/>
|
||||
<WifiForm></WifiForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function PreparationStep({
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tierciary'}
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tiertiary'}
|
||||
onClick={prevStep}
|
||||
>
|
||||
Previous step
|
||||
|
||||
@@ -47,7 +47,7 @@ export function StartRecording({
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tierciary'}
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tiertiary'}
|
||||
onClick={prevStep}
|
||||
>
|
||||
Previous step
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
import { useAutobone } from '../../../../../hooks/autobone';
|
||||
import { Button } from '../../../../commons/Button';
|
||||
import { Typography } from '../../../../commons/Typography';
|
||||
@@ -68,7 +67,7 @@ export function VerifyResultsStep({
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tierciary'}
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tiertiary'}
|
||||
onClick={redo}
|
||||
>
|
||||
Redo recording
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
RpcMessage,
|
||||
SettingsRequestT,
|
||||
SettingsResponseT,
|
||||
SteamVRTrackersSettingT,
|
||||
SteamVRTrackersSettingT
|
||||
} from 'solarxr-protocol';
|
||||
import { useConfig } from '../../../hooks/config';
|
||||
import { useWebsocketAPI } from '../../../hooks/websocket-api';
|
||||
@@ -45,6 +45,7 @@ interface SettingsForm {
|
||||
};
|
||||
interface: {
|
||||
devmode: boolean;
|
||||
watchNewDevices: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ export function GeneralSettings() {
|
||||
skatingCorrection: false,
|
||||
},
|
||||
filtering: { amount: 0.1, type: FilteringType.NONE },
|
||||
interface: { devmode: false },
|
||||
interface: { devmode: false, watchNewDevices: true },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -104,11 +105,14 @@ export function GeneralSettings() {
|
||||
const filtering = new FilteringSettingsT();
|
||||
filtering.type = values.filtering.type;
|
||||
filtering.amount = values.filtering.amount;
|
||||
|
||||
settings.filtering = filtering;
|
||||
|
||||
sendRPCPacket(RpcMessage.ChangeSettingsRequest, settings);
|
||||
|
||||
setConfig({ debug: values.interface.devmode });
|
||||
setConfig({
|
||||
debug: values.interface.devmode,
|
||||
watchNewDevices: values.interface.watchNewDevices,
|
||||
});
|
||||
|
||||
// if devmode was changed update the page
|
||||
const skeletonSettings = document.getElementById('skeletonSettings');
|
||||
@@ -132,6 +136,7 @@ export function GeneralSettings() {
|
||||
const formData: DefaultValues<SettingsForm> = {
|
||||
interface: {
|
||||
devmode: config?.debug,
|
||||
watchNewDevices: config?.watchNewDevices,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -285,7 +290,7 @@ export function GeneralSettings() {
|
||||
movement patterns.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 pb-5">
|
||||
<div className="grid sm:grid-cols-2 gap-3 pb-5">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
@@ -308,7 +313,7 @@ export function GeneralSettings() {
|
||||
Change the way the arms are tracked.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 pb-5">
|
||||
<div className="grid sm:grid-cols-2 pb-5">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
@@ -325,7 +330,7 @@ export function GeneralSettings() {
|
||||
these on.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 pb-5">
|
||||
<div className="grid sm:grid-cols-2 gap-3 pb-5">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
@@ -355,23 +360,48 @@ export function GeneralSettings() {
|
||||
<SettingsPageLayout icon={<SquaresIcon></SquaresIcon>} id="interface">
|
||||
<>
|
||||
<Typography variant="main-title">Interface</Typography>
|
||||
<Typography bold>Developer Mode</Typography>
|
||||
<div className="flex flex-col pt-2">
|
||||
<Typography color="secondary">
|
||||
This mode can be useful if you need in-depth data or to interact
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
with connected trackers on a more advanced level
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 gap-3 pt-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="interface.devmode"
|
||||
label="Developer mode"
|
||||
/>
|
||||
<div className="gap-4 grid">
|
||||
<div className="grid sm:grid-cols-2">
|
||||
<div>
|
||||
<Typography bold>Developer Mode</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
This mode can be useful if you need in-depth data or to
|
||||
interact with connected trackers on a more advanced level
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="interface.devmode"
|
||||
label="Developer mode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2">
|
||||
<div>
|
||||
<Typography bold>Serial device detection</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
This option will show a pop-up every time you plug a new
|
||||
serial device that could be a tracker. It helps improving
|
||||
the setup process of a tracker
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="interface.watchNewDevices"
|
||||
label="Serial device detection"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPageLayout>
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
CloseSerialRequestT,
|
||||
OpenSerialRequestT,
|
||||
RpcMessage,
|
||||
SerialDevicesRequestT,
|
||||
SerialDevicesResponseT,
|
||||
SerialDeviceT,
|
||||
SerialTrackerFactoryResetRequestT,
|
||||
SerialTrackerGetInfoRequestT,
|
||||
SerialTrackerRebootRequestT,
|
||||
SerialUpdateResponseT,
|
||||
SerialUpdateResponseT
|
||||
} from 'solarxr-protocol';
|
||||
import { useElemSize, useLayout } from '../../../hooks/layout';
|
||||
import { useWebsocketAPI } from '../../../hooks/websocket-api';
|
||||
import { Button } from '../../commons/Button';
|
||||
import { Dropdown } from '../../commons/Dropdown';
|
||||
import { Typography } from '../../commons/Typography';
|
||||
|
||||
export interface WifiForm {
|
||||
ssid: string;
|
||||
password: string;
|
||||
export interface SerialForm {
|
||||
port: string;
|
||||
}
|
||||
|
||||
export function Serial() {
|
||||
@@ -25,16 +30,55 @@ export function Serial() {
|
||||
ref: consoleRef,
|
||||
} = useLayout<HTMLDivElement>();
|
||||
|
||||
const { state } = useLocation();
|
||||
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const { height } = useElemSize(toolbarRef);
|
||||
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
// const consoleRef = useRef<HTMLPreElement>(null);
|
||||
const [consoleContent, setConsole] = useState('');
|
||||
|
||||
const [isSerialOpen, setSerialOpen] = useState(false);
|
||||
const [serialDevices, setSerialDevices] = useState<
|
||||
Omit<SerialDeviceT, 'pack'>[]
|
||||
>([]);
|
||||
|
||||
const { control, watch, handleSubmit, reset } = useForm<SerialForm>({
|
||||
defaultValues: { port: 'Auto' },
|
||||
});
|
||||
|
||||
const { port } = watch();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch(() => handleSubmit(onSubmit)());
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
const onSubmit = (value: SerialForm) => {
|
||||
console.log('open', value.port);
|
||||
openSerial(value.port);
|
||||
setConsole('');
|
||||
};
|
||||
|
||||
const openSerial = (port: string) => {
|
||||
sendRPCPacket(RpcMessage.CloseSerialRequest, new CloseSerialRequestT());
|
||||
const req = new OpenSerialRequestT();
|
||||
req.auto = port === 'Auto';
|
||||
req.port = port;
|
||||
console.log('Open with', req);
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, req);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(RpcMessage.SerialDevicesRequest, new SerialDevicesRequestT());
|
||||
const typedState: { serialPort: string } = state as any;
|
||||
if (typedState.serialPort) {
|
||||
reset({ port: typedState.serialPort });
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
return () => {
|
||||
sendRPCPacket(RpcMessage.CloseSerialRequest, new CloseSerialRequestT());
|
||||
};
|
||||
@@ -45,9 +89,6 @@ export function Serial() {
|
||||
(data: SerialUpdateResponseT) => {
|
||||
if (data.closed) {
|
||||
setSerialOpen(false);
|
||||
setTimeout(() => {
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (!data.closed) {
|
||||
@@ -60,6 +101,16 @@ export function Serial() {
|
||||
}
|
||||
);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.SerialDevicesResponse,
|
||||
(res: SerialDevicesResponseT) => {
|
||||
setSerialDevices([
|
||||
{ name: 'Auto', port: 'Auto' },
|
||||
...(res.devices || []),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (consoleRef.current)
|
||||
consoleRef.current.scrollTo({
|
||||
@@ -69,15 +120,14 @@ export function Serial() {
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
if (!isSerialOpen)
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, new OpenSerialRequestT());
|
||||
if (!isSerialOpen) openSerial(port);
|
||||
else clearInterval(id);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [isSerialOpen, sendRPCPacket]);
|
||||
}, [isSerialOpen]);
|
||||
|
||||
const reboot = () => {
|
||||
sendRPCPacket(
|
||||
@@ -128,15 +178,29 @@ export function Serial() {
|
||||
</div>
|
||||
<div className="" ref={toolbarRef}>
|
||||
<div className="border-t-2 pt-2 border-background-60 border-solid m-2 gap-2 flex flex-row">
|
||||
<Button variant="quaternary" onClick={reboot}>
|
||||
Reboot
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={factoryReset}>
|
||||
Factory Reset
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={getInfos}>
|
||||
Get Infos
|
||||
</Button>
|
||||
<div className="flex flex-grow gap-2">
|
||||
<Button variant="quaternary" onClick={reboot}>
|
||||
Reboot
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={factoryReset}>
|
||||
Factory Reset
|
||||
</Button>
|
||||
<Button variant="quaternary" onClick={getInfos}>
|
||||
Get Infos
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Dropdown
|
||||
control={control}
|
||||
name="port"
|
||||
placeholder="Select a serial port"
|
||||
items={serialDevices.map((device) => ({
|
||||
label: device.name?.toString() || 'error',
|
||||
value: device.port?.toString() || 'error',
|
||||
}))}
|
||||
></Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
BaseDirectory,
|
||||
readTextFile,
|
||||
writeFile,
|
||||
createDir,
|
||||
readTextFile,
|
||||
renameFile,
|
||||
writeFile
|
||||
} from '@tauri-apps/api/fs';
|
||||
|
||||
import { createContext, useContext, useRef, useState } from 'react';
|
||||
@@ -18,7 +18,7 @@ export interface WindowConfig {
|
||||
export interface Config {
|
||||
debug: boolean;
|
||||
doneOnboarding: boolean;
|
||||
window: WindowConfig;
|
||||
watchNewDevices: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigContext {
|
||||
@@ -28,7 +28,7 @@ export interface ConfigContext {
|
||||
loadConfig: () => Promise<Config>;
|
||||
}
|
||||
|
||||
const initialConfig = { doneOnboarding: false };
|
||||
const initialConfig = { doneOnboarding: false, watchNewDevices: true };
|
||||
|
||||
export function useConfigProvider(): ConfigContext {
|
||||
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -51,10 +51,9 @@ export function useConfigProvider(): ConfigContext {
|
||||
{ contents: JSON.stringify(newConfig), path: 'config.json.tmp' },
|
||||
{ dir: BaseDirectory.App }
|
||||
);
|
||||
await renameFile(
|
||||
'config.json.tmp', 'config.json',
|
||||
{ dir: BaseDirectory.App }
|
||||
);
|
||||
await renameFile('config.json.tmp', 'config.json', {
|
||||
dir: BaseDirectory.App,
|
||||
});
|
||||
debounceTimer.current = null;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
9
gui/src/hooks/previous.ts
Normal file
9
gui/src/hooks/previous.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
useEffect(() => {
|
||||
ref.current = value; // assign the value of ref to the argument
|
||||
}, [value]); // this code will run when the value of 'value' changes
|
||||
return ref.current; // in the end, return the current ref value.
|
||||
}
|
||||
61
gui/src/hooks/wifi-form.tsx
Normal file
61
gui/src/hooks/wifi-form.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Input } from '../components/commons/Input';
|
||||
import { useOnboarding } from './onboarding';
|
||||
|
||||
export interface WifiFormData {
|
||||
ssid: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useWifiForm() {
|
||||
const navigate = useNavigate();
|
||||
const { state, setWifiCredentials } = useOnboarding();
|
||||
const { register, reset, handleSubmit, formState } = useForm<WifiFormData>({
|
||||
defaultValues: {},
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (state.wifi) {
|
||||
reset({
|
||||
ssid: state.wifi.ssid,
|
||||
password: state.wifi.password,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const submitWifiCreds = (value: WifiFormData) => {
|
||||
setWifiCredentials(value.ssid, value.password);
|
||||
navigate('/onboarding/connect-trackers', {
|
||||
state: { alonePage: state.alonePage },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
submitWifiCreds,
|
||||
handleSubmit,
|
||||
register,
|
||||
formState,
|
||||
hasWifiCreds: !!state.wifi,
|
||||
WifiForm: () => (
|
||||
<>
|
||||
<Input
|
||||
{...register('ssid', { required: true })}
|
||||
type="text"
|
||||
label="SSID"
|
||||
placeholder="Enter SSID"
|
||||
variant="secondary"
|
||||
/>
|
||||
<Input
|
||||
{...register('password')}
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Enter password"
|
||||
variant="secondary"
|
||||
/>
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
|
||||
import { esbuildCommonjs, viteCommonjs } from '@originjs/vite-plugin-commonjs';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
@@ -15,6 +15,7 @@ export default defineConfig({
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
target: 'es2020',
|
||||
plugins: [esbuildCommonjs(['solarxr-protocol'])],
|
||||
},
|
||||
needsInterop: ['solarxr-protocol'],
|
||||
include: ['solarxr-protocol'],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.protocol.datafeed.DataFeedHandler;
|
||||
import dev.slimevr.protocol.pubsub.PubSubHandler;
|
||||
import dev.slimevr.protocol.rpc.RPCHandler;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.data_feed.DataFeedMessageHeader;
|
||||
import solarxr_protocol.pub_sub.PubSubHeader;
|
||||
|
||||
@@ -1,844 +0,0 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import com.jme3.math.Quaternion;
|
||||
import dev.slimevr.autobone.AutoBone.Epoch;
|
||||
import dev.slimevr.autobone.AutoBoneListener;
|
||||
import dev.slimevr.autobone.AutoBoneProcessType;
|
||||
import dev.slimevr.config.FiltersConfig;
|
||||
import dev.slimevr.config.OSCConfig;
|
||||
import dev.slimevr.config.OverlayConfig;
|
||||
import dev.slimevr.filtering.TrackerFilters;
|
||||
import dev.slimevr.osc.VRCOSCHandler;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.serial.SerialListener;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigOffsets;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigToggles;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValues;
|
||||
import dev.slimevr.vr.trackers.IMUTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.datatypes.TransactionId;
|
||||
import solarxr_protocol.rpc.*;
|
||||
import solarxr_protocol.rpc.settings.ModelRatios;
|
||||
import solarxr_protocol.rpc.settings.ModelSettings;
|
||||
import solarxr_protocol.rpc.settings.ModelToggles;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
implements SerialListener, AutoBoneListener {
|
||||
|
||||
private final ProtocolAPI api;
|
||||
|
||||
private long currTransactionId = 0;
|
||||
|
||||
public RPCHandler(ProtocolAPI api) {
|
||||
super();
|
||||
this.api = api;
|
||||
|
||||
registerPacketListener(RpcMessage.ResetRequest, this::onResetRequest);
|
||||
registerPacketListener(RpcMessage.AssignTrackerRequest, this::onAssignTrackerRequest);
|
||||
registerPacketListener(RpcMessage.SettingsRequest, this::onSettingsRequest);
|
||||
registerPacketListener(RpcMessage.ChangeSettingsRequest, this::onChangeSettingsRequest);
|
||||
|
||||
registerPacketListener(RpcMessage.RecordBVHRequest, this::onRecordBVHRequest);
|
||||
|
||||
registerPacketListener(RpcMessage.SkeletonResetAllRequest, this::onSkeletonResetAllRequest);
|
||||
registerPacketListener(RpcMessage.SkeletonConfigRequest, this::onSkeletonConfigRequest);
|
||||
registerPacketListener(
|
||||
RpcMessage.ChangeSkeletonConfigRequest,
|
||||
this::onChangeSkeletonConfigRequest
|
||||
);
|
||||
|
||||
registerPacketListener(RpcMessage.SetWifiRequest, this::onSetWifiRequest);
|
||||
registerPacketListener(
|
||||
RpcMessage.SerialTrackerRebootRequest,
|
||||
this::SerialTrackerRebootRequest
|
||||
);
|
||||
registerPacketListener(
|
||||
RpcMessage.SerialTrackerGetInfoRequest,
|
||||
this::SerialTrackerGetInfoRequest
|
||||
);
|
||||
registerPacketListener(
|
||||
RpcMessage.SerialTrackerFactoryResetRequest,
|
||||
this::SerialTrackerFactoryResetRequest
|
||||
);
|
||||
registerPacketListener(RpcMessage.OpenSerialRequest, this::onOpenSerialRequest);
|
||||
registerPacketListener(RpcMessage.CloseSerialRequest, this::onCloseSerialRequest);
|
||||
|
||||
registerPacketListener(RpcMessage.AutoBoneProcessRequest, this::onAutoBoneProcessRequest);
|
||||
|
||||
registerPacketListener(
|
||||
RpcMessage.OverlayDisplayModeChangeRequest,
|
||||
this::onOverlayDisplayModeChangeRequest
|
||||
);
|
||||
registerPacketListener(
|
||||
RpcMessage.OverlayDisplayModeRequest,
|
||||
this::onOverlayDisplayModeRequest
|
||||
);
|
||||
|
||||
this.api.server.getSerialHandler().addListener(this);
|
||||
this.api.server.getAutoBoneHandler().addListener(this);
|
||||
}
|
||||
|
||||
private void onOverlayDisplayModeRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
OverlayConfig config = this.api.server.getConfigManager().getVrConfig().getOverlay();
|
||||
int response = OverlayDisplayModeResponse
|
||||
.createOverlayDisplayModeResponse(fbb, config.isVisible(), config.isMirrored());
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.OverlayDisplayModeResponse, response);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
private void onOverlayDisplayModeChangeRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
OverlayDisplayModeChangeRequest req = (OverlayDisplayModeChangeRequest) messageHeader
|
||||
.message(new OverlayDisplayModeChangeRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
OverlayConfig config = this.api.server.getConfigManager().getVrConfig().getOverlay();
|
||||
config.setMirrored(req.isMirrored());
|
||||
config.setVisible(req.isVisible());
|
||||
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
public void onSetWifiRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SetWifiRequest req = (SetWifiRequest) messageHeader.message(new SetWifiRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (
|
||||
req.password() == null
|
||||
|| req.ssid() == null
|
||||
|| !this.api.server.getSerialHandler().isConnected()
|
||||
)
|
||||
return;
|
||||
this.api.server.getSerialHandler().setWifi(req.ssid(), req.password());
|
||||
}
|
||||
|
||||
public void onOpenSerialRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
OpenSerialRequest req = (OpenSerialRequest) messageHeader.message(new OpenSerialRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
conn.getContext().setUseSerial(true);
|
||||
|
||||
try {
|
||||
this.api.server.getSerialHandler().openSerial();
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Unable to open serial port", e);
|
||||
} catch (Throwable e) {
|
||||
LogManager.severe("Using serial ports is not supported on this platform", e);
|
||||
}
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, !this.api.server.getSerialHandler().isConnected());
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onCloseSerialRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
CloseSerialRequest req = (CloseSerialRequest) messageHeader
|
||||
.message(new CloseSerialRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
conn.getContext().setUseSerial(false);
|
||||
|
||||
this.api.server.getSerialHandler().closeSerial();
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, !this.api.server.getSerialHandler().isConnected());
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onSkeletonResetAllRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SkeletonResetAllRequest req = (SkeletonResetAllRequest) messageHeader
|
||||
.message(new SkeletonResetAllRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.humanPoseProcessor.getSkeletonConfig().resetConfigs();
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
|
||||
// might not be a good idea maybe let the client ask again
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
|
||||
int config = RPCBuilder.createSkeletonConfig(fbb, this.api.server.humanPoseProcessor);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onSkeletonConfigRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SkeletonConfigRequest req = (SkeletonConfigRequest) messageHeader
|
||||
.message(new SkeletonConfigRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
|
||||
int config = RPCBuilder.createSkeletonConfig(fbb, this.api.server.humanPoseProcessor);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onChangeSkeletonConfigRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
ChangeSkeletonConfigRequest req = (ChangeSkeletonConfigRequest) messageHeader
|
||||
.message(new ChangeSkeletonConfigRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
SkeletonConfigOffsets joint = SkeletonConfigOffsets.getById(req.bone());
|
||||
|
||||
this.api.server.humanPoseProcessor.setSkeletonConfig(joint, req.value());
|
||||
this.api.server.humanPoseProcessor.getSkeletonConfig().save();
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
public void onRecordBVHRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
RecordBVHRequest req = (RecordBVHRequest) messageHeader.message(new RecordBVHRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.stop()) {
|
||||
if (this.api.server.getBvhRecorder().isRecording())
|
||||
this.api.server.getBvhRecorder().endRecording();
|
||||
} else {
|
||||
if (!this.api.server.getBvhRecorder().isRecording())
|
||||
this.api.server.getBvhRecorder().startRecording();
|
||||
}
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(40);
|
||||
int status = RecordBVHStatus
|
||||
.createRecordBVHStatus(fbb, this.api.server.getBvhRecorder().isRecording());
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onResetRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
ResetRequest req = (ResetRequest) messageHeader.message(new ResetRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.resetType() == ResetType.Quick)
|
||||
this.api.server.resetTrackersYaw();
|
||||
if (req.resetType() == ResetType.Full)
|
||||
this.api.server.resetTrackers();
|
||||
if (req.resetType() == ResetType.Mounting)
|
||||
this.api.server.resetTrackersMounting();
|
||||
LogManager.severe("[WebSocketAPI] Reset performed");
|
||||
}
|
||||
|
||||
public void onAssignTrackerRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
AssignTrackerRequest req = (AssignTrackerRequest) messageHeader
|
||||
.message(new AssignTrackerRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
Tracker tracker = this.api.server.getTrackerById(req.trackerId().unpack());
|
||||
if (tracker == null)
|
||||
return;
|
||||
tracker = tracker.get();
|
||||
|
||||
TrackerPosition pos = TrackerPosition.getByBodyPart(req.bodyPosition()).orElse(null);
|
||||
tracker.setBodyPosition(pos);
|
||||
|
||||
if (req.mountingRotation() != null) {
|
||||
if (tracker instanceof IMUTracker imu) {
|
||||
imu
|
||||
.setMountingRotation(
|
||||
new Quaternion(
|
||||
req.mountingRotation().x(),
|
||||
req.mountingRotation().y(),
|
||||
req.mountingRotation().z(),
|
||||
req.mountingRotation().w()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (req.displayName() != null) {
|
||||
if (tracker instanceof IMUTracker imu) {
|
||||
imu.setCustomName(req.displayName());
|
||||
}
|
||||
}
|
||||
|
||||
this.api.server.trackerUpdated(tracker);
|
||||
}
|
||||
|
||||
public void onSettingsRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
WindowsNamedPipeBridge bridge = this.api.server.getVRBridge(WindowsNamedPipeBridge.class);
|
||||
|
||||
int steamvrTrackerSettings = 0;
|
||||
if (bridge != null) {
|
||||
steamvrTrackerSettings = SteamVRTrackersSetting
|
||||
.createSteamVRTrackersSetting(
|
||||
fbb,
|
||||
bridge.getShareSetting(TrackerRole.WAIST),
|
||||
bridge.getShareSetting(TrackerRole.CHEST),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_FOOT)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_FOOT),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_KNEE)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_KNEE),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_ELBOW)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_ELBOW)
|
||||
);
|
||||
}
|
||||
|
||||
FiltersConfig filtersConfig = this.api.server
|
||||
.getConfigManager()
|
||||
.getVrConfig()
|
||||
.getFilters();
|
||||
|
||||
int filterSettings = FilteringSettings
|
||||
.createFilteringSettings(
|
||||
fbb,
|
||||
TrackerFilters.getByConfigkey(filtersConfig.getType()).id,
|
||||
filtersConfig.getAmount()
|
||||
);
|
||||
|
||||
OSCConfig vrcOSCConfig = this.api.server
|
||||
.getConfigManager()
|
||||
.getVrConfig()
|
||||
.getVrcOSC();
|
||||
int vrcOSCSettings = createOSCSettings(
|
||||
fbb,
|
||||
vrcOSCConfig
|
||||
);
|
||||
|
||||
var config = this.api.server.humanPoseProcessor.getSkeletonConfig();
|
||||
int togglesOffset = ModelToggles
|
||||
.createModelToggles(
|
||||
fbb,
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_PELVIS_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD),
|
||||
config.getToggle(SkeletonConfigToggles.SKATING_CORRECTION),
|
||||
config.getToggle(SkeletonConfigToggles.FLOOR_CLIP)
|
||||
);
|
||||
int ratiosOffset = ModelRatios
|
||||
.createModelRatios(
|
||||
fbb,
|
||||
config.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING)
|
||||
);
|
||||
int modelSettings = ModelSettings.createModelSettings(fbb, togglesOffset, ratiosOffset);
|
||||
|
||||
int settings = SettingsResponse
|
||||
.createSettingsResponse(
|
||||
fbb,
|
||||
steamvrTrackerSettings,
|
||||
filterSettings,
|
||||
vrcOSCSettings,
|
||||
modelSettings
|
||||
);
|
||||
int outbound = createRPCMessage(fbb, RpcMessage.SettingsResponse, settings);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
private static int createOSCSettings(
|
||||
FlatBufferBuilder fbb,
|
||||
OSCConfig config
|
||||
) {
|
||||
|
||||
int trackersSettingOffset = OSCTrackersSetting
|
||||
.createOSCTrackersSetting(
|
||||
fbb,
|
||||
config.getOSCTrackerRole(TrackerRole.HEAD, false),
|
||||
config.getOSCTrackerRole(TrackerRole.CHEST, false),
|
||||
config.getOSCTrackerRole(TrackerRole.WAIST, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_KNEE, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_FOOT, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_ELBOW, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_HAND, false)
|
||||
);
|
||||
|
||||
int addressStringOffset = fbb.createString(config.getAddress());
|
||||
VRCOSCSettings.startVRCOSCSettings(fbb);
|
||||
VRCOSCSettings.addEnabled(fbb, config.getEnabled());
|
||||
VRCOSCSettings.addPortIn(fbb, config.getPortIn());
|
||||
VRCOSCSettings.addPortOut(fbb, config.getPortOut());
|
||||
VRCOSCSettings
|
||||
.addAddress(
|
||||
fbb,
|
||||
addressStringOffset
|
||||
);
|
||||
VRCOSCSettings
|
||||
.addTrackers(
|
||||
fbb,
|
||||
trackersSettingOffset
|
||||
);
|
||||
|
||||
return VRCOSCSettings.endVRCOSCSettings(fbb);
|
||||
}
|
||||
|
||||
public void onChangeSettingsRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
|
||||
ChangeSettingsRequest req = (ChangeSettingsRequest) messageHeader
|
||||
.message(new ChangeSettingsRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.steamVrTrackers() != null) {
|
||||
WindowsNamedPipeBridge bridge = this.api.server
|
||||
.getVRBridge(WindowsNamedPipeBridge.class);
|
||||
|
||||
if (bridge != null) {
|
||||
bridge.changeShareSettings(TrackerRole.WAIST, req.steamVrTrackers().waist());
|
||||
bridge.changeShareSettings(TrackerRole.CHEST, req.steamVrTrackers().chest());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_FOOT, req.steamVrTrackers().feet());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_FOOT, req.steamVrTrackers().feet());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_KNEE, req.steamVrTrackers().knees());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_KNEE, req.steamVrTrackers().knees());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_ELBOW, req.steamVrTrackers().elbows());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_ELBOW, req.steamVrTrackers().elbows());
|
||||
}
|
||||
}
|
||||
|
||||
if (req.filtering() != null) {
|
||||
TrackerFilters type = TrackerFilters.fromId(req.filtering().type());
|
||||
if (type != null) {
|
||||
FiltersConfig filtersConfig = this.api.server
|
||||
.getConfigManager()
|
||||
.getVrConfig()
|
||||
.getFilters();
|
||||
filtersConfig.setType(type.configKey);
|
||||
filtersConfig.setAmount(req.filtering().amount());
|
||||
filtersConfig.updateTrackersFilters();
|
||||
}
|
||||
}
|
||||
|
||||
if (req.vrcOsc() != null) {
|
||||
OSCConfig vrcOSCConfig = this.api.server
|
||||
.getConfigManager()
|
||||
.getVrConfig()
|
||||
.getVrcOSC();
|
||||
if (vrcOSCConfig != null) {
|
||||
VRCOSCHandler VRCOSCHandler = this.api.server.getVRCOSCHandler();
|
||||
var trackers = req.vrcOsc().trackers();
|
||||
|
||||
vrcOSCConfig.setEnabled(req.vrcOsc().enabled());
|
||||
vrcOSCConfig.setPortIn(req.vrcOsc().portIn());
|
||||
vrcOSCConfig.setPortOut(req.vrcOsc().portOut());
|
||||
vrcOSCConfig.setAddress(req.vrcOsc().address());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.HEAD, trackers.head());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.CHEST, trackers.chest());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.WAIST, trackers.waist());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_KNEE, trackers.knees());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_KNEE, trackers.knees());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_FOOT, trackers.feet());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_FOOT, trackers.feet());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_ELBOW, trackers.elbows());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_ELBOW, trackers.elbows());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_HAND, trackers.hands());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_HAND, trackers.hands());
|
||||
|
||||
|
||||
VRCOSCHandler.refreshSettings();
|
||||
}
|
||||
}
|
||||
|
||||
var modelSettings = req.modelSettings();
|
||||
if (modelSettings != null) {
|
||||
var cfg = this.api.server.humanPoseProcessor.getSkeletonConfig();
|
||||
var toggles = modelSettings.toggles();
|
||||
var ratios = modelSettings.ratios();
|
||||
|
||||
if (toggles != null) {
|
||||
// Note: toggles.has____ returns the same as toggles._____ this
|
||||
// seems like a bug
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_SPINE_MODEL,
|
||||
toggles.extendedSpine()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
|
||||
toggles.extendedPelvis()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_KNEE_MODEL,
|
||||
toggles.extendedKnee()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
|
||||
toggles.forceArmsFromHmd()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_SPINE_MODEL,
|
||||
toggles.extendedSpine()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
|
||||
toggles.extendedPelvis()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_KNEE_MODEL,
|
||||
toggles.extendedKnee()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
|
||||
toggles.forceArmsFromHmd()
|
||||
);
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.FLOOR_CLIP,
|
||||
toggles.floorClip()
|
||||
);
|
||||
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.SKATING_CORRECTION,
|
||||
toggles.skatingCorrection()
|
||||
);
|
||||
}
|
||||
|
||||
if (ratios != null) {
|
||||
if (ratios.hasImputeWaistFromChestHip()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING,
|
||||
ratios.imputeWaistFromChestHip()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeWaistFromChestLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING,
|
||||
ratios.imputeWaistFromChestLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeHipFromChestLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING,
|
||||
ratios.imputeHipFromChestLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeHipFromWaistLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING,
|
||||
ratios.imputeHipFromWaistLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasInterpHipLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.HIP_LEGS_AVERAGING,
|
||||
ratios.interpHipLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasInterpKneeTrackerAnkle()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING,
|
||||
ratios.interpKneeTrackerAnkle()
|
||||
);
|
||||
}
|
||||
}
|
||||
cfg.save();
|
||||
}
|
||||
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(GenericConnection conn, RpcMessageHeader message) {
|
||||
BiConsumer<GenericConnection, RpcMessageHeader> consumer = this.handlers[message
|
||||
.messageType()];
|
||||
if (consumer != null)
|
||||
consumer.accept(conn, message);
|
||||
else
|
||||
LogManager
|
||||
.info("[ProtocolAPI] Unhandled RPC packet received id: " + message.messageType());
|
||||
}
|
||||
|
||||
public int createRPCMessage(FlatBufferBuilder fbb, byte messageType, int messageOffset) {
|
||||
int[] data = new int[1];
|
||||
|
||||
RpcMessageHeader.startRpcMessageHeader(fbb);
|
||||
RpcMessageHeader.addMessage(fbb, messageOffset);
|
||||
RpcMessageHeader.addMessageType(fbb, messageType);
|
||||
RpcMessageHeader.addTxId(fbb, TransactionId.createTransactionId(fbb, currTransactionId++));
|
||||
data[0] = RpcMessageHeader.endRpcMessageHeader(fbb);
|
||||
|
||||
int messages = MessageBundle.createRpcMsgsVector(fbb, data);
|
||||
|
||||
MessageBundle.startMessageBundle(fbb);
|
||||
MessageBundle.addRpcMsgs(fbb, messages);
|
||||
return MessageBundle.endMessageBundle(fbb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messagesCount() {
|
||||
return RpcMessage.names.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialConnected(SerialPort port) {
|
||||
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useSerial())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, false);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public void SerialTrackerRebootRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerRebootRequest req = (SerialTrackerRebootRequest) messageHeader
|
||||
.message(new SerialTrackerRebootRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().rebootRequest();
|
||||
}
|
||||
|
||||
public void SerialTrackerGetInfoRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerGetInfoRequest req = (SerialTrackerGetInfoRequest) messageHeader
|
||||
.message(new SerialTrackerGetInfoRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().infoRequest();
|
||||
}
|
||||
|
||||
public void SerialTrackerFactoryResetRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerFactoryResetRequest req = (SerialTrackerFactoryResetRequest) messageHeader
|
||||
.message(new SerialTrackerFactoryResetRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().factoryResetRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialDisconnected() {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useSerial())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, true);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
conn.getContext().setUseSerial(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialLog(String str) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useSerial())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int logOffset = fbb.createString(str);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addLog(fbb, logOffset);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public void onAutoBoneProcessRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
AutoBoneProcessRequest req = (AutoBoneProcessRequest) messageHeader
|
||||
.message(new AutoBoneProcessRequest());
|
||||
if (req == null || conn.getContext().useAutoBone())
|
||||
return;
|
||||
|
||||
conn.getContext().setUseAutoBone(true);
|
||||
this.api.server
|
||||
.getAutoBoneHandler()
|
||||
.startProcessByType(AutoBoneProcessType.getById(req.processType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
Integer messageOffset = message != null ? fbb.createString(message) : null;
|
||||
|
||||
AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb);
|
||||
AutoBoneProcessStatusResponse.addProcessType(fbb, processType.id);
|
||||
if (messageOffset != null)
|
||||
AutoBoneProcessStatusResponse.addMessage(fbb, messageOffset);
|
||||
if (total > 0 && current >= 0) {
|
||||
AutoBoneProcessStatusResponse.addCurrent(fbb, current);
|
||||
AutoBoneProcessStatusResponse.addTotal(fbb, total);
|
||||
}
|
||||
AutoBoneProcessStatusResponse.addCompleted(fbb, completed);
|
||||
AutoBoneProcessStatusResponse.addSuccess(fbb, success);
|
||||
int update = AutoBoneProcessStatusResponse
|
||||
.endAutoBoneProcessStatusResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.AutoBoneProcessStatusResponse,
|
||||
update
|
||||
);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
if (completed) {
|
||||
conn.getContext().setUseAutoBone(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneRecordingEnd(PoseFrames recording) {
|
||||
// Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEpoch(Epoch epoch) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int[] skeletonPartOffsets = new int[epoch.configValues.size()];
|
||||
int i = 0;
|
||||
for (
|
||||
Entry<SkeletonConfigOffsets, Float> skeletonConfig : epoch.configValues
|
||||
.entrySet()
|
||||
) {
|
||||
skeletonPartOffsets[i++] = SkeletonPart
|
||||
.createSkeletonPart(
|
||||
fbb,
|
||||
skeletonConfig.getKey().id,
|
||||
skeletonConfig.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
int skeletonPartsOffset = AutoBoneEpochResponse
|
||||
.createAdjustedSkeletonPartsVector(fbb, skeletonPartOffsets);
|
||||
|
||||
int update = AutoBoneEpochResponse
|
||||
.createAutoBoneEpochResponse(
|
||||
fbb,
|
||||
epoch.epoch,
|
||||
epoch.totalEpochs,
|
||||
epoch.epochError.getMean(),
|
||||
skeletonPartsOffset
|
||||
);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.AutoBoneEpochResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEnd(EnumMap<SkeletonConfigOffsets, Float> configValues) {
|
||||
// Do nothing, the last epoch from "onAutoBoneEpoch" should be all
|
||||
// that's needed
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dev.slimevr.protocol;
|
||||
package dev.slimevr.protocol.datafeed;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import com.jme3.math.Quaternion;
|
||||
@@ -1,6 +1,9 @@
|
||||
package dev.slimevr.protocol;
|
||||
package dev.slimevr.protocol.datafeed;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.ProtocolHandler;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.data_feed.*;
|
||||
@@ -124,12 +127,10 @@ public class DataFeedHandler extends ProtocolHandler<DataFeedMessageHeader> {
|
||||
data[index] = DataFeedMessageHeader.endDataFeedMessageHeader(fbb);
|
||||
|
||||
conn.getContext().getDataFeedTimers().set(index, currTime);
|
||||
if (fbb != null) {
|
||||
int messages = MessageBundle.createDataFeedMsgsVector(fbb, data);
|
||||
int packet = createMessage(fbb, messages);
|
||||
fbb.finish(packet);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
int messages = MessageBundle.createDataFeedMsgsVector(fbb, data);
|
||||
int packet = createMessage(fbb, messages);
|
||||
fbb.finish(packet);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -1,4 +1,4 @@
|
||||
package dev.slimevr.protocol;
|
||||
package dev.slimevr.protocol.pubsub;
|
||||
|
||||
import solarxr_protocol.pub_sub.TopicIdT;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package dev.slimevr.protocol;
|
||||
package dev.slimevr.protocol.pubsub;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.ProtocolHandler;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.pub_sub.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
package dev.slimevr.protocol;
|
||||
package dev.slimevr.protocol.rpc;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.vr.processor.HumanPoseProcessor;
|
||||
364
server/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java
Normal file
364
server/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java
Normal file
@@ -0,0 +1,364 @@
|
||||
package dev.slimevr.protocol.rpc;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import com.jme3.math.Quaternion;
|
||||
import dev.slimevr.autobone.AutoBone.Epoch;
|
||||
import dev.slimevr.autobone.AutoBoneListener;
|
||||
import dev.slimevr.autobone.AutoBoneProcessType;
|
||||
import dev.slimevr.config.OverlayConfig;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.ProtocolHandler;
|
||||
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler;
|
||||
import dev.slimevr.protocol.rpc.settings.RPCSettingsHandler;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigOffsets;
|
||||
import dev.slimevr.vr.trackers.IMUTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.datatypes.TransactionId;
|
||||
import solarxr_protocol.rpc.*;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
implements AutoBoneListener {
|
||||
|
||||
private final ProtocolAPI api;
|
||||
|
||||
private long currTransactionId = 0;
|
||||
|
||||
public RPCHandler(ProtocolAPI api) {
|
||||
super();
|
||||
this.api = api;
|
||||
|
||||
new RPCSerialHandler(this, api);
|
||||
new RPCSettingsHandler(this, api);
|
||||
|
||||
registerPacketListener(RpcMessage.ResetRequest, this::onResetRequest);
|
||||
registerPacketListener(RpcMessage.AssignTrackerRequest, this::onAssignTrackerRequest);
|
||||
|
||||
|
||||
registerPacketListener(RpcMessage.RecordBVHRequest, this::onRecordBVHRequest);
|
||||
|
||||
registerPacketListener(RpcMessage.SkeletonResetAllRequest, this::onSkeletonResetAllRequest);
|
||||
registerPacketListener(RpcMessage.SkeletonConfigRequest, this::onSkeletonConfigRequest);
|
||||
registerPacketListener(
|
||||
RpcMessage.ChangeSkeletonConfigRequest,
|
||||
this::onChangeSkeletonConfigRequest
|
||||
);
|
||||
|
||||
registerPacketListener(RpcMessage.AutoBoneProcessRequest, this::onAutoBoneProcessRequest);
|
||||
|
||||
registerPacketListener(
|
||||
RpcMessage.OverlayDisplayModeChangeRequest,
|
||||
this::onOverlayDisplayModeChangeRequest
|
||||
);
|
||||
registerPacketListener(
|
||||
RpcMessage.OverlayDisplayModeRequest,
|
||||
this::onOverlayDisplayModeRequest
|
||||
);
|
||||
|
||||
this.api.server.getAutoBoneHandler().addListener(this);
|
||||
}
|
||||
|
||||
private void onOverlayDisplayModeRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
OverlayConfig config = this.api.server.getConfigManager().getVrConfig().getOverlay();
|
||||
int response = OverlayDisplayModeResponse
|
||||
.createOverlayDisplayModeResponse(fbb, config.isVisible(), config.isMirrored());
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.OverlayDisplayModeResponse, response);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
private void onOverlayDisplayModeChangeRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
OverlayDisplayModeChangeRequest req = (OverlayDisplayModeChangeRequest) messageHeader
|
||||
.message(new OverlayDisplayModeChangeRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
OverlayConfig config = this.api.server.getConfigManager().getVrConfig().getOverlay();
|
||||
config.setMirrored(req.isMirrored());
|
||||
config.setVisible(req.isVisible());
|
||||
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
public void onSkeletonResetAllRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SkeletonResetAllRequest req = (SkeletonResetAllRequest) messageHeader
|
||||
.message(new SkeletonResetAllRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.humanPoseProcessor.getSkeletonConfig().resetConfigs();
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
|
||||
// might not be a good idea maybe let the client ask again
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
|
||||
int config = RPCBuilder.createSkeletonConfig(fbb, this.api.server.humanPoseProcessor);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onSkeletonConfigRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SkeletonConfigRequest req = (SkeletonConfigRequest) messageHeader
|
||||
.message(new SkeletonConfigRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
|
||||
int config = RPCBuilder.createSkeletonConfig(fbb, this.api.server.humanPoseProcessor);
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onChangeSkeletonConfigRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
ChangeSkeletonConfigRequest req = (ChangeSkeletonConfigRequest) messageHeader
|
||||
.message(new ChangeSkeletonConfigRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
SkeletonConfigOffsets joint = SkeletonConfigOffsets.getById(req.bone());
|
||||
|
||||
this.api.server.humanPoseProcessor.setSkeletonConfig(joint, req.value());
|
||||
this.api.server.humanPoseProcessor.getSkeletonConfig().save();
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
public void onRecordBVHRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
RecordBVHRequest req = (RecordBVHRequest) messageHeader.message(new RecordBVHRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.stop()) {
|
||||
if (this.api.server.getBvhRecorder().isRecording())
|
||||
this.api.server.getBvhRecorder().endRecording();
|
||||
} else {
|
||||
if (!this.api.server.getBvhRecorder().isRecording())
|
||||
this.api.server.getBvhRecorder().startRecording();
|
||||
}
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(40);
|
||||
int status = RecordBVHStatus
|
||||
.createRecordBVHStatus(fbb, this.api.server.getBvhRecorder().isRecording());
|
||||
int outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onResetRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
ResetRequest req = (ResetRequest) messageHeader.message(new ResetRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.resetType() == ResetType.Quick)
|
||||
this.api.server.resetTrackersYaw();
|
||||
if (req.resetType() == ResetType.Full)
|
||||
this.api.server.resetTrackers();
|
||||
if (req.resetType() == ResetType.Mounting)
|
||||
this.api.server.resetTrackersMounting();
|
||||
LogManager.severe("[WebSocketAPI] Reset performed");
|
||||
}
|
||||
|
||||
public void onAssignTrackerRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
AssignTrackerRequest req = (AssignTrackerRequest) messageHeader
|
||||
.message(new AssignTrackerRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
Tracker tracker = this.api.server.getTrackerById(req.trackerId().unpack());
|
||||
if (tracker == null)
|
||||
return;
|
||||
tracker = tracker.get();
|
||||
|
||||
TrackerPosition pos = TrackerPosition.getByBodyPart(req.bodyPosition()).orElse(null);
|
||||
tracker.setBodyPosition(pos);
|
||||
|
||||
if (req.mountingRotation() != null) {
|
||||
if (tracker instanceof IMUTracker imu) {
|
||||
imu
|
||||
.setMountingRotation(
|
||||
new Quaternion(
|
||||
req.mountingRotation().x(),
|
||||
req.mountingRotation().y(),
|
||||
req.mountingRotation().z(),
|
||||
req.mountingRotation().w()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (req.displayName() != null) {
|
||||
if (tracker instanceof IMUTracker imu) {
|
||||
imu.setCustomName(req.displayName());
|
||||
}
|
||||
}
|
||||
|
||||
this.api.server.trackerUpdated(tracker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(GenericConnection conn, RpcMessageHeader message) {
|
||||
BiConsumer<GenericConnection, RpcMessageHeader> consumer = this.handlers[message
|
||||
.messageType()];
|
||||
if (consumer != null)
|
||||
consumer.accept(conn, message);
|
||||
else
|
||||
LogManager
|
||||
.info("[ProtocolAPI] Unhandled RPC packet received id: " + message.messageType());
|
||||
}
|
||||
|
||||
public int createRPCMessage(FlatBufferBuilder fbb, byte messageType, int messageOffset) {
|
||||
int[] data = new int[1];
|
||||
|
||||
RpcMessageHeader.startRpcMessageHeader(fbb);
|
||||
RpcMessageHeader.addMessage(fbb, messageOffset);
|
||||
RpcMessageHeader.addMessageType(fbb, messageType);
|
||||
RpcMessageHeader.addTxId(fbb, TransactionId.createTransactionId(fbb, currTransactionId++));
|
||||
data[0] = RpcMessageHeader.endRpcMessageHeader(fbb);
|
||||
|
||||
int messages = MessageBundle.createRpcMsgsVector(fbb, data);
|
||||
|
||||
MessageBundle.startMessageBundle(fbb);
|
||||
MessageBundle.addRpcMsgs(fbb, messages);
|
||||
return MessageBundle.endMessageBundle(fbb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messagesCount() {
|
||||
return RpcMessage.names.length;
|
||||
}
|
||||
|
||||
public void onAutoBoneProcessRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
AutoBoneProcessRequest req = (AutoBoneProcessRequest) messageHeader
|
||||
.message(new AutoBoneProcessRequest());
|
||||
if (req == null || conn.getContext().useAutoBone())
|
||||
return;
|
||||
|
||||
conn.getContext().setUseAutoBone(true);
|
||||
this.api.server
|
||||
.getAutoBoneHandler()
|
||||
.startProcessByType(AutoBoneProcessType.getById(req.processType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
Integer messageOffset = message != null ? fbb.createString(message) : null;
|
||||
|
||||
AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb);
|
||||
AutoBoneProcessStatusResponse.addProcessType(fbb, processType.id);
|
||||
if (messageOffset != null)
|
||||
AutoBoneProcessStatusResponse.addMessage(fbb, messageOffset);
|
||||
if (total > 0 && current >= 0) {
|
||||
AutoBoneProcessStatusResponse.addCurrent(fbb, current);
|
||||
AutoBoneProcessStatusResponse.addTotal(fbb, total);
|
||||
}
|
||||
AutoBoneProcessStatusResponse.addCompleted(fbb, completed);
|
||||
AutoBoneProcessStatusResponse.addSuccess(fbb, success);
|
||||
int update = AutoBoneProcessStatusResponse
|
||||
.endAutoBoneProcessStatusResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.AutoBoneProcessStatusResponse,
|
||||
update
|
||||
);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
if (completed) {
|
||||
conn.getContext().setUseAutoBone(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneRecordingEnd(PoseFrames recording) {
|
||||
// Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEpoch(Epoch epoch) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int[] skeletonPartOffsets = new int[epoch.configValues.size()];
|
||||
int i = 0;
|
||||
for (
|
||||
Entry<SkeletonConfigOffsets, Float> skeletonConfig : epoch.configValues
|
||||
.entrySet()
|
||||
) {
|
||||
skeletonPartOffsets[i++] = SkeletonPart
|
||||
.createSkeletonPart(
|
||||
fbb,
|
||||
skeletonConfig.getKey().id,
|
||||
skeletonConfig.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
int skeletonPartsOffset = AutoBoneEpochResponse
|
||||
.createAdjustedSkeletonPartsVector(fbb, skeletonPartOffsets);
|
||||
|
||||
int update = AutoBoneEpochResponse
|
||||
.createAutoBoneEpochResponse(
|
||||
fbb,
|
||||
epoch.epoch,
|
||||
epoch.totalEpochs,
|
||||
epoch.epochError.getMean(),
|
||||
skeletonPartsOffset
|
||||
);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.AutoBoneEpochResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEnd(EnumMap<SkeletonConfigOffsets, Float> configValues) {
|
||||
// Do nothing, the last epoch from "onAutoBoneEpoch" should be all
|
||||
// that's needed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package dev.slimevr.protocol.rpc.serial;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.rpc.RPCHandler;
|
||||
import dev.slimevr.serial.SerialListener;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.rpc.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
public record RPCSerialHandler(RPCHandler rpcHandler, ProtocolAPI api) implements SerialListener {
|
||||
|
||||
public RPCSerialHandler(RPCHandler rpcHandler, ProtocolAPI api) {
|
||||
this.rpcHandler = rpcHandler;
|
||||
this.api = api;
|
||||
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.SerialTrackerRebootRequest,
|
||||
this::onSerialTrackerRebootRequest
|
||||
);
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.SerialTrackerGetInfoRequest,
|
||||
this::onSerialTrackerGetInfoRequest
|
||||
);
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.SerialTrackerFactoryResetRequest,
|
||||
this::onSerialTrackerFactoryResetRequest
|
||||
);
|
||||
rpcHandler.registerPacketListener(RpcMessage.SetWifiRequest, this::onSetWifiRequest);
|
||||
rpcHandler.registerPacketListener(RpcMessage.OpenSerialRequest, this::onOpenSerialRequest);
|
||||
rpcHandler
|
||||
.registerPacketListener(RpcMessage.CloseSerialRequest, this::onCloseSerialRequest);
|
||||
rpcHandler
|
||||
.registerPacketListener(RpcMessage.SerialDevicesRequest, this::onRequestSerialDevices);
|
||||
this.api.server.getSerialHandler().addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialDisconnected() {
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, true);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
this.forAllListeners((conn) -> {
|
||||
conn.send(fbb.dataBuffer());
|
||||
conn.getContext().setUseSerial(false);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialLog(String str) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int logOffset = fbb.createString(str);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addLog(fbb, logOffset);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
this.forAllListeners((conn) -> {
|
||||
conn.send(fbb.dataBuffer());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialConnected(SerialPort port) {
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, false);
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
this.forAllListeners((conn) -> {
|
||||
conn.send(fbb.dataBuffer());
|
||||
});
|
||||
}
|
||||
|
||||
public void onSerialTrackerRebootRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerRebootRequest req = (SerialTrackerRebootRequest) messageHeader
|
||||
.message(new SerialTrackerRebootRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().rebootRequest();
|
||||
}
|
||||
|
||||
public void onSerialTrackerGetInfoRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerGetInfoRequest req = (SerialTrackerGetInfoRequest) messageHeader
|
||||
.message(new SerialTrackerGetInfoRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().infoRequest();
|
||||
}
|
||||
|
||||
public void onSerialTrackerFactoryResetRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
SerialTrackerFactoryResetRequest req = (SerialTrackerFactoryResetRequest) messageHeader
|
||||
.message(new SerialTrackerFactoryResetRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
this.api.server.getSerialHandler().factoryResetRequest();
|
||||
}
|
||||
|
||||
|
||||
private void onRequestSerialDevices(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SerialDevicesRequest req = (SerialDevicesRequest) messageHeader
|
||||
.message(new SerialDevicesRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
|
||||
List<Integer> devicesOffsets = new ArrayList<>();
|
||||
|
||||
try {
|
||||
this.api.server.getSerialHandler().getKnownPorts().forEach((port) -> {
|
||||
int portOffset = fbb.createString(port.getPortLocation());
|
||||
int nameOffset = fbb.createString(port.getDescriptivePortName());
|
||||
devicesOffsets.add(SerialDevice.createSerialDevice(fbb, portOffset, nameOffset));
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
LogManager.severe("Using serial ports is not supported on this platform", e);
|
||||
}
|
||||
|
||||
SerialDevicesResponse.startDevicesVector(fbb, devicesOffsets.size());
|
||||
devicesOffsets.forEach(offset -> SerialDevicesResponse.addDevices(fbb, offset));
|
||||
int devices = fbb.endVector();
|
||||
int serialDeviceOffsets = SerialDevicesResponse.createSerialDevicesResponse(fbb, devices);
|
||||
int outbound = rpcHandler
|
||||
.createRPCMessage(fbb, RpcMessage.SerialDevicesResponse, serialDeviceOffsets);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onSetWifiRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
SetWifiRequest req = (SetWifiRequest) messageHeader.message(new SetWifiRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (
|
||||
req.password() == null
|
||||
|| req.ssid() == null
|
||||
|| !this.api.server.getSerialHandler().isConnected()
|
||||
)
|
||||
return;
|
||||
this.api.server.getSerialHandler().setWifi(req.ssid(), req.password());
|
||||
}
|
||||
|
||||
public void onOpenSerialRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
OpenSerialRequest req = (OpenSerialRequest) messageHeader.message(new OpenSerialRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
conn.getContext().setUseSerial(true);
|
||||
|
||||
try {
|
||||
this.api.server.getSerialHandler().openSerial(req.port(), req.auto());
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Unable to open serial port", e);
|
||||
} catch (Throwable e) {
|
||||
LogManager.severe("Using serial ports is not supported on this platform", e);
|
||||
}
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, !this.api.server.getSerialHandler().isConnected());
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onCloseSerialRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
CloseSerialRequest req = (CloseSerialRequest) messageHeader
|
||||
.message(new CloseSerialRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
conn.getContext().setUseSerial(false);
|
||||
|
||||
this.api.server.getSerialHandler().closeSerial();
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
SerialUpdateResponse.startSerialUpdateResponse(fbb);
|
||||
SerialUpdateResponse.addClosed(fbb, !this.api.server.getSerialHandler().isConnected());
|
||||
int update = SerialUpdateResponse.endSerialUpdateResponse(fbb);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void forAllListeners(Consumer<? super GenericConnection> action) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useSerial())
|
||||
.forEach(action)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package dev.slimevr.protocol.rpc.settings;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.config.FiltersConfig;
|
||||
import dev.slimevr.config.OSCConfig;
|
||||
import dev.slimevr.filtering.TrackerFilters;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigToggles;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValues;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import solarxr_protocol.rpc.*;
|
||||
import solarxr_protocol.rpc.settings.ModelRatios;
|
||||
import solarxr_protocol.rpc.settings.ModelSettings;
|
||||
import solarxr_protocol.rpc.settings.ModelToggles;
|
||||
|
||||
|
||||
public class RPCSettingsBuilder {
|
||||
|
||||
public static int createOSCSettings(
|
||||
FlatBufferBuilder fbb,
|
||||
OSCConfig config
|
||||
) {
|
||||
|
||||
int trackersSettingOffset = OSCTrackersSetting
|
||||
.createOSCTrackersSetting(
|
||||
fbb,
|
||||
config.getOSCTrackerRole(TrackerRole.HEAD, false),
|
||||
config.getOSCTrackerRole(TrackerRole.CHEST, false),
|
||||
config.getOSCTrackerRole(TrackerRole.WAIST, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_KNEE, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_FOOT, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_ELBOW, false),
|
||||
config.getOSCTrackerRole(TrackerRole.LEFT_HAND, false)
|
||||
);
|
||||
|
||||
int addressStringOffset = fbb.createString(config.getAddress());
|
||||
VRCOSCSettings.startVRCOSCSettings(fbb);
|
||||
VRCOSCSettings.addEnabled(fbb, config.getEnabled());
|
||||
VRCOSCSettings.addPortIn(fbb, config.getPortIn());
|
||||
VRCOSCSettings.addPortOut(fbb, config.getPortOut());
|
||||
VRCOSCSettings
|
||||
.addAddress(
|
||||
fbb,
|
||||
addressStringOffset
|
||||
);
|
||||
VRCOSCSettings
|
||||
.addTrackers(
|
||||
fbb,
|
||||
trackersSettingOffset
|
||||
);
|
||||
|
||||
return VRCOSCSettings.endVRCOSCSettings(fbb);
|
||||
}
|
||||
|
||||
public static int createFilterSettings(
|
||||
FlatBufferBuilder fbb,
|
||||
FiltersConfig filtersConfig
|
||||
) {
|
||||
return FilteringSettings
|
||||
.createFilteringSettings(
|
||||
fbb,
|
||||
TrackerFilters.getByConfigkey(filtersConfig.getType()).id,
|
||||
filtersConfig.getAmount()
|
||||
);
|
||||
}
|
||||
|
||||
public static int createSteamVRSettings(FlatBufferBuilder fbb, WindowsNamedPipeBridge bridge) {
|
||||
int steamvrTrackerSettings = 0;
|
||||
if (bridge != null) {
|
||||
steamvrTrackerSettings = SteamVRTrackersSetting
|
||||
.createSteamVRTrackersSetting(
|
||||
fbb,
|
||||
bridge.getShareSetting(TrackerRole.WAIST),
|
||||
bridge.getShareSetting(TrackerRole.CHEST),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_FOOT)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_FOOT),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_KNEE)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_KNEE),
|
||||
bridge.getShareSetting(TrackerRole.LEFT_ELBOW)
|
||||
&& bridge.getShareSetting(TrackerRole.RIGHT_ELBOW)
|
||||
);
|
||||
}
|
||||
return steamvrTrackerSettings;
|
||||
}
|
||||
|
||||
public static int createModelSettings(FlatBufferBuilder fbb, SkeletonConfig config) {
|
||||
int togglesOffset = ModelToggles
|
||||
.createModelToggles(
|
||||
fbb,
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_PELVIS_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL),
|
||||
config.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD),
|
||||
config.getToggle(SkeletonConfigToggles.SKATING_CORRECTION),
|
||||
config.getToggle(SkeletonConfigToggles.FLOOR_CLIP)
|
||||
);
|
||||
int ratiosOffset = ModelRatios
|
||||
.createModelRatios(
|
||||
fbb,
|
||||
config.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.HIP_LEGS_AVERAGING),
|
||||
config.getValue(SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING)
|
||||
);
|
||||
return ModelSettings.createModelSettings(fbb, togglesOffset, ratiosOffset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package dev.slimevr.protocol.rpc.settings;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.config.FiltersConfig;
|
||||
import dev.slimevr.config.OSCConfig;
|
||||
import dev.slimevr.filtering.TrackerFilters;
|
||||
import dev.slimevr.osc.VRCOSCHandler;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.rpc.RPCHandler;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigToggles;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValues;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import solarxr_protocol.rpc.*;
|
||||
|
||||
|
||||
public record RPCSettingsHandler(RPCHandler rpcHandler, ProtocolAPI api) {
|
||||
|
||||
|
||||
public RPCSettingsHandler(RPCHandler rpcHandler, ProtocolAPI api) {
|
||||
this.rpcHandler = rpcHandler;
|
||||
this.api = api;
|
||||
|
||||
rpcHandler.registerPacketListener(RpcMessage.SettingsRequest, this::onSettingsRequest);
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.ChangeSettingsRequest,
|
||||
this::onChangeSettingsRequest
|
||||
);
|
||||
}
|
||||
|
||||
public void onSettingsRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
WindowsNamedPipeBridge bridge = this.api.server.getVRBridge(WindowsNamedPipeBridge.class);
|
||||
|
||||
int settings = SettingsResponse
|
||||
.createSettingsResponse(
|
||||
fbb,
|
||||
RPCSettingsBuilder.createSteamVRSettings(fbb, bridge),
|
||||
RPCSettingsBuilder
|
||||
.createFilterSettings(
|
||||
fbb,
|
||||
this.api.server.getConfigManager().getVrConfig().getFilters()
|
||||
),
|
||||
RPCSettingsBuilder
|
||||
.createOSCSettings(
|
||||
fbb,
|
||||
this.api.server.getConfigManager().getVrConfig().getVrcOSC()
|
||||
),
|
||||
RPCSettingsBuilder
|
||||
.createModelSettings(
|
||||
fbb,
|
||||
this.api.server.humanPoseProcessor.getSkeletonConfig()
|
||||
)
|
||||
);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SettingsResponse, settings);
|
||||
fbb.finish(outbound);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public void onChangeSettingsRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
|
||||
ChangeSettingsRequest req = (ChangeSettingsRequest) messageHeader
|
||||
.message(new ChangeSettingsRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
if (req.steamVrTrackers() != null) {
|
||||
WindowsNamedPipeBridge bridge = this.api.server
|
||||
.getVRBridge(WindowsNamedPipeBridge.class);
|
||||
|
||||
if (bridge != null) {
|
||||
bridge.changeShareSettings(TrackerRole.WAIST, req.steamVrTrackers().waist());
|
||||
bridge.changeShareSettings(TrackerRole.CHEST, req.steamVrTrackers().chest());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_FOOT, req.steamVrTrackers().feet());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_FOOT, req.steamVrTrackers().feet());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_KNEE, req.steamVrTrackers().knees());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_KNEE, req.steamVrTrackers().knees());
|
||||
bridge.changeShareSettings(TrackerRole.LEFT_ELBOW, req.steamVrTrackers().elbows());
|
||||
bridge.changeShareSettings(TrackerRole.RIGHT_ELBOW, req.steamVrTrackers().elbows());
|
||||
}
|
||||
}
|
||||
|
||||
if (req.filtering() != null) {
|
||||
TrackerFilters type = TrackerFilters.fromId(req.filtering().type());
|
||||
if (type != null) {
|
||||
FiltersConfig filtersConfig = this.api.server
|
||||
.getConfigManager()
|
||||
.getVrConfig()
|
||||
.getFilters();
|
||||
filtersConfig.setType(type.configKey);
|
||||
filtersConfig.setAmount(req.filtering().amount());
|
||||
filtersConfig.updateTrackersFilters();
|
||||
}
|
||||
}
|
||||
|
||||
if (req.vrcOsc() != null) {
|
||||
OSCConfig vrcOSCConfig = this.api.server.getConfigManager().getVrConfig().getVrcOSC();
|
||||
if (vrcOSCConfig != null) {
|
||||
VRCOSCHandler VRCOSCHandler = this.api.server.getVRCOSCHandler();
|
||||
var trackers = req.vrcOsc().trackers();
|
||||
|
||||
vrcOSCConfig.setEnabled(req.vrcOsc().enabled());
|
||||
vrcOSCConfig.setPortIn(req.vrcOsc().portIn());
|
||||
vrcOSCConfig.setPortOut(req.vrcOsc().portOut());
|
||||
vrcOSCConfig.setAddress(req.vrcOsc().address());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.HEAD, trackers.head());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.CHEST, trackers.chest());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.WAIST, trackers.waist());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_KNEE, trackers.knees());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_KNEE, trackers.knees());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_FOOT, trackers.feet());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_FOOT, trackers.feet());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_ELBOW, trackers.elbows());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_ELBOW, trackers.elbows());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.LEFT_HAND, trackers.hands());
|
||||
vrcOSCConfig.setOSCTrackerRole(TrackerRole.RIGHT_HAND, trackers.hands());
|
||||
|
||||
|
||||
VRCOSCHandler.refreshSettings();
|
||||
}
|
||||
}
|
||||
|
||||
var modelSettings = req.modelSettings();
|
||||
if (modelSettings != null) {
|
||||
var cfg = this.api.server.humanPoseProcessor.getSkeletonConfig();
|
||||
var toggles = modelSettings.toggles();
|
||||
var ratios = modelSettings.ratios();
|
||||
|
||||
if (toggles != null) {
|
||||
// Note: toggles.has____ returns the same as toggles._____ this
|
||||
// seems like a bug
|
||||
cfg.setToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL, toggles.extendedSpine());
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
|
||||
toggles.extendedPelvis()
|
||||
);
|
||||
cfg.setToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL, toggles.extendedKnee());
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
|
||||
toggles.forceArmsFromHmd()
|
||||
);
|
||||
cfg.setToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL, toggles.extendedSpine());
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
|
||||
toggles.extendedPelvis()
|
||||
);
|
||||
cfg.setToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL, toggles.extendedKnee());
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
|
||||
toggles.forceArmsFromHmd()
|
||||
);
|
||||
cfg.setToggle(SkeletonConfigToggles.FLOOR_CLIP, toggles.floorClip());
|
||||
|
||||
cfg
|
||||
.setToggle(
|
||||
SkeletonConfigToggles.SKATING_CORRECTION,
|
||||
toggles.skatingCorrection()
|
||||
);
|
||||
}
|
||||
|
||||
if (ratios != null) {
|
||||
if (ratios.hasImputeWaistFromChestHip()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING,
|
||||
ratios.imputeWaistFromChestHip()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeWaistFromChestLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING,
|
||||
ratios.imputeWaistFromChestLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeHipFromChestLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING,
|
||||
ratios.imputeHipFromChestLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasImputeHipFromWaistLegs()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING,
|
||||
ratios.imputeHipFromWaistLegs()
|
||||
);
|
||||
}
|
||||
if (ratios.hasInterpHipLegs()) {
|
||||
cfg.setValue(SkeletonConfigValues.HIP_LEGS_AVERAGING, ratios.interpHipLegs());
|
||||
}
|
||||
if (ratios.hasInterpKneeTrackerAnkle()) {
|
||||
cfg
|
||||
.setValue(
|
||||
SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING,
|
||||
ratios.interpKneeTrackerAnkle()
|
||||
);
|
||||
}
|
||||
}
|
||||
cfg.save();
|
||||
}
|
||||
|
||||
this.api.server.getConfigManager().saveConfig();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,10 +9,10 @@ import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public class SerialHandler implements SerialPortMessageListener {
|
||||
@@ -28,20 +28,22 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
listeners.removeIf(listener -> l == listener);
|
||||
}
|
||||
|
||||
public boolean openSerial() {
|
||||
public boolean openSerial(String portLocation, boolean auto) {
|
||||
if (this.isConnected()) {
|
||||
return true;
|
||||
if (trackerPort != null)
|
||||
trackerPort.closePort();
|
||||
}
|
||||
|
||||
System.out.println("Trying to open:" + portLocation + " auto: " + auto);
|
||||
|
||||
SerialPort[] ports = SerialPort.getCommPorts();
|
||||
for (SerialPort port : ports) {
|
||||
if (
|
||||
port.getDescriptivePortName().toLowerCase().contains("ch340")
|
||||
|| port.getDescriptivePortName().toLowerCase().contains("cp21")
|
||||
|| port.getDescriptivePortName().toLowerCase().contains("ch910")
|
||||
|| (port.getDescriptivePortName().toLowerCase().contains("usb")
|
||||
&& port.getDescriptivePortName().toLowerCase().contains("seri"))
|
||||
) {
|
||||
if (!auto && port.getPortLocation().equals(portLocation)) {
|
||||
trackerPort = port;
|
||||
break;
|
||||
}
|
||||
|
||||
if (auto && isKnownBoard(port.getDescriptivePortName())) {
|
||||
trackerPort = port;
|
||||
break;
|
||||
}
|
||||
@@ -49,6 +51,7 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
if (trackerPort == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trackerPort.setBaudRate(115200);
|
||||
trackerPort.clearRTS();
|
||||
trackerPort.clearDTR();
|
||||
@@ -91,7 +94,7 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
OutputStream os = trackerPort.getOutputStream();
|
||||
OutputStreamWriter writer = new OutputStreamWriter(os);
|
||||
try {
|
||||
writer.append(serialText + "\n");
|
||||
writer.append(serialText).append("\n");
|
||||
writer.flush();
|
||||
this.addLog("-> " + serialText + "\n");
|
||||
} catch (IOException e) {
|
||||
@@ -149,4 +152,21 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
public boolean delimiterIndicatesEndOfMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Stream<SerialPort> getKnownPorts() {
|
||||
return Arrays
|
||||
.stream(SerialPort.getCommPorts())
|
||||
.filter((port) -> isKnownBoard(port.getDescriptivePortName()));
|
||||
|
||||
}
|
||||
|
||||
private boolean isKnownBoard(String com) {
|
||||
String lowerCom = com.toLowerCase();
|
||||
|
||||
return lowerCom.contains("ch340")
|
||||
|| lowerCom.contains("cp21")
|
||||
|| lowerCom.contains("ch910")
|
||||
|| (lowerCom.contains("usb")
|
||||
&& lowerCom.contains("seri"));
|
||||
}
|
||||
}
|
||||
|
||||
Submodule solarxr-protocol updated: db25fe3dd9...cc6be79999
Reference in New Issue
Block a user