Serial Selection and detection (#312)

This commit is contained in:
lucas lelievre
2022-11-27 19:13:46 +01:00
committed by GitHub
parent c3e2987e82
commit 9b624f5d9a
36 changed files with 1642 additions and 1027 deletions

View File

@@ -33,7 +33,7 @@
"typescript": "^4.6.3"
},
"scripts": {
"start": "vite",
"start": "vite --force",
"build": "vite build",
"dev": "tauri dev",
"tauri": "tauri",

View File

@@ -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="/"

View File

@@ -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({

View 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>
);
}

View File

@@ -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],

View 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>
);
}

View File

@@ -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':

View 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>
)}
/>
);
}

View File

@@ -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}

View File

@@ -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}

View 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>
);
}

View File

@@ -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>

View File

@@ -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';

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View 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.
}

View 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"
/>
</>
),
};
}

View File

@@ -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'],

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -1,4 +1,4 @@
package dev.slimevr.protocol;
package dev.slimevr.protocol.datafeed;
import com.google.flatbuffers.FlatBufferBuilder;
import com.jme3.math.Quaternion;

View File

@@ -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());
}
}
}));

View File

@@ -1,4 +1,4 @@
package dev.slimevr.protocol;
package dev.slimevr.protocol.pubsub;
import solarxr_protocol.pub_sub.TopicIdT;

View File

@@ -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.*;

View File

@@ -1,4 +1,4 @@
package dev.slimevr.protocol;
package dev.slimevr.protocol.rpc;
import com.google.flatbuffers.FlatBufferBuilder;
import dev.slimevr.vr.processor.HumanPoseProcessor;

View 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
}
}

View File

@@ -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)
);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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"));
}
}