mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Moving wifi provisioning and new device detection to the server (#542)
This commit is contained in:
@@ -451,11 +451,14 @@ onboarding-connect_tracker-description-p0 = Now onto the fun part, connecting al
|
||||
onboarding-connect_tracker-description-p1 = Simply connect all that aren't connected yet, through a USB port.
|
||||
onboarding-connect_tracker-issue-serial = I'm having trouble connecting!
|
||||
onboarding-connect_tracker-usb = USB Tracker
|
||||
onboarding-connect_tracker-connection_status-connecting = Sending Wi-Fi credentials
|
||||
onboarding-connect_tracker-connection_status-connected = Connected to Wi-Fi
|
||||
onboarding-connect_tracker-connection_status-error = Unable to connect to Wi-Fi
|
||||
onboarding-connect_tracker-connection_status-start_connecting = Looking for trackers
|
||||
onboarding-connect_tracker-connection_status-handshake = Connected to the Server
|
||||
onboarding-connect_tracker-connection_status-none = Looking for trackers
|
||||
onboarding-connect_tracker-connection_status-serial_init = Connecting to serial device
|
||||
onboarding-connect_tracker-connection_status-provisioning = Sending Wi-Fi credentials
|
||||
onboarding-connect_tracker-connection_status-connecting = Trying to connect to Wi-Fi
|
||||
onboarding-connect_tracker-connection_status-looking_for_server = Looking for server
|
||||
onboarding-connect_tracker-connection_status-connection_error = Unable to connect to Wi-Fi
|
||||
onboarding-connect_tracker-connection_status-could_not_find_server = Could not find the server
|
||||
onboarding-connect_tracker-connection_status-done = Connected to the Server
|
||||
# $amount (Number) - Amount of trackers connected (this is a number, but you can use CLDR plural rules for your language)
|
||||
# More info on https://www.unicode.org/cldr/cldr-aux/charts/22/supplemental/language_plural_rules.html
|
||||
# English in this case only has 2 plural rules, which are "one" and "other",
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
NewSerialDeviceResponseT,
|
||||
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';
|
||||
@@ -18,96 +16,18 @@ import { USBIcon } from './commons/icon/UsbIcon';
|
||||
import { Input } from './commons/Input';
|
||||
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 { l10n } = useLocalization();
|
||||
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 { useRPCPacket } = useWebsocketAPI();
|
||||
const [isOpen, setOpen] = useState<SerialDeviceT | null>(null);
|
||||
const [showWifiForm, setShowWifiForm] = useState(false);
|
||||
|
||||
const { handleSubmit, submitWifiCreds, formState, hasWifiCreds, control } =
|
||||
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 closeModal = () => {
|
||||
setOpen(null);
|
||||
setShowWifiForm(false);
|
||||
@@ -134,9 +54,14 @@ export function SerialDetectionModal() {
|
||||
};
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.SerialDevicesResponse,
|
||||
(val: SerialDevicesResponseT) => {
|
||||
setCurrentDevices(val.devices);
|
||||
RpcMessage.NewSerialDeviceResponse,
|
||||
({ device }: NewSerialDeviceResponseT) => {
|
||||
if (
|
||||
config?.watchNewDevices &&
|
||||
!['/settings/serial', '/onboarding/connect-trackers'].includes(pathname)
|
||||
) {
|
||||
setOpen(device);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export const Input = ({
|
||||
variant = 'primary',
|
||||
rules,
|
||||
}: {
|
||||
rules: UseControllerProps<any>['rules'];
|
||||
rules?: UseControllerProps<any>['rules'];
|
||||
control: Control<any>;
|
||||
} & InputProps &
|
||||
Partial<HTMLInputElement>) => {
|
||||
|
||||
@@ -3,11 +3,11 @@ import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
CloseSerialRequestT,
|
||||
OpenSerialRequestT,
|
||||
RpcMessage,
|
||||
SerialUpdateResponseT,
|
||||
SetWifiRequestT,
|
||||
StartWifiProvisioningRequestT,
|
||||
StopWifiProvisioningRquestT,
|
||||
WifiProvisioningStatus,
|
||||
WifiProvisioningStatusResponseT,
|
||||
} from 'solarxr-protocol';
|
||||
import { useLayout } from '../../../hooks/layout';
|
||||
import { useOnboarding } from '../../../hooks/onboarding';
|
||||
@@ -21,20 +21,23 @@ import { Typography } from '../../commons/Typography';
|
||||
import { TrackerCard } from '../../tracker/TrackerCard';
|
||||
|
||||
const BOTTOM_HEIGHT = 80;
|
||||
type ConnectionStatus =
|
||||
| 'CONNECTING'
|
||||
| 'CONNECTED'
|
||||
| 'HANDSHAKE'
|
||||
| 'ERROR'
|
||||
| 'START-CONNECTING';
|
||||
|
||||
const statusLabelMap = {
|
||||
['CONNECTING']: 'onboarding-connect_tracker-connection_status-connecting',
|
||||
['CONNECTED']: 'onboarding-connect_tracker-connection_status-connected',
|
||||
['ERROR']: 'onboarding-connect_tracker-connection_status-error',
|
||||
['START-CONNECTING']:
|
||||
'onboarding-connect_tracker-connection_status-start_connecting',
|
||||
['HANDSHAKE']: 'onboarding-connect_tracker-connection_status-handshake',
|
||||
[WifiProvisioningStatus.NONE]: 'onboarding-connect_tracker-connection_status-none',
|
||||
[WifiProvisioningStatus.SERIAL_INIT]:
|
||||
'onboarding-connect_tracker-connection_status-serial_init',
|
||||
[WifiProvisioningStatus.PROVISIONING]:
|
||||
'onboarding-connect_tracker-connection_status-provisioning',
|
||||
[WifiProvisioningStatus.CONNECTING]:
|
||||
'onboarding-connect_tracker-connection_status-connecting',
|
||||
[WifiProvisioningStatus.LOOKING_FOR_SERVER]:
|
||||
'onboarding-connect_tracker-connection_status-looking_for_server',
|
||||
[WifiProvisioningStatus.DONE]:
|
||||
'onboarding-connect_tracker-connection_status-done',
|
||||
[WifiProvisioningStatus.CONNECTION_ERROR]:
|
||||
'onboarding-connect_tracker-connection_status-connection_error',
|
||||
[WifiProvisioningStatus.COULD_NOT_FIND_SERVER]:
|
||||
'onboarding-connect_tracker-connection_status-could_not_find_server',
|
||||
};
|
||||
|
||||
export function ConnectTrackersPage() {
|
||||
@@ -44,90 +47,41 @@ export function ConnectTrackersPage() {
|
||||
const { applyProgress, state, skipSetup } = useOnboarding();
|
||||
const navigate = useNavigate();
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const [isSerialOpen, setSerialOpen] = useState(false);
|
||||
const [connectionStatus, setConnectionStatus] =
|
||||
useState<ConnectionStatus>('START-CONNECTING');
|
||||
const [provisioningStatus, setProvisioningStatus] =
|
||||
useState<WifiProvisioningStatus>(WifiProvisioningStatus.NONE);
|
||||
|
||||
applyProgress(0.4);
|
||||
|
||||
const connectedTrackers = useConnectedTrackers();
|
||||
|
||||
const openSerial = () => {
|
||||
const req = new OpenSerialRequestT();
|
||||
req.auto = true;
|
||||
|
||||
sendRPCPacket(RpcMessage.OpenSerialRequest, req);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.wifi) {
|
||||
navigate('/onboarding/wifi-creds');
|
||||
}
|
||||
|
||||
openSerial();
|
||||
const req = new StartWifiProvisioningRequestT();
|
||||
req.ssid = state.wifi?.ssid as string;
|
||||
req.password = state.wifi?.password as string;
|
||||
|
||||
sendRPCPacket(RpcMessage.StartWifiProvisioningRequest, req);
|
||||
return () => {
|
||||
sendRPCPacket(RpcMessage.CloseSerialRequest, new CloseSerialRequestT());
|
||||
sendRPCPacket(
|
||||
RpcMessage.StopWifiProvisioningRquest,
|
||||
new StopWifiProvisioningRquestT()
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.SerialUpdateResponse,
|
||||
(data: SerialUpdateResponseT) => {
|
||||
if (data.closed) {
|
||||
setSerialOpen(false);
|
||||
setConnectionStatus('START-CONNECTING');
|
||||
setTimeout(() => {
|
||||
openSerial();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (!data.closed && !isSerialOpen) {
|
||||
setSerialOpen(true);
|
||||
setConnectionStatus('START-CONNECTING');
|
||||
}
|
||||
|
||||
if (data.log) {
|
||||
const log = data.log as string;
|
||||
if (connectionStatus === 'START-CONNECTING' && state.wifi) {
|
||||
setConnectionStatus('CONNECTING');
|
||||
if (!state.wifi) return;
|
||||
const wifi = new SetWifiRequestT();
|
||||
wifi.ssid = state.wifi.ssid;
|
||||
wifi.password = state.wifi.password;
|
||||
sendRPCPacket(RpcMessage.SetWifiRequest, wifi);
|
||||
}
|
||||
|
||||
if (log.includes('Connected successfully to SSID')) {
|
||||
setConnectionStatus('CONNECTED');
|
||||
}
|
||||
|
||||
if (log.includes('Handshake successful')) {
|
||||
setConnectionStatus('HANDSHAKE');
|
||||
setTimeout(() => {
|
||||
setConnectionStatus('START-CONNECTING');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
if (
|
||||
// eslint-disable-next-line quotes
|
||||
log.includes("Can't connect from any credentials")
|
||||
) {
|
||||
setConnectionStatus('ERROR');
|
||||
}
|
||||
}
|
||||
RpcMessage.WifiProvisioningStatusResponse,
|
||||
({ status }: WifiProvisioningStatusResponseT) => {
|
||||
setProvisioningStatus(status);
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
if (!isSerialOpen) openSerial();
|
||||
else clearInterval(id);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [isSerialOpen, sendRPCPacket]);
|
||||
const isError =
|
||||
provisioningStatus === WifiProvisioningStatus.CONNECTION_ERROR ||
|
||||
provisioningStatus === WifiProvisioningStatus.COULD_NOT_FIND_SERVER;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
@@ -170,12 +124,12 @@ export function ConnectTrackersPage() {
|
||||
className={classNames(
|
||||
'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'
|
||||
isError && 'border-2 border-status-critical'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col justify-center fill-background-10">
|
||||
<LoaderIcon
|
||||
youSpinMeRightRoundBabyRightRound={connectionStatus !== 'ERROR'}
|
||||
youSpinMeRightRoundBabyRightRound={!isError}
|
||||
></LoaderIcon>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
@@ -183,7 +137,7 @@ export function ConnectTrackersPage() {
|
||||
{l10n.getString('onboarding-connect_tracker-usb')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(statusLabelMap[connectionStatus])}
|
||||
{l10n.getString(statusLabelMap[provisioningStatus])}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export function AutomaticMountingPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5 h-full items-center w-full justify-center">
|
||||
<div className="flex flex-col gap-2 h-full items-center w-full justify-center">
|
||||
<div className="flex flex-col w-full h-full justify-center max-w-3xl gap-5">
|
||||
<div className="flex flex-col max-w-lg gap-3">
|
||||
{!state.alonePage && (
|
||||
|
||||
@@ -62,6 +62,8 @@ dependencies {
|
||||
implementation("com.github.jonpeterson:jackson-module-model-versioning:1.2.2")
|
||||
implementation("org.apache.commons:commons-math3:3.6.1")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
implementation("org.apache.commons:commons-collections4:4.1")
|
||||
|
||||
implementation("net.java.dev.jna:jna:5.+")
|
||||
implementation("net.java.dev.jna:jna-platform:5.+")
|
||||
implementation("com.illposed.osc:javaosc-core:0.8")
|
||||
|
||||
@@ -13,6 +13,7 @@ import dev.slimevr.platform.linux.UnixSocketBridge;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.poserecorder.BVHRecorder;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.serial.ProvisioningHandler;
|
||||
import dev.slimevr.serial.SerialHandler;
|
||||
import dev.slimevr.tracking.DeviceManager;
|
||||
import dev.slimevr.tracking.processor.HumanPoseManager;
|
||||
@@ -63,6 +64,7 @@ public class VRServer extends Thread {
|
||||
private final ConfigManager configManager;
|
||||
private final Timer timer = new Timer();
|
||||
private final NanoTimer fpsTimer = new NanoTimer();
|
||||
private final ProvisioningHandler provisioningHandler;
|
||||
|
||||
/**
|
||||
* This function is used by VRWorkout, do not remove!
|
||||
@@ -80,6 +82,9 @@ public class VRServer extends Thread {
|
||||
deviceManager = new DeviceManager(this);
|
||||
|
||||
serialHandler = new SerialHandler();
|
||||
|
||||
provisioningHandler = new ProvisioningHandler(this);
|
||||
|
||||
autoBoneHandler = new AutoBoneHandler(this);
|
||||
protocolAPI = new ProtocolAPI(this);
|
||||
|
||||
@@ -432,6 +437,10 @@ public class VRServer extends Thread {
|
||||
return fpsTimer;
|
||||
}
|
||||
|
||||
public ProvisioningHandler getProvisioningHandler() {
|
||||
return provisioningHandler;
|
||||
}
|
||||
|
||||
public void clearTrackersDriftCompensation() {
|
||||
for (Tracker t : getAllTrackers()) {
|
||||
Tracker tracker = t.get();
|
||||
|
||||
@@ -19,6 +19,8 @@ public class ConnectionContext {
|
||||
private final List<Integer> subscribedTopics = new ArrayList<>();
|
||||
|
||||
private boolean useSerial = false;
|
||||
|
||||
private boolean useProvisioning = false;
|
||||
private boolean useAutoBone = false;
|
||||
|
||||
public List<DataFeedConfigT> getDataFeedConfigList() {
|
||||
@@ -54,4 +56,11 @@ public class ConnectionContext {
|
||||
this.useAutoBone = useAutoBone;
|
||||
}
|
||||
|
||||
public boolean useProvisioning() {
|
||||
return useProvisioning;
|
||||
}
|
||||
|
||||
public void setUseProvisioning(boolean useProvisioning) {
|
||||
this.useProvisioning = useProvisioning;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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.RPCProvisioningHandler;
|
||||
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler;
|
||||
import dev.slimevr.protocol.rpc.settings.RPCSettingsHandler;
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets;
|
||||
@@ -40,6 +41,7 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
this.api = api;
|
||||
|
||||
new RPCSerialHandler(this, api);
|
||||
new RPCProvisioningHandler(this, api);
|
||||
new RPCSettingsHandler(this, api);
|
||||
|
||||
registerPacketListener(RpcMessage.ResetRequest, this::onResetRequest);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package dev.slimevr.protocol.rpc.serial;
|
||||
|
||||
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.ProvisioningListener;
|
||||
import dev.slimevr.serial.ProvisioningStatus;
|
||||
import solarxr_protocol.rpc.*;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
public record RPCProvisioningHandler(RPCHandler rpcHandler, ProtocolAPI api)
|
||||
implements ProvisioningListener {
|
||||
|
||||
public RPCProvisioningHandler(RPCHandler rpcHandler, ProtocolAPI api) {
|
||||
this.rpcHandler = rpcHandler;
|
||||
this.api = api;
|
||||
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.StartWifiProvisioningRequest,
|
||||
this::onStartWifiProvisioningRequest
|
||||
);
|
||||
rpcHandler
|
||||
.registerPacketListener(
|
||||
RpcMessage.StopWifiProvisioningRequest,
|
||||
this::onStopWifiProvisioningRequest
|
||||
);
|
||||
|
||||
this.api.server.getProvisioningHandler().addListener(this);
|
||||
}
|
||||
|
||||
public void onStartWifiProvisioningRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
StartWifiProvisioningRequest req = (StartWifiProvisioningRequest) messageHeader
|
||||
.message(new StartWifiProvisioningRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
this.api.server.getProvisioningHandler().start(req.ssid(), req.password(), req.port());
|
||||
conn.getContext().setUseProvisioning(true);
|
||||
}
|
||||
|
||||
public void onStopWifiProvisioningRequest(
|
||||
GenericConnection conn,
|
||||
RpcMessageHeader messageHeader
|
||||
) {
|
||||
StopWifiProvisioningRequest req = (StopWifiProvisioningRequest) messageHeader
|
||||
.message(new StopWifiProvisioningRequest());
|
||||
if (req == null)
|
||||
return;
|
||||
conn.getContext().setUseProvisioning(false);
|
||||
this.api.server.getProvisioningHandler().stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvisioningStatusChange(ProvisioningStatus status) {
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
WifiProvisioningStatusResponse.startWifiProvisioningStatusResponse(fbb);
|
||||
WifiProvisioningStatusResponse.addStatus(fbb, status.getId());
|
||||
int update = WifiProvisioningStatusResponse.endWifiProvisioningStatusResponse(fbb);
|
||||
int outbound = rpcHandler
|
||||
.createRPCMessage(fbb, RpcMessage.WifiProvisioningStatusResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
this.forAllListeners((conn) -> conn.send(fbb.dataBuffer()));
|
||||
}
|
||||
|
||||
private void forAllListeners(Consumer<? super GenericConnection> action) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useProvisioning())
|
||||
.forEach(action)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,31 @@ public record RPCSerialHandler(RPCHandler rpcHandler, ProtocolAPI api) implement
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSerialDevice(SerialPort port) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int portOffset = fbb.createString(port.getPortLocation());
|
||||
int nameOffset = fbb.createString(port.getDescriptivePortName());
|
||||
int deviceOffset = SerialDevice.createSerialDevice(fbb, portOffset, nameOffset);
|
||||
int newSerialOffset = NewSerialDeviceResponse
|
||||
.createNewSerialDeviceResponse(fbb, deviceOffset);
|
||||
int outbound = rpcHandler
|
||||
.createRPCMessage(fbb, RpcMessage.NewSerialDeviceResponse, newSerialOffset);
|
||||
fbb.finish(outbound);
|
||||
|
||||
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.forEach((conn) -> {
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialConnected(SerialPort port) {
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@ public class RPCSettingsBuilder {
|
||||
tapDetectionConfig.getQuickResetTaps(),
|
||||
tapDetectionConfig.getMountingResetDelay(),
|
||||
tapDetectionConfig.getMountingResetEnabled(),
|
||||
tapDetectionConfig.getMountingResetTaps()
|
||||
tapDetectionConfig.getMountingResetTaps(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
172
server/src/main/java/dev/slimevr/serial/ProvisioningHandler.java
Normal file
172
server/src/main/java/dev/slimevr/serial/ProvisioningHandler.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package dev.slimevr.serial;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import dev.slimevr.VRServer;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
|
||||
public class ProvisioningHandler implements SerialListener {
|
||||
|
||||
private ProvisioningStatus provisioningStatus = ProvisioningStatus.NONE;
|
||||
|
||||
private boolean isRunning = false;
|
||||
private final List<ProvisioningListener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private String ssid;
|
||||
private String password;
|
||||
|
||||
private String preferredPort;
|
||||
|
||||
private final Timer provisioningTickTimer = new Timer("ProvisioningTickTimer");
|
||||
private long lastStatusChange = -1;
|
||||
private final VRServer vrServer;
|
||||
|
||||
public ProvisioningHandler(VRServer vrServer) {
|
||||
this.vrServer = vrServer;
|
||||
vrServer.getSerialHandler().addListener(this);
|
||||
this.provisioningTickTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning)
|
||||
return;
|
||||
provisioningTick();
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
|
||||
public void start(String ssid, String password, String port) {
|
||||
this.isRunning = true;
|
||||
this.ssid = ssid;
|
||||
this.password = password;
|
||||
this.preferredPort = port;
|
||||
this.provisioningStatus = ProvisioningStatus.NONE;
|
||||
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.isRunning = false;
|
||||
this.ssid = null;
|
||||
this.password = null;
|
||||
this.changeStatus(ProvisioningStatus.NONE);
|
||||
this.vrServer.getSerialHandler().closeSerial();
|
||||
}
|
||||
|
||||
public void initSerial(String port) {
|
||||
this.provisioningStatus = ProvisioningStatus.SERIAL_INIT;
|
||||
|
||||
try {
|
||||
if (port != null)
|
||||
vrServer.getSerialHandler().openSerial(port, false);
|
||||
else
|
||||
vrServer.getSerialHandler().openSerial(null, true);
|
||||
} 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void tryProvisioning() {
|
||||
this.changeStatus(ProvisioningStatus.PROVISIONING);
|
||||
vrServer.getSerialHandler().setWifi(this.ssid, this.password);
|
||||
}
|
||||
|
||||
|
||||
public void provisioningTick() {
|
||||
|
||||
if (
|
||||
this.provisioningStatus == ProvisioningStatus.CONNECTION_ERROR
|
||||
|| this.provisioningStatus == ProvisioningStatus.DONE
|
||||
)
|
||||
return;
|
||||
|
||||
|
||||
if (System.currentTimeMillis() - this.lastStatusChange > 10000) {
|
||||
if (this.provisioningStatus == ProvisioningStatus.NONE)
|
||||
this.initSerial(this.preferredPort);
|
||||
else if (this.provisioningStatus == ProvisioningStatus.PROVISIONING)
|
||||
this.tryProvisioning();
|
||||
else if (this.provisioningStatus == ProvisioningStatus.LOOKING_FOR_SERVER)
|
||||
this.changeStatus(ProvisioningStatus.COULD_NOT_FIND_SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSerialConnected(SerialPort port) {
|
||||
if (!isRunning)
|
||||
return;
|
||||
this.tryProvisioning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialDisconnected() {
|
||||
if (!isRunning)
|
||||
return;
|
||||
this.changeStatus(ProvisioningStatus.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSerialLog(String str) {
|
||||
if (!isRunning)
|
||||
return;
|
||||
|
||||
if (
|
||||
provisioningStatus == ProvisioningStatus.PROVISIONING
|
||||
&& str.contains("New wifi credentials set")
|
||||
) {
|
||||
this.changeStatus(ProvisioningStatus.CONNECTING);
|
||||
}
|
||||
|
||||
if (
|
||||
provisioningStatus == ProvisioningStatus.CONNECTING
|
||||
&& str.contains("Looking for the server")
|
||||
) {
|
||||
this.changeStatus(ProvisioningStatus.LOOKING_FOR_SERVER);
|
||||
}
|
||||
|
||||
if (
|
||||
provisioningStatus == ProvisioningStatus.LOOKING_FOR_SERVER
|
||||
&& str.contains("Handshake successful")
|
||||
) {
|
||||
this.changeStatus(ProvisioningStatus.DONE);
|
||||
}
|
||||
|
||||
if (
|
||||
provisioningStatus == ProvisioningStatus.CONNECTING
|
||||
&& str.contains("Can't connect from any credentials")
|
||||
) {
|
||||
this.changeStatus(ProvisioningStatus.CONNECTION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeStatus(ProvisioningStatus status) {
|
||||
this.lastStatusChange = System.currentTimeMillis();
|
||||
if (this.provisioningStatus != status) {
|
||||
this.listeners.forEach((l) -> l.onProvisioningStatusChange(status));
|
||||
this.provisioningStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewSerialDevice(SerialPort port) {
|
||||
this.initSerial(this.preferredPort);
|
||||
}
|
||||
|
||||
public void addListener(ProvisioningListener channel) {
|
||||
this.listeners.add(channel);
|
||||
}
|
||||
|
||||
public void removeListener(ProvisioningListener l) {
|
||||
listeners.removeIf(listener -> l == listener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package dev.slimevr.serial;
|
||||
|
||||
public interface ProvisioningListener {
|
||||
|
||||
void onProvisioningStatusChange(ProvisioningStatus status);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dev.slimevr.serial;
|
||||
|
||||
public enum ProvisioningStatus {
|
||||
|
||||
NONE(0),
|
||||
SERIAL_INIT(1),
|
||||
PROVISIONING(2),
|
||||
CONNECTING(3),
|
||||
CONNECTION_ERROR(4),
|
||||
LOOKING_FOR_SERVER(5),
|
||||
COULD_NOT_FIND_SERVER(6),
|
||||
DONE(7);
|
||||
|
||||
public final int id;
|
||||
|
||||
ProvisioningStatus(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,57 @@ package dev.slimevr.serial;
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import com.fazecast.jSerialComm.SerialPortEvent;
|
||||
import com.fazecast.jSerialComm.SerialPortMessageListener;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.Equator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public class SerialHandler implements SerialPortMessageListener {
|
||||
|
||||
private final List<SerialListener> listeners = new CopyOnWriteArrayList<>();
|
||||
private SerialPort trackerPort = null;
|
||||
private final Timer getDevicesTimer = new Timer("GetDevicesTimer");
|
||||
|
||||
private SerialPort currentPort = null;
|
||||
|
||||
private boolean watchingNewDevices = false;
|
||||
private SerialPort[] lastKnownPorts = new SerialPort[] {};
|
||||
|
||||
public SerialHandler() {
|
||||
startWatchingNewDevices();
|
||||
}
|
||||
|
||||
public void startWatchingNewDevices() {
|
||||
if (this.watchingNewDevices)
|
||||
return;
|
||||
this.watchingNewDevices = true;
|
||||
this.getDevicesTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
public void run() {
|
||||
detectNewPorts();
|
||||
}
|
||||
}, 0, 3000);
|
||||
}
|
||||
|
||||
public void stopWatchingNewDevices() {
|
||||
if (!this.watchingNewDevices)
|
||||
return;
|
||||
this.watchingNewDevices = false;
|
||||
this.getDevicesTimer.cancel();
|
||||
this.getDevicesTimer.purge();
|
||||
}
|
||||
|
||||
public void onNewDevice(SerialPort port) {
|
||||
this.listeners.forEach((listener) -> listener.onNewSerialDevice(port));
|
||||
}
|
||||
|
||||
|
||||
public void addListener(SerialListener channel) {
|
||||
this.listeners.add(channel);
|
||||
@@ -30,37 +65,38 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
|
||||
public boolean openSerial(String portLocation, boolean auto) {
|
||||
if (this.isConnected()) {
|
||||
if (trackerPort != null)
|
||||
trackerPort.closePort();
|
||||
if (currentPort != null)
|
||||
currentPort.closePort();
|
||||
}
|
||||
|
||||
System.out.println("Trying to open:" + portLocation + " auto: " + auto);
|
||||
|
||||
SerialPort[] ports = SerialPort.getCommPorts();
|
||||
lastKnownPorts = ports;
|
||||
for (SerialPort port : ports) {
|
||||
if (!auto && port.getPortLocation().equals(portLocation)) {
|
||||
trackerPort = port;
|
||||
currentPort = port;
|
||||
break;
|
||||
}
|
||||
|
||||
if (auto && isKnownBoard(port.getDescriptivePortName())) {
|
||||
trackerPort = port;
|
||||
currentPort = port;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trackerPort == null) {
|
||||
if (currentPort == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trackerPort.setBaudRate(115200);
|
||||
trackerPort.clearRTS();
|
||||
trackerPort.clearDTR();
|
||||
if (!trackerPort.openPort()) {
|
||||
currentPort.setBaudRate(115200);
|
||||
currentPort.clearRTS();
|
||||
currentPort.clearDTR();
|
||||
if (!currentPort.openPort()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trackerPort.addDataListener(this);
|
||||
this.listeners.forEach((listener) -> listener.onSerialConnected(trackerPort));
|
||||
currentPort.addDataListener(this);
|
||||
this.listeners.forEach((listener) -> listener.onSerialConnected(currentPort));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,20 +114,20 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
|
||||
public void closeSerial() {
|
||||
try {
|
||||
if (trackerPort != null)
|
||||
trackerPort.closePort();
|
||||
if (currentPort != null)
|
||||
currentPort.closePort();
|
||||
this.listeners.forEach(SerialListener::onSerialDisconnected);
|
||||
System.out.println("Port closed okay");
|
||||
trackerPort = null;
|
||||
currentPort = null;
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error closing port: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSerial(String serialText) {
|
||||
if (trackerPort == null)
|
||||
if (currentPort == null)
|
||||
return;
|
||||
OutputStream os = trackerPort.getOutputStream();
|
||||
OutputStream os = currentPort.getOutputStream();
|
||||
OutputStreamWriter writer = new OutputStreamWriter(os);
|
||||
try {
|
||||
writer.append(serialText).append("\n");
|
||||
@@ -104,9 +140,9 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
}
|
||||
|
||||
public void setWifi(String ssid, String passwd) {
|
||||
if (trackerPort == null)
|
||||
if (currentPort == null)
|
||||
return;
|
||||
OutputStream os = trackerPort.getOutputStream();
|
||||
OutputStream os = currentPort.getOutputStream();
|
||||
OutputStreamWriter writer = new OutputStreamWriter(os);
|
||||
try {
|
||||
writer.append("SET WIFI \"").append(ssid).append("\" \"").append(passwd).append("\"\n");
|
||||
@@ -140,7 +176,7 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.trackerPort != null && this.trackerPort.isOpen();
|
||||
return this.currentPort != null && this.currentPort.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,4 +205,34 @@ public class SerialHandler implements SerialPortMessageListener {
|
||||
|| (lowerCom.contains("usb")
|
||||
&& lowerCom.contains("seri"));
|
||||
}
|
||||
|
||||
private void detectNewPorts() {
|
||||
try {
|
||||
List<SerialPort> differences = new ArrayList<>(
|
||||
CollectionUtils
|
||||
.removeAll(
|
||||
this.getKnownPorts().toList(),
|
||||
Arrays.asList(lastKnownPorts),
|
||||
new Equator<>() {
|
||||
@Override
|
||||
public boolean equate(SerialPort o1, SerialPort o2) {
|
||||
return o1.getPortLocation().equals(o2.getPortLocation())
|
||||
&& o1
|
||||
.getDescriptivePortName()
|
||||
.equals(o1.getDescriptivePortName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hash(SerialPort o) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
lastKnownPorts = SerialPort.getCommPorts();
|
||||
differences.forEach(this::onNewDevice);
|
||||
} catch (Throwable e) {
|
||||
LogManager.severe("Using serial ports is not supported on this platform", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ public interface SerialListener {
|
||||
void onSerialDisconnected();
|
||||
|
||||
void onSerialLog(String str);
|
||||
|
||||
void onNewSerialDevice(SerialPort port);
|
||||
}
|
||||
|
||||
Submodule solarxr-protocol updated: ffeb6a6165...798dea45d4
Reference in New Issue
Block a user