From 281810dfbb68dc8fcdc80801449ac36d3b115836 Mon Sep 17 00:00:00 2001 From: lucas lelievre Date: Thu, 31 Mar 2022 16:00:13 +0200 Subject: [PATCH] begin websocket api --- build.gradle | 9 +- log_last.log.1 | 1 + log_last.log.lck | 0 log_main.log.1 | 1 + log_main.log.lck | 0 logs/log_2021-12-05.log.lck | 0 logs/log_2022-03-24.log.1 | 1 + logs/log_2022-03-24.log.lck | 0 logs/log_2022-03-30.log.lck | 0 logs/log_2022-03-31.log.lck | 0 settings.gradle | 4 + slimevr_protocol | 1 + src/main/java/dev/slimevr/Main.java | 5 +- src/main/java/dev/slimevr/VRServer.java | 7 +- .../dev/slimevr/bridge/WebSocketVRBridge.java | 1 + .../slimevr/websocketapi/APIApplication.java | 7 + .../slimevr/websocketapi/WebsocketAPI.java | 159 ++++++++++++++++++ 17 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 log_last.log.1 create mode 100644 log_last.log.lck create mode 100644 log_main.log.1 create mode 100644 log_main.log.lck create mode 100644 logs/log_2021-12-05.log.lck create mode 100644 logs/log_2022-03-24.log.1 create mode 100644 logs/log_2022-03-24.log.lck create mode 100644 logs/log_2022-03-30.log.lck create mode 100644 logs/log_2022-03-31.log.lck create mode 160000 slimevr_protocol create mode 100644 src/main/java/dev/slimevr/websocketapi/APIApplication.java create mode 100644 src/main/java/dev/slimevr/websocketapi/WebsocketAPI.java diff --git a/build.gradle b/build.gradle index 690ae7612..e75e4ec91 100644 --- a/build.gradle +++ b/build.gradle @@ -21,9 +21,9 @@ javadoc.options.encoding = 'UTF-8' tasks.withType(JavaCompile) { options.encoding = 'UTF-8' - if (JavaVersion.current().isJava9Compatible()) { - options.release = 8 - } +// if (JavaVersion.current().isJava9Compatible()) { +// options.release = 8 +// } } tasks.withType(Test) { systemProperty('file.encoding', 'UTF-8') @@ -42,6 +42,9 @@ allprojects { dependencies { implementation project(':slime-java-commons') + implementation project(":slimevr-protocol") + + implementation group: 'com.google.flatbuffers', name: 'flatbuffers-java', version: '2.0.3' // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'org.apache.commons:commons-math3:3.6.1' diff --git a/log_last.log.1 b/log_last.log.1 new file mode 100644 index 000000000..d3025baac --- /dev/null +++ b/log_last.log.1 @@ -0,0 +1 @@ +2022-03-24 21:58:25 [SEVERE] SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running. diff --git a/log_last.log.lck b/log_last.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/log_main.log.1 b/log_main.log.1 new file mode 100644 index 000000000..d3025baac --- /dev/null +++ b/log_main.log.1 @@ -0,0 +1 @@ +2022-03-24 21:58:25 [SEVERE] SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running. diff --git a/log_main.log.lck b/log_main.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/logs/log_2021-12-05.log.lck b/logs/log_2021-12-05.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/logs/log_2022-03-24.log.1 b/logs/log_2022-03-24.log.1 new file mode 100644 index 000000000..d3025baac --- /dev/null +++ b/logs/log_2022-03-24.log.1 @@ -0,0 +1 @@ +2022-03-24 21:58:25 [SEVERE] SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running. diff --git a/logs/log_2022-03-24.log.lck b/logs/log_2022-03-24.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/logs/log_2022-03-30.log.lck b/logs/log_2022-03-30.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/logs/log_2022-03-31.log.lck b/logs/log_2022-03-31.log.lck new file mode 100644 index 000000000..e69de29bb diff --git a/settings.gradle b/settings.gradle index e204e193f..53e6e77f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,3 +9,7 @@ rootProject.name = 'SlimeVR Server' include ':slime-java-commons' + + +include ':slimevr-protocol' +project(':slimevr-protocol').projectDir = new File('slimevr_protocol/protocol/java') diff --git a/slimevr_protocol b/slimevr_protocol new file mode 160000 index 000000000..502154485 --- /dev/null +++ b/slimevr_protocol @@ -0,0 +1 @@ +Subproject commit 5021544854346abf601dfce9b791847f4c57917f diff --git a/src/main/java/dev/slimevr/Main.java b/src/main/java/dev/slimevr/Main.java index a685d56f1..756b802d5 100644 --- a/src/main/java/dev/slimevr/Main.java +++ b/src/main/java/dev/slimevr/Main.java @@ -6,6 +6,7 @@ import java.net.ServerSocket; import javax.swing.JOptionPane; +import dev.slimevr.websocketapi.WebsocketAPI; import org.apache.commons.lang3.JavaVersion; import org.apache.commons.lang3.SystemUtils; @@ -41,6 +42,7 @@ public class Main { new ServerSocket(6969).close(); new ServerSocket(35903).close(); new ServerSocket(21110).close(); + new ServerSocket(21111).close(); } catch (IOException e) { LogManager.log.severe("SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running."); JOptionPane.showMessageDialog(null, "SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running.", "SlimeVR: Ports are busy", JOptionPane.ERROR_MESSAGE); @@ -51,7 +53,8 @@ public class Main { vrServer = new VRServer(); vrServer.start(); new Keybinding(vrServer); - new VRServerGUI(vrServer); + new WebsocketAPI(vrServer).start(); +// new VRServerGUI(vrServer); } catch(Throwable e) { e.printStackTrace(); try { diff --git a/src/main/java/dev/slimevr/VRServer.java b/src/main/java/dev/slimevr/VRServer.java index 60589bae9..537064bd7 100644 --- a/src/main/java/dev/slimevr/VRServer.java +++ b/src/main/java/dev/slimevr/VRServer.java @@ -17,7 +17,6 @@ import java.util.function.Consumer; import dev.slimevr.bridge.Bridge; import dev.slimevr.platform.windows.WindowsNamedPipeBridge; -import dev.slimevr.platform.windows.WindowsSteamVRPipeInputBridge; import dev.slimevr.bridge.VMCBridge; import dev.slimevr.bridge.WebSocketVRBridge; import dev.slimevr.util.ann.VRServerThread; @@ -75,7 +74,7 @@ public class VRServer extends Thread { WindowsNamedPipeBridge feederBridge = new WindowsNamedPipeBridge(null, "steamvr_feeder", "SteamVR Feeder Bridge", "\\\\.\\pipe\\SlimeVRInput", new FastList()); tasks.add(() -> feederBridge.startBridge()); bridges.add(feederBridge); - + } // Create WebSocket server @@ -215,7 +214,7 @@ public class VRServer extends Thread { public void run() { trackersServer.start(); while(true) { - //final long start = System.currentTimeMillis(); +// final long start = System.currentTimeMillis(); do { Runnable task = tasks.poll(); if(task == null) @@ -232,7 +231,7 @@ public class VRServer extends Thread { humanPoseProcessor.update(); for(int i = 0; i < bridges.size(); ++i) bridges.get(i).dataWrite(); - //final long time = System.currentTimeMillis() - start; +// final long time = System.currentTimeMillis() - start; try { Thread.sleep(1); // 1000Hz } catch(InterruptedException e) { diff --git a/src/main/java/dev/slimevr/bridge/WebSocketVRBridge.java b/src/main/java/dev/slimevr/bridge/WebSocketVRBridge.java index 4a83048f2..d6b5dd3dd 100644 --- a/src/main/java/dev/slimevr/bridge/WebSocketVRBridge.java +++ b/src/main/java/dev/slimevr/bridge/WebSocketVRBridge.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import dev.slimevr.bridge.Bridge; import org.java_websocket.WebSocket; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_6455; diff --git a/src/main/java/dev/slimevr/websocketapi/APIApplication.java b/src/main/java/dev/slimevr/websocketapi/APIApplication.java new file mode 100644 index 000000000..7c9dede75 --- /dev/null +++ b/src/main/java/dev/slimevr/websocketapi/APIApplication.java @@ -0,0 +1,7 @@ +package dev.slimevr.websocketapi; + +public class APIApplication { + + public int applicationType; + +} diff --git a/src/main/java/dev/slimevr/websocketapi/WebsocketAPI.java b/src/main/java/dev/slimevr/websocketapi/WebsocketAPI.java new file mode 100644 index 000000000..825abe5f0 --- /dev/null +++ b/src/main/java/dev/slimevr/websocketapi/WebsocketAPI.java @@ -0,0 +1,159 @@ +package dev.slimevr.websocketapi; + +import com.google.flatbuffers.FlatBufferBuilder; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import dev.slimevr.VRServer; +import dev.slimevr.vr.trackers.IMUTracker; +import dev.slimevr.vr.trackers.ReferenceAdjustedTracker; +import dev.slimevr.vr.trackers.Tracker; +import io.eiren.util.ann.ThreadSafe; +import io.eiren.util.logging.LogManager; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import slimevr_protocol.datatypes.Quat; +import slimevr_protocol.datatypes.Vec3f; +import slimevr_protocol.server.*; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; + + +public class WebsocketAPI extends WebSocketServer { + + public VRServer server; + + private final HashMap applications; + + private long lastMillis = 0; + + public WebsocketAPI(VRServer server) { + super(new InetSocketAddress(21111), Collections.singletonList(new Draft_6455())); + this.server = server; + this.applications = new HashMap<>(); + System.out.println("INIT"); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + LogManager.log.info("[WebSocketAPI] New connection from: " + conn.getRemoteSocketAddress().getAddress().getHostAddress()); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + LogManager.log.info("[WebSocketAPI] Disconnected: " + conn.getRemoteSocketAddress().getAddress().getHostAddress() + ", (" + code + ") " + reason + ". Remote: " + remote); + this.applications.remove(conn.hashCode()); + } + + @Override + public void onMessage(WebSocket conn, String message) {} + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + InboundPacket inboundPacket = InboundPacket.getRootAsInboundPacket(message); + + APIApplication application = this.applications.get(conn.hashCode()); + + try { + switch (inboundPacket.packetType()) { + case InboundUnion.HandshakeRequest: { + HandshakeRequest req = (HandshakeRequest) inboundPacket.packet(new HandshakeRequest()); + if (req != null) this.onHandshakeRequest(conn, req); + break; + } + default: + LogManager.log.warning("[WebSocketAPI] Received unknown packet type: " + inboundPacket.packetType()); + } + } catch (Exception e) { + LogManager.log.severe("[WebSocketAPI] Failed to parse packet: " + inboundPacket.packetType(), e); + } + } + + + public void onHandshakeRequest(WebSocket con, HandshakeRequest req) { + APIApplication app = new APIApplication(); + app.applicationType = req.applicationType(); + this.applications.put(con.hashCode(), app); + } + + @ThreadSafe + public void updateTrackersList() { + if (System.currentTimeMillis() - lastMillis <= 100) { + return; + } + FlatBufferBuilder fbb = new FlatBufferBuilder(300); + int[] trackersIds = new int[this.server.getTrackersCount()]; + + for (int index = 0; index < this.server.getTrackersCount(); index++) { + Tracker tracker = this.server.getAllTrackers().get(index); + + if(tracker instanceof ReferenceAdjustedTracker) + tracker = ((ReferenceAdjustedTracker) tracker).getTracker(); + + int trackerName = fbb.createString(tracker.getName()); + + Vector3f pos3f = new Vector3f(); + Quaternion quatRot = new Quaternion(); + tracker.getPosition(pos3f); + tracker.getRotation(quatRot); + + DeviceStatus.startDeviceStatus(fbb); + DeviceStatus.addName(fbb, trackerName); + DeviceStatus.addPosition(fbb, Vec3f.createVec3f(fbb, pos3f.x, pos3f.y, pos3f.z)); + DeviceStatus.addRotation(fbb, Quat.createQuat(fbb, quatRot.getX(), quatRot.getY(), quatRot.getZ(), quatRot.getW())); + + if (tracker instanceof IMUTracker) { + IMUTracker imu = (IMUTracker) tracker; + // put back to 0 - 256 so we can fit it in a byte instead of float + DeviceStatus.addBattery(fbb, (int)(imu.getBatteryLevel() * 256)); + DeviceStatus.addPing(fbb, imu.ping); + DeviceStatus.addSignal(fbb, imu.signalStrength); + DeviceStatus.addTps(fbb, (int)Math.floor(imu.getTPS())); + } + trackersIds[index] = DeviceStatus.endDeviceStatus(fbb); + } + + int trackers = TrackersList.createTrackersVector(fbb, trackersIds); + + TrackersList.startTrackersList(fbb); + TrackersList.addTrackers(fbb, trackers); + int list = TrackersList.endTrackersList(fbb); + + int outbound = OutboundPacket.createOutboundPacket(fbb, 0, false, OutboundUnion.TrackersList, list); + + + fbb.finish(outbound); + + ByteBuffer buf = fbb.dataBuffer(); + this.sendToApps(buf); + + lastMillis = System.currentTimeMillis(); + } + + + public void sendToApps(ByteBuffer buf) { + this.getConnections() + .stream() + .filter((conn) -> this.applications.containsKey(conn.hashCode())) + .forEach((conn) -> conn.send(buf)); + } + + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + LogManager.log.info("[WebSocketAPI] Web Socket API started on port " + getPort()); + setConnectionLostTimeout(0); + + this.server.addOnTick(this::updateTrackersList); + } +}