Compare commits

...

16 Commits

Author SHA1 Message Date
unlogisch04
5e89e64e27 Add ESP32 Upload possibility 2026-01-25 23:43:49 +01:00
unlogisch04
bd6d9b693a Try to set MCU into flash mode over Serial 2026-01-25 23:12:52 +01:00
unlogisch04
57d74747c2 Fix Serial output 2026-01-25 23:07:37 +01:00
Sapphire
0a493ac345 Fix scheduled resets triggering immediately when delay is unspecified (#1724) 2026-01-25 07:08:25 +02:00
Sapphire
17bb2703d1 Fix unassigned HMD flightlist step never showing (#1723) 2026-01-24 07:34:37 +02:00
Sapphire
f0981bf709 ProtocolAPI: Initialise RPCHandler after other members (#1722) 2026-01-24 01:17:25 +03:00
peelz
99de554c18 Add delay param to ResetRequest (#1712) 2026-01-23 03:38:08 +04:00
Sapphire
f95a4d56d7 Add feet mounting reset keybind (#1717) 2026-01-23 03:14:27 +04:00
Sapphire
1df3c9d322 Hide devices with unknown board type in DIY firmware tool (#1718) 2026-01-23 03:14:12 +04:00
sctanf
e0838cce6c Battery indicators (#1714) 2026-01-23 03:13:36 +04:00
Sapphire
e25d3201c2 Fix translation key for Autobone processing text (#1719) 2026-01-23 03:12:18 +04:00
Sapphire
5d14f14139 Fix alignment of neck and head dots in tracker assignment page (#1720) 2026-01-23 03:11:37 +04:00
Eiren Rain
09e81f5ace Fix trackers table having an extra header 2026-01-19 20:29:23 +01:00
Maya
8f57ef2de4 Migrate core/dev.slimevr.protocol to Kotlin (#1688)
Co-authored-by: Butterscotch! <bscotchvanilla@gmail.com>
2026-01-14 15:50:04 +01:00
loucass003
ea242960b3 Lint 2026-01-12 21:34:19 +01:00
Eiren Rain
35ac14a7de Packet loss (#1687)
Co-authored-by: loucass003 <loucass003@gmail.com>
2026-01-12 21:22:53 +01:00
51 changed files with 2002 additions and 2010 deletions

View File

@@ -336,6 +336,7 @@ tracker-table-column-name = Name
tracker-table-column-type = Type
tracker-table-column-battery = Battery
tracker-table-column-ping = Ping
tracker-table-column-packet_loss = Packet Loss
tracker-table-column-tps = TPS
tracker-table-column-temperature = Temp. °C
tracker-table-column-linear-acceleration = Accel. X/Y/Z
@@ -375,6 +376,10 @@ tracker-infos-magnetometer-status-v1 = { $status ->
[ENABLED] Enabled
}
tracker-infos-packet_loss = Packet Loss
tracker-infos-packets_lost = Packets Lost
tracker-infos-packets_received = Packets Received
## Tracker settings
tracker-settings-back = Go back to trackers list
tracker-settings-title = Tracker settings

View File

@@ -59,14 +59,14 @@ export function PersonFrontIcon({ mirror = true }: { mirror?: boolean }) {
/>
<circle
className="body-part-circle"
cx="81.5"
cx="82"
cy="80"
r={CIRCLE_RADIUS}
id={BodyPart[BodyPart.NECK]}
/>
<circle
className="body-part-circle"
cx="81.5"
cx="82"
cy="35"
r={CIRCLE_RADIUS}
id={BodyPart[BodyPart.HEAD]}

View File

@@ -11,6 +11,9 @@ export function BatteryIcon({
charging: boolean;
}) {
const col = useMemo(() => {
if (disabled) return 'fill-background-40';
else if (charging) return 'fill-status-success';
const colorsMap: { [key: number]: string } = {
0.4: 'fill-status-success',
0.2: 'fill-status-warning',
@@ -20,10 +23,8 @@ export function BatteryIcon({
const val = Object.keys(colorsMap)
.filter((key) => +key < value)
.sort((a, b) => +b - +a)[0];
return disabled
? 'fill-background-40'
: colorsMap[+val] || 'fill-background-10';
}, [value, disabled]);
return colorsMap[+val] || 'fill-background-10';
}, [value, disabled, charging]);
return (
<svg
@@ -59,13 +60,21 @@ export function BatteryIcon({
/>
</mask>
<g mask="url(#mask0_4_39)" className={classNames(col, 'opacity-100')}>
<rect width={value * 18} height="9" />
<rect width={charging ? 18 : value * 18} height="9" />
</g>
{charging && (
{charging && value <= 1 && (
<path
d="M 0.93561138,11.744353 2.4349252,6.1488377 H 0.0312815 L 3.5761014,0.00903018 2.2061799,5.1216451 h 2.4534885 z"
d="M 7.7638355,8.4189633 8.0112251,4.9834646 5.7712838,4.9834645 8.5644084,0.07977871 8.3170195,3.5152773 H 10.55696 Z"
fill="#081e30"
transform="translate(5,-1)"
/>
)}
{charging && value > 1 && (
<path
d="M 5.5342464,4.6225095 C 6.1777799,5.0106205 6.6131537,5.2516456 7.5253371,6.545223 8.4340868,4.4016445 8.7809738,3.661475 10.605195,1.5520288"
fill="none"
stroke="#081e30"
strokeWidth={1.5}
strokeLinecap="square"
/>
)}
</svg>

View File

@@ -231,7 +231,10 @@ function OTADevicesList({
const allDevices = useAtomValue(devicesAtom);
const devices =
allDevices.filter(({ trackers }) => {
allDevices.filter(({ hardwareInfo, trackers }) => {
// filter out devices we can't update
if (!hardwareInfo?.officialBoardType) return false;
// if the device has no trackers it is prob misconfigured so we skip for safety
if (trackers.length <= 0) return false;

View File

@@ -75,7 +75,7 @@ export function VerifyResultsStep({
hasRecording === ProcessStatus.FULFILLED && (
<Typography>
{l10n.getString(
'onboarding-automatic-proportions-verify-results-processing'
'onboarding-automatic_proportions-verify_results-processing'
)}
</Typography>
)}

View File

@@ -376,6 +376,37 @@ export function TrackerSettingsPage() {
{tracker?.device?.hardwareInfo?.networkProtocolVersion || '--'}
</Typography>
</div>
{tracker?.device?.hardwareStatus?.packetsReceived !== null && (
<>
<div className="flex justify-between">
<Typography>
{l10n.getString('tracker-infos-packet_loss')}
</Typography>
<Typography>
{(
(tracker?.device?.hardwareStatus?.packetLoss ?? 0) * 100
).toFixed(0)}
%
</Typography>
</div>
<div className="flex justify-between">
<Typography>
{l10n.getString('tracker-infos-packets_lost')}
</Typography>
<Typography>
{tracker?.device?.hardwareStatus?.packetsLost ?? '0'}
</Typography>
</div>
<div className="flex justify-between">
<Typography>
{l10n.getString('tracker-infos-packets_received')}
</Typography>
<Typography>
{tracker?.device?.hardwareStatus?.packetsReceived ?? '0'}
</Typography>
</div>
</>
)}
</div>
{tracker?.tracker && (
<IMUVisualizerWidget tracker={tracker?.tracker} />

View File

@@ -1,15 +1,24 @@
import { WifiIcon } from '@/components/commons/icon/WifiIcon';
import { Typography } from '@/components/commons/Typography';
import { Tooltip } from '@/components/commons/Tooltip';
export function TrackerWifi({
rssi,
ping,
rssiShowNumeric,
disabled,
packetLoss,
packetsLost,
packetsReceived,
showPacketLoss = false,
textColor = 'primary',
}: {
rssi: number | null;
ping: number | null;
packetLoss?: number | null;
packetsLost?: number | null;
packetsReceived?: number | null;
showPacketLoss?: boolean;
rssiShowNumeric?: boolean;
disabled?: boolean;
textColor?: string;
@@ -31,6 +40,17 @@ export function TrackerWifi({
{rssi} dBm
</Typography>
)}
{showPacketLoss && packetsReceived != null && (
<Tooltip
preferedDirection="top"
content={<Typography id="tracker-infos-packet_loss" />}
>
<Typography
color={textColor}
whitespace="whitespace-nowrap"
>{`${((packetLoss ?? 0) * 100).toFixed(0)}% (${packetsLost ?? 0} / ${packetsReceived})`}</Typography>
</Tooltip>
)}
</div>
)) || (
<div className="flex flex-col justify-center w-12">

View File

@@ -245,6 +245,10 @@ function Row({
ping={device?.hardwareStatus?.ping}
disabled={tracker.status === TrackerStatusEnum.DISCONNECTED}
textColor={fontColor}
showPacketLoss
packetLoss={device.hardwareStatus.packetLoss}
packetsLost={device.hardwareStatus.packetsLost}
packetsReceived={device.hardwareStatus.packetsReceived}
/>
)}
</Cell>

View File

@@ -3,6 +3,7 @@ package dev.slimevr
import com.melloware.jintellitype.HotkeyListener
import com.melloware.jintellitype.JIntellitype
import dev.slimevr.config.KeybindingsConfig
import dev.slimevr.tracking.trackers.TrackerUtils
import io.eiren.util.OperatingSystem
import io.eiren.util.OperatingSystem.Companion.currentPlatform
import io.eiren.util.ann.AWTThread
@@ -37,6 +38,11 @@ class Keybinding @AWTThread constructor(val server: VRServer) : HotkeyListener {
.registerHotKey(MOUNTING_RESET, mountingResetBinding)
LogManager.info("[Keybinding] Bound reset mounting to $mountingResetBinding")
val feetMountingResetBinding = config.feetMountingResetBinding
JIntellitype.getInstance()
.registerHotKey(FEET_MOUNTING_RESET, feetMountingResetBinding)
LogManager.info("[Keybinding] Bound feet reset mounting to $feetMountingResetBinding")
val pauseTrackingBinding = config.pauseTrackingBinding
JIntellitype.getInstance()
.registerHotKey(PAUSE_TRACKING, pauseTrackingBinding)
@@ -63,6 +69,12 @@ class Keybinding @AWTThread constructor(val server: VRServer) : HotkeyListener {
config.mountingResetDelay,
)
FEET_MOUNTING_RESET -> server.scheduleResetTrackersMounting(
RESET_SOURCE_NAME,
config.feetMountingResetDelay,
TrackerUtils.feetsBodyParts,
)
PAUSE_TRACKING ->
server
.scheduleTogglePauseTracking(
@@ -78,6 +90,7 @@ class Keybinding @AWTThread constructor(val server: VRServer) : HotkeyListener {
private const val FULL_RESET = 1
private const val YAW_RESET = 2
private const val MOUNTING_RESET = 3
private const val PAUSE_TRACKING = 4
private const val FEET_MOUNTING_RESET = 4
private const val PAUSE_TRACKING = 5
}
}

View File

@@ -8,6 +8,8 @@ public class KeybindingsConfig {
private String mountingResetBinding = "CTRL+ALT+SHIFT+I";
private String feetMountingResetBinding = "CTRL+ALT+SHIFT+P";
private String pauseTrackingBinding = "CTRL+ALT+SHIFT+O";
private long fullResetDelay = 0L;
@@ -16,6 +18,8 @@ public class KeybindingsConfig {
private long mountingResetDelay = 0L;
private long feetMountingResetDelay = 0L;
private long pauseTrackingDelay = 0L;
@@ -34,6 +38,10 @@ public class KeybindingsConfig {
return mountingResetBinding;
}
public String getFeetMountingResetBinding() {
return feetMountingResetBinding;
}
public String getPauseTrackingBinding() {
return pauseTrackingBinding;
}
@@ -62,6 +70,14 @@ public class KeybindingsConfig {
mountingResetDelay = delay;
}
public long getFeetMountingResetDelay() {
return feetMountingResetDelay;
}
public void setFeetMountingResetDelay(long delay) {
feetMountingResetDelay = delay;
}
public long getPauseTrackingDelay() {
return pauseTrackingDelay;
}

View File

@@ -11,6 +11,7 @@ import dev.slimevr.serial.SerialPort
import dev.slimevr.tracking.trackers.Tracker
import dev.slimevr.tracking.trackers.TrackerStatus
import dev.slimevr.tracking.trackers.TrackerStatusListener
import dev.slimevr.tracking.trackers.udp.MCUType
import dev.slimevr.tracking.trackers.udp.UDPDevice
import io.eiren.util.logging.LogManager
import kotlinx.coroutines.*
@@ -101,11 +102,27 @@ class FirmwareUpdateHandler(private val server: VRServer) :
)
return@suspendCancellableCoroutine
}
// TODO:
// - Use the Firmware Builder to get the expected MCU
// It would be wrong to assume that the Target MCU is the correct one,
// just because the device is listening on the correct port.
// The Upload protocol does not verify the compatibility of the firmware with the MCU.
val port = when (udpDevice.mcuType) {
MCUType.ESP8266 -> 8266
MCUType.ESP32, MCUType.ESP32_C3 -> 3232
else -> error("MCU-Typ: ${udpDevice.mcuType} not supported for OTA updates")
}
LogManager.info("[FirmwareUpdateHandler] Starting OTA update for device ${deviceId.id} at ${udpDevice.ipAddress.hostAddress}:$port and MCU ${udpDevice.mcuType}")
val task = OTAUpdateTask(
part.firmware,
deviceId,
udpDevice.ipAddress,
::onStatusChange,
port,
)
c.invokeOnCancellation {
task.cancel()
@@ -156,6 +173,15 @@ class FirmwareUpdateHandler(private val server: VRServer) :
flasher.addBin(part.firmware, part.offset.toInt())
}
// TODO:
// - Check if FW is able to use flashmode
// - Add check if the flashmode was successfully set to surpress the request
// for manual flashmode setting prompt in gui
server.serialHandler.openSerial(deviceId.id, false)
server.serialHandler.write("SET FLASHMODE\r\n".toByteArray())
server.serialHandler.closeSerial()
flasher.addProgressListener(object : FlashingProgressListener {
override fun progress(progress: Float) {
onStatusChange(

View File

@@ -14,6 +14,8 @@ import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import java.util.function.Consumer
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
import kotlin.math.min
class OTAUpdateTask(
@@ -21,8 +23,9 @@ class OTAUpdateTask(
private val deviceId: UpdateDeviceId<Int>,
private val deviceIp: InetAddress,
private val statusCallback: Consumer<UpdateStatusEvent<Int>>,
private val port: Int = 8266,
) {
private val receiveBuffer: ByteArray = ByteArray(38)
private val receiveBuffer: ByteArray = ByteArray(69)
var socketServer: ServerSocket? = null
var uploadSocket: Socket? = null
var authSocket: DatagramSocket? = null
@@ -40,17 +43,35 @@ class OTAUpdateTask(
return md5str.toString()
}
@Throws(NoSuchAlgorithmException::class)
private fun bytesToSha256(bytes: ByteArray): String {
val sha256 = MessageDigest.getInstance("SHA-256")
sha256.update(bytes)
val digest = sha256.digest()
val sha256str = StringBuilder()
for (b in digest) {
sha256str.append(String.format("%02x", b))
}
return sha256str.toString()
}
fun pbkdf2Hmac(password: String, salt: ByteArray, iterations: Int, keyLength: Int): ByteArray {
val spec = PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
return factory.generateSecret(spec).encoded
}
private fun authenticate(localPort: Int): Boolean {
try {
DatagramSocket().use { socket ->
authSocket = socket
statusCallback.accept(UpdateStatusEvent(deviceId, FirmwareUpdateStatus.AUTHENTICATING))
LogManager.info("[OTAUpdate] Sending OTA invitation to: $deviceIp")
LogManager.info("[OTAUpdate] Sending OTA invitation to: $deviceIp:$port")
val fileMd5 = bytesToMd5(firmware)
val message = "$FLASH $localPort ${firmware.size} $fileMd5\n"
socket.send(DatagramPacket(message.toByteArray(), message.length, deviceIp, PORT))
socket.send(DatagramPacket(message.toByteArray(), message.length, deviceIp, port))
socket.soTimeout = 10000
val authPacket = DatagramPacket(receiveBuffer, receiveBuffer.size)
@@ -68,13 +89,26 @@ class OTAUpdateTask(
if (args.size != 2 || args[0] != "AUTH") return false
LogManager.info("[OTAUpdate] Authenticating...")
var payload = ""
var signature = ""
val authToken = args[1]
val signature = bytesToMd5(UUID.randomUUID().toString().toByteArray())
val hashedPassword = bytesToMd5(PASSWORD.toByteArray())
val resultText = "$hashedPassword:$authToken:$signature"
val payload = bytesToMd5(resultText.toByteArray())
if (authToken.length == 32) {
signature =
bytesToMd5(UUID.randomUUID().toString().toByteArray())
val hashedPassword = bytesToMd5(PASSWORD.toByteArray())
val resultText = "$hashedPassword:$authToken:$signature"
payload = bytesToMd5(resultText.toByteArray())
} else if (authToken.length == 64) {
signature =
bytesToSha256(UUID.randomUUID().toString().toByteArray())
val salt = "$authToken:$signature"
val hashedPassword = bytesToSha256(PASSWORD.toByteArray())
val derivedkey = pbkdf2Hmac(hashedPassword, salt.toByteArray(), 10000, 32)
val derivedkeyHex = derivedkey.joinToString("") { "%02x".format(it) }
val challenge = "$derivedkeyHex:$authToken:$signature"
payload = bytesToSha256(challenge.toByteArray())
}
val authMessage = "$AUTH $signature $payload\n"
socket.soTimeout = 10000
@@ -83,7 +117,7 @@ class OTAUpdateTask(
authMessage.toByteArray(),
authMessage.length,
deviceIp,
PORT,
port,
),
)
@@ -198,7 +232,6 @@ class OTAUpdateTask(
companion object {
private const val FLASH = 0
private const val PORT = 8266
private const val PASSWORD = "SlimeVR-OTA"
private const val AUTH = 200
}

View File

@@ -1,49 +0,0 @@
package dev.slimevr.protocol;
import java.util.ArrayList;
import java.util.List;
public class ConnectionContext {
private final List<DataFeed> dataFeedList = new ArrayList<>();
private final List<Integer> subscribedTopics = new ArrayList<>();
private boolean useSerial = false;
private boolean useProvisioning = false;
private boolean useAutoBone = false;
public List<DataFeed> getDataFeedList() {
return dataFeedList;
}
public List<Integer> getSubscribedTopics() {
return subscribedTopics;
}
public boolean useSerial() {
return useSerial;
}
public void setUseSerial(boolean useSerial) {
this.useSerial = useSerial;
}
public boolean useAutoBone() {
return useAutoBone;
}
public void setUseAutoBone(boolean useAutoBone) {
this.useAutoBone = useAutoBone;
}
public boolean useProvisioning() {
return useProvisioning;
}
public void setUseProvisioning(boolean useProvisioning) {
this.useProvisioning = useProvisioning;
}
}

View File

@@ -0,0 +1,10 @@
package dev.slimevr.protocol
class ConnectionContext {
val dataFeedList: MutableList<DataFeed> = mutableListOf()
val subscribedTopics: MutableList<Int> = mutableListOf()
var useSerial: Boolean = false
var useProvisioning: Boolean = false
var useAutoBone: Boolean = false
}

View File

@@ -1,26 +0,0 @@
package dev.slimevr.protocol;
import solarxr_protocol.data_feed.DataFeedConfigT;
public class DataFeed {
private DataFeedConfigT config;
private Long timeLastSent;
public DataFeed(DataFeedConfigT config) {
this.config = config;
this.timeLastSent = System.currentTimeMillis();
}
public DataFeedConfigT getConfig() {
return config;
}
public Long getTimeLastSent() {
return timeLastSent;
}
public void setTimeLastSent(Long timeLastSent) {
this.timeLastSent = timeLastSent;
}
}

View File

@@ -0,0 +1,7 @@
package dev.slimevr.protocol
import solarxr_protocol.data_feed.DataFeedConfigT
class DataFeed(val config: DataFeedConfigT) {
var timeLastSent: Long = System.currentTimeMillis()
}

View File

@@ -1,14 +0,0 @@
package dev.slimevr.protocol;
import java.nio.ByteBuffer;
import java.util.UUID;
public interface GenericConnection {
UUID getConnectionId();
ConnectionContext getContext();
void send(ByteBuffer bytes);
}

View File

@@ -0,0 +1,12 @@
package dev.slimevr.protocol
import java.nio.ByteBuffer
import java.util.UUID
interface GenericConnection {
val connectionId: UUID
val context: ConnectionContext
fun send(bytes: ByteBuffer)
}

View File

@@ -1,69 +0,0 @@
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;
import solarxr_protocol.rpc.RpcMessageHeader;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class ProtocolAPI {
public final VRServer server;
public final RPCHandler rpcHandler;
public final DataFeedHandler dataFeedHandler;
public final PubSubHandler pubSubHandler;
private final List<ProtocolAPIServer> servers = new ArrayList<>();
public ProtocolAPI(VRServer server) {
this.server = server;
this.rpcHandler = new RPCHandler(this);
this.dataFeedHandler = new DataFeedHandler(this);
this.pubSubHandler = new PubSubHandler(this);
}
public void onMessage(GenericConnection conn, ByteBuffer message) {
MessageBundle messageBundle = MessageBundle.getRootAsMessageBundle(message);
try {
for (int index = 0; index < messageBundle.dataFeedMsgsLength(); index++) {
DataFeedMessageHeader header = messageBundle.dataFeedMsgsVector().get(index);
this.dataFeedHandler.onMessage(conn, header);
}
for (int index = 0; index < messageBundle.rpcMsgsLength(); index++) {
RpcMessageHeader header = messageBundle.rpcMsgsVector().get(index);
this.rpcHandler.onMessage(conn, header);
}
for (int index = 0; index < messageBundle.pubSubMsgsLength(); index++) {
PubSubHeader header = messageBundle.pubSubMsgsVector().get(index);
this.pubSubHandler.onMessage(conn, header);
}
} catch (AssertionError e) {
// Catch flatbuffer errors
e.printStackTrace();
}
}
public List<ProtocolAPIServer> getAPIServers() {
return servers;
}
public void registerAPIServer(ProtocolAPIServer server) {
this.servers.add(server);
}
public void removeAPIServer(ProtocolAPIServer server) {
this.servers.remove(server);
}
}

View File

@@ -0,0 +1,47 @@
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 java.nio.ByteBuffer
class ProtocolAPI(val server: VRServer) {
val apiServers: MutableList<ProtocolAPIServer> = ArrayList()
val dataFeedHandler: DataFeedHandler = DataFeedHandler(this)
val pubSubHandler: PubSubHandler = PubSubHandler(this)
val rpcHandler: RPCHandler = RPCHandler(this)
fun onMessage(conn: GenericConnection, message: ByteBuffer) {
val messageBundle = MessageBundle.getRootAsMessageBundle(message)
try {
for (index in 0..<messageBundle.dataFeedMsgsLength()) {
val header = messageBundle.dataFeedMsgsVector().get(index)
this.dataFeedHandler.onMessage(conn, header)
}
for (index in 0..<messageBundle.rpcMsgsLength()) {
val header = messageBundle.rpcMsgsVector().get(index)
this.rpcHandler.onMessage(conn, header)
}
for (index in 0..<messageBundle.pubSubMsgsLength()) {
val header = messageBundle.pubSubMsgsVector().get(index)
this.pubSubHandler.onMessage(conn, header)
}
} catch (e: AssertionError) {
// Catch flatbuffer errors
e.printStackTrace()
}
}
fun registerAPIServer(server: ProtocolAPIServer) {
this.apiServers.add(server)
}
fun removeAPIServer(server: ProtocolAPIServer) {
this.apiServers.remove(server)
}
}

View File

@@ -1,9 +0,0 @@
package dev.slimevr.protocol;
import java.util.stream.Stream;
public interface ProtocolAPIServer {
Stream<GenericConnection> getAPIConnections();
}

View File

@@ -0,0 +1,7 @@
package dev.slimevr.protocol
import java.util.stream.Stream
interface ProtocolAPIServer {
val apiConnections: Stream<GenericConnection>
}

View File

@@ -1,413 +0,0 @@
package dev.slimevr.protocol.datafeed;
import com.google.flatbuffers.FlatBufferBuilder;
import dev.slimevr.tracking.trackers.Device;
import dev.slimevr.tracking.trackers.Tracker;
import dev.slimevr.tracking.trackers.udp.MagnetometerStatus;
import dev.slimevr.tracking.trackers.udp.UDPDevice;
import io.github.axisangles.ktmath.Quaternion;
import io.github.axisangles.ktmath.Vector3;
import solarxr_protocol.data_feed.Bone;
import solarxr_protocol.data_feed.DataFeedUpdate;
import solarxr_protocol.data_feed.device_data.DeviceData;
import solarxr_protocol.data_feed.device_data.DeviceDataMaskT;
import solarxr_protocol.data_feed.tracker.TrackerData;
import solarxr_protocol.data_feed.tracker.TrackerDataMaskT;
import solarxr_protocol.data_feed.tracker.TrackerInfo;
import solarxr_protocol.datatypes.DeviceId;
import solarxr_protocol.datatypes.Ipv4Address;
import solarxr_protocol.datatypes.Temperature;
import solarxr_protocol.datatypes.TrackerId;
import solarxr_protocol.datatypes.hardware_info.HardwareInfo;
import solarxr_protocol.datatypes.hardware_info.HardwareStatus;
import solarxr_protocol.datatypes.math.Quat;
import solarxr_protocol.datatypes.math.Vec3f;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class DataFeedBuilder {
public static int createHardwareInfo(FlatBufferBuilder fbb, Device device) {
int nameOffset = device.getFirmwareVersion() != null
? fbb.createString(device.getFirmwareVersion())
: 0;
int manufacturerOffset = device.getManufacturer() != null
? fbb.createString(device.getManufacturer())
: 0;
int firmwareDateOffset = device.getFirmwareDate() != null
? fbb.createString(device.getFirmwareDate())
: 0;
int hardwareIdentifierOffset = fbb.createString(device.getHardwareIdentifier());
HardwareInfo.startHardwareInfo(fbb);
HardwareInfo.addFirmwareVersion(fbb, nameOffset);
HardwareInfo.addManufacturer(fbb, manufacturerOffset);
HardwareInfo.addHardwareIdentifier(fbb, hardwareIdentifierOffset);
HardwareInfo.addFirmwareDate(fbb, firmwareDateOffset);
if (device instanceof UDPDevice udpDevice) {
var address = udpDevice.getIpAddress().getAddress();
HardwareInfo
.addIpAddress(
fbb,
Ipv4Address
.createIpv4Address(
fbb,
ByteBuffer.wrap(address).getInt()
)
);
HardwareInfo.addNetworkProtocolVersion(fbb, udpDevice.protocolVersion);
}
// BRUH MOMENT
// TODO need support: HardwareInfo.addHardwareRevision(fbb,
// hardwareRevisionOffset);
// TODO need support: HardwareInfo.addDisplayName(fbb, de);
HardwareInfo.addMcuId(fbb, device.getMcuType().getSolarType());
HardwareInfo.addOfficialBoardType(fbb, device.getBoardType().getSolarType());
return HardwareInfo.endHardwareInfo(fbb);
}
public static int createTrackerId(FlatBufferBuilder fbb, Tracker tracker) {
TrackerId.startTrackerId(fbb);
TrackerId.addTrackerNum(fbb, tracker.getTrackerNum());
if (tracker.getDevice() != null)
TrackerId.addDeviceId(fbb, DeviceId.createDeviceId(fbb, tracker.getDevice().getId()));
return TrackerId.endTrackerId(fbb);
}
public static int createVec3(FlatBufferBuilder fbb, Vector3 vec) {
return Vec3f
.createVec3f(
fbb,
vec.getX(),
vec.getY(),
vec.getZ()
);
}
public static int createQuat(FlatBufferBuilder fbb, Quaternion quaternion) {
return Quat
.createQuat(
fbb,
quaternion.getX(),
quaternion.getY(),
quaternion.getZ(),
quaternion.getW()
);
}
public static int createTrackerInfos(
FlatBufferBuilder fbb,
boolean infoMask,
Tracker tracker
) {
if (!infoMask)
return 0;
int displayNameOffset = fbb.createString(tracker.getDisplayName());
int customNameOffset = tracker.getCustomName() != null
? fbb.createString(tracker.getCustomName())
: 0;
TrackerInfo.startTrackerInfo(fbb);
if (tracker.getTrackerPosition() != null)
TrackerInfo.addBodyPart(fbb, tracker.getTrackerPosition().getBodyPart());
TrackerInfo.addEditable(fbb, tracker.getUserEditable());
TrackerInfo.addIsComputed(fbb, tracker.isComputed());
TrackerInfo.addDisplayName(fbb, displayNameOffset);
TrackerInfo.addCustomName(fbb, customNameOffset);
if (tracker.getImuType() != null) {
TrackerInfo.addImuType(fbb, tracker.getImuType().getSolarType());
}
// TODO need support: TrackerInfo.addPollRate(fbb, tracker.);
if (tracker.isImu()) {
TrackerInfo.addIsImu(fbb, true);
TrackerInfo
.addAllowDriftCompensation(
fbb,
tracker.getResetsHandler().getAllowDriftCompensation()
);
} else {
TrackerInfo.addIsImu(fbb, false);
TrackerInfo.addAllowDriftCompensation(fbb, false);
}
if (tracker.getAllowMounting()) {
Quaternion quaternion = tracker.getResetsHandler().getMountingOrientation();
Quaternion mountResetFix = tracker.getResetsHandler().getMountRotFix();
TrackerInfo.addMountingOrientation(fbb, createQuat(fbb, quaternion));
TrackerInfo.addMountingResetOrientation(fbb, createQuat(fbb, mountResetFix));
}
TrackerInfo.addMagnetometer(fbb, tracker.getMagStatus().getSolarType());
TrackerInfo.addIsHmd(fbb, tracker.isHmd());
TrackerInfo.addDataSupport(fbb, tracker.getTrackerDataType().getSolarType());
return TrackerInfo.endTrackerInfo(fbb);
}
public static int createTrackerPosition(FlatBufferBuilder fbb, Tracker tracker) {
return createVec3(fbb, tracker.getPosition());
}
public static int createTrackerRotation(FlatBufferBuilder fbb, Tracker tracker) {
return createQuat(fbb, tracker.getRawRotation());
}
public static int createTrackerAcceleration(FlatBufferBuilder fbb, Tracker tracker) {
return createVec3(fbb, tracker.getAcceleration());
}
public static int createTrackerMagneticVector(FlatBufferBuilder fbb, Tracker tracker) {
return createVec3(fbb, tracker.getMagVector());
}
public static int createTrackerTemperature(FlatBufferBuilder fbb, Tracker tracker) {
if (tracker.getTemperature() == null)
return 0;
return Temperature.createTemperature(fbb, tracker.getTemperature());
}
public static int createTrackerData(
FlatBufferBuilder fbb,
TrackerDataMaskT mask,
Tracker tracker
) {
int trackerInfosOffset = DataFeedBuilder.createTrackerInfos(fbb, mask.getInfo(), tracker);
int trackerIdOffset = DataFeedBuilder.createTrackerId(fbb, tracker);
int stayAlignedOffset = 0;
if (mask.getStayAligned()) {
stayAlignedOffset = DataFeedBuilderKotlin.INSTANCE
.createTrackerStayAlignedTracker(fbb, tracker.getStayAligned());
}
TrackerData.startTrackerData(fbb);
TrackerData.addTrackerId(fbb, trackerIdOffset);
if (trackerInfosOffset != 0)
TrackerData.addInfo(fbb, trackerInfosOffset);
if (mask.getStatus())
TrackerData.addStatus(fbb, tracker.getStatus().getId() + 1);
if (mask.getPosition() && tracker.getHasPosition())
TrackerData.addPosition(fbb, DataFeedBuilder.createTrackerPosition(fbb, tracker));
if (mask.getRotation() && tracker.getHasRotation())
TrackerData.addRotation(fbb, DataFeedBuilder.createTrackerRotation(fbb, tracker));
if (mask.getLinearAcceleration() && tracker.getHasAcceleration())
TrackerData
.addLinearAcceleration(
fbb,
DataFeedBuilder.createTrackerAcceleration(fbb, tracker)
);
if (mask.getTemp()) {
int trackerTemperatureOffset = DataFeedBuilder.createTrackerTemperature(fbb, tracker);
if (trackerTemperatureOffset != 0)
TrackerData.addTemp(fbb, trackerTemperatureOffset);
}
if (tracker.getAllowMounting() && tracker.getHasRotation()) {
if (mask.getRotationReferenceAdjusted()) {
TrackerData
.addRotationReferenceAdjusted(fbb, createQuat(fbb, tracker.getRotation()));
}
if (mask.getRotationIdentityAdjusted()) {
TrackerData
.addRotationIdentityAdjusted(
fbb,
createQuat(fbb, tracker.getIdentityAdjustedRotation())
);
}
} else if (tracker.getAllowReset() && tracker.getHasRotation()) {
if (mask.getRotationReferenceAdjusted()) {
TrackerData
.addRotationReferenceAdjusted(fbb, createQuat(fbb, tracker.getRotation()));
}
if (mask.getRotationIdentityAdjusted()) {
TrackerData
.addRotationIdentityAdjusted(fbb, createQuat(fbb, tracker.getRawRotation()));
}
}
if (mask.getTps()) {
TrackerData.addTps(fbb, (int) tracker.getTps());
}
if (mask.getRawMagneticVector() && tracker.getMagStatus() == MagnetometerStatus.ENABLED) {
TrackerData.addRawMagneticVector(fbb, createTrackerMagneticVector(fbb, tracker));
}
if (mask.getStayAligned()) {
TrackerData.addStayAligned(fbb, stayAlignedOffset);
}
return TrackerData.endTrackerData(fbb);
}
public static int createTrackersData(
FlatBufferBuilder fbb,
DeviceDataMaskT mask,
Device device
) {
if (mask.getTrackerData() == null)
return 0;
List<Integer> trackersOffsets = new ArrayList<>();
device
.getTrackers()
.forEach(
(key, value) -> trackersOffsets
.add(DataFeedBuilder.createTrackerData(fbb, mask.getTrackerData(), value))
);
DeviceData.startTrackersVector(fbb, trackersOffsets.size());
trackersOffsets.forEach(offset -> DeviceData.addTrackers(fbb, offset));
return fbb.endVector();
}
public static int createDeviceData(
FlatBufferBuilder fbb,
int id,
DeviceDataMaskT mask,
Device device
) {
if (!mask.getDeviceData())
return 0;
if (device.getTrackers().size() <= 0)
return 0;
Tracker firstTracker = device.getTrackers().get(0);
if (firstTracker == null) {
// Not actually the "first" tracker, but do we care?
firstTracker = device.getTrackers().entrySet().iterator().next().getValue();
}
Tracker tracker = firstTracker;
if (tracker == null)
return 0;
HardwareStatus.startHardwareStatus(fbb);
HardwareStatus.addErrorStatus(fbb, tracker.getStatus().getId());
if (tracker.getBatteryVoltage() != null) {
HardwareStatus.addBatteryVoltage(fbb, tracker.getBatteryVoltage());
}
if (tracker.getBatteryLevel() != null) {
HardwareStatus.addBatteryPctEstimate(fbb, (int) tracker.getBatteryLevel().floatValue());
}
if (tracker.getPing() != null) {
HardwareStatus.addPing(fbb, tracker.getPing());
}
if (tracker.getSignalStrength() != null) {
HardwareStatus.addRssi(fbb, (short) tracker.getSignalStrength().floatValue());
}
if (tracker.getBatteryRemainingRuntime() != null) {
HardwareStatus.addBatteryRuntimeEstimate(fbb, tracker.getBatteryRemainingRuntime());
}
int hardwareDataOffset = HardwareStatus.endHardwareStatus(fbb);
int hardwareInfoOffset = DataFeedBuilder.createHardwareInfo(fbb, device);
int trackersOffset = DataFeedBuilder.createTrackersData(fbb, mask, device);
int nameOffset = device.getName() != null
? fbb.createString(device.getName())
: 0;
DeviceData.startDeviceData(fbb);
DeviceData.addCustomName(fbb, nameOffset);
DeviceData.addId(fbb, DeviceId.createDeviceId(fbb, id));
DeviceData.addHardwareStatus(fbb, hardwareDataOffset);
DeviceData.addHardwareInfo(fbb, hardwareInfoOffset);
DeviceData.addTrackers(fbb, trackersOffset);
return DeviceData.endDeviceData(fbb);
}
public static int createSyntheticTrackersData(
FlatBufferBuilder fbb,
TrackerDataMaskT trackerDataMaskT,
List<Tracker> trackers
) {
if (trackerDataMaskT == null)
return 0;
List<Integer> trackerOffsets = new ArrayList<>();
trackers
.forEach(
(tracker) -> trackerOffsets
.add(DataFeedBuilder.createTrackerData(fbb, trackerDataMaskT, tracker))
);
DataFeedUpdate.startSyntheticTrackersVector(fbb, trackerOffsets.size());
trackerOffsets.forEach((tracker -> DataFeedUpdate.addSyntheticTrackers(fbb, tracker)));
return fbb.endVector();
}
public static int createDevicesData(
FlatBufferBuilder fbb,
DeviceDataMaskT deviceDataMaskT,
List<Device> devices
) {
if (deviceDataMaskT == null)
return 0;
int[] devicesDataOffsets = new int[devices.size()];
for (int i = 0; i < devices.size(); i++) {
Device device = devices.get(i);
devicesDataOffsets[i] = DataFeedBuilder
.createDeviceData(fbb, device.getId(), deviceDataMaskT, device);
}
return DataFeedUpdate.createDevicesVector(fbb, devicesDataOffsets);
}
public static int createBonesData(
FlatBufferBuilder fbb,
boolean shouldSend,
List<dev.slimevr.tracking.processor.Bone> bones
) {
if (!shouldSend) {
return 0;
}
var boneOffsets = new int[bones.size()];
for (var i = 0; i < bones.size(); ++i) {
var bi = bones.get(i);
var headPosG = bi.getPosition();
var rotG = bi.getGlobalRotation();
var length = bi.getLength();
Bone.startBone(fbb);
var rotGOffset = createQuat(fbb, rotG);
Bone.addRotationG(fbb, rotGOffset);
var headPosGOffset = Vec3f
.createVec3f(fbb, headPosG.getX(), headPosG.getY(), headPosG.getZ());
Bone.addHeadPositionG(fbb, headPosGOffset);
Bone.addBodyPart(fbb, bi.getBoneType().bodyPart);
Bone.addBoneLength(fbb, length);
boneOffsets[i] = Bone.endBone(fbb);
}
return DataFeedUpdate.createBonesVector(fbb, boneOffsets);
}
}

View File

@@ -0,0 +1,503 @@
package dev.slimevr.protocol.datafeed
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.guards.ServerGuards
import dev.slimevr.tracking.processor.Bone
import dev.slimevr.tracking.processor.skeleton.HumanSkeleton
import dev.slimevr.tracking.processor.stayaligned.poses.RelaxedPose
import dev.slimevr.tracking.processor.stayaligned.trackers.RestDetector
import dev.slimevr.tracking.processor.stayaligned.trackers.StayAlignedTrackerState
import dev.slimevr.tracking.trackers.Device
import dev.slimevr.tracking.trackers.Tracker
import dev.slimevr.tracking.trackers.udp.MagnetometerStatus
import dev.slimevr.tracking.trackers.udp.UDPDevice
import io.github.axisangles.ktmath.Quaternion
import io.github.axisangles.ktmath.Vector3
import solarxr_protocol.data_feed.DataFeedUpdate
import solarxr_protocol.data_feed.device_data.DeviceData
import solarxr_protocol.data_feed.device_data.DeviceDataMaskT
import solarxr_protocol.data_feed.stay_aligned.StayAlignedPose
import solarxr_protocol.data_feed.stay_aligned.StayAlignedTracker
import solarxr_protocol.data_feed.tracker.TrackerData
import solarxr_protocol.data_feed.tracker.TrackerDataMaskT
import solarxr_protocol.data_feed.tracker.TrackerInfo
import solarxr_protocol.datatypes.DeviceId
import solarxr_protocol.datatypes.Ipv4Address
import solarxr_protocol.datatypes.Temperature
import solarxr_protocol.datatypes.TrackerId
import solarxr_protocol.datatypes.hardware_info.HardwareInfo
import solarxr_protocol.datatypes.hardware_info.HardwareStatus
import solarxr_protocol.datatypes.math.Quat
import solarxr_protocol.datatypes.math.Vec3f
import java.nio.ByteBuffer
import java.util.function.Consumer
fun createHardwareInfo(fbb: FlatBufferBuilder, device: Device): Int {
val nameOffset = if (device.firmwareVersion != null) {
fbb.createString(device.firmwareVersion)
} else {
0
}
val manufacturerOffset = if (device.manufacturer != null) {
fbb.createString(device.manufacturer)
} else {
0
}
val firmwareDateOffset = if (device.firmwareDate != null) {
fbb.createString(device.firmwareDate)
} else {
0
}
val hardwareIdentifierOffset = fbb.createString(device.hardwareIdentifier)
HardwareInfo.startHardwareInfo(fbb)
HardwareInfo.addFirmwareVersion(fbb, nameOffset)
HardwareInfo.addFirmwareDate(fbb, firmwareDateOffset)
HardwareInfo.addManufacturer(fbb, manufacturerOffset)
HardwareInfo.addHardwareIdentifier(fbb, hardwareIdentifierOffset)
if (device is UDPDevice) {
val address = device.ipAddress.address
HardwareInfo
.addIpAddress(
fbb,
Ipv4Address
.createIpv4Address(
fbb,
ByteBuffer.wrap(address).getInt().toLong(),
),
)
HardwareInfo.addNetworkProtocolVersion(fbb, device.protocolVersion)
}
// BRUH MOMENT
// TODO need support: HardwareInfo.addHardwareRevision(fbb,
// hardwareRevisionOffset);
// TODO need support: HardwareInfo.addDisplayName(fbb, de);
HardwareInfo.addMcuId(fbb, device.mcuType.getSolarType())
HardwareInfo.addOfficialBoardType(fbb, device.boardType.getSolarType())
return HardwareInfo.endHardwareInfo(fbb)
}
fun createTrackerId(fbb: FlatBufferBuilder, tracker: Tracker): Int {
TrackerId.startTrackerId(fbb)
TrackerId.addTrackerNum(fbb, tracker.trackerNum)
if (tracker.device != null) {
TrackerId.addDeviceId(
fbb,
DeviceId.createDeviceId(fbb, tracker.device.id),
)
}
return TrackerId.endTrackerId(fbb)
}
fun createVec3(fbb: FlatBufferBuilder, vec: Vector3): Int = Vec3f
.createVec3f(
fbb,
vec.x,
vec.y,
vec.z,
)
fun createQuat(fbb: FlatBufferBuilder, quaternion: Quaternion): Int = Quat
.createQuat(
fbb,
quaternion.x,
quaternion.y,
quaternion.z,
quaternion.w,
)
fun createTrackerInfos(
fbb: FlatBufferBuilder,
infoMask: Boolean,
tracker: Tracker,
): Int {
if (!infoMask) return 0
val displayNameOffset = fbb.createString(tracker.displayName)
val customNameOffset = if (tracker.customName != null) {
fbb.createString(tracker.customName)
} else {
0
}
TrackerInfo.startTrackerInfo(fbb)
if (tracker.trackerPosition != null) {
TrackerInfo.addBodyPart(
fbb,
tracker.trackerPosition!!.bodyPart,
)
}
TrackerInfo.addEditable(fbb, tracker.userEditable)
TrackerInfo.addIsComputed(fbb, tracker.isComputed)
TrackerInfo.addDisplayName(fbb, displayNameOffset)
TrackerInfo.addCustomName(fbb, customNameOffset)
if (tracker.imuType != null) {
TrackerInfo.addImuType(fbb, tracker.imuType.getSolarType())
}
// TODO need support: TrackerInfo.addPollRate(fbb, tracker.);
if (tracker.isImu()) {
TrackerInfo.addIsImu(fbb, true)
TrackerInfo
.addAllowDriftCompensation(
fbb,
tracker.resetsHandler.allowDriftCompensation,
)
} else {
TrackerInfo.addIsImu(fbb, false)
TrackerInfo.addAllowDriftCompensation(fbb, false)
}
if (tracker.allowMounting) {
val quaternion = tracker.resetsHandler.mountingOrientation
val mountResetFix = tracker.resetsHandler.mountRotFix
TrackerInfo.addMountingOrientation(fbb, createQuat(fbb, quaternion))
TrackerInfo.addMountingResetOrientation(fbb, createQuat(fbb, mountResetFix))
}
TrackerInfo.addMagnetometer(fbb, tracker.magStatus.getSolarType())
TrackerInfo.addIsHmd(fbb, tracker.isHmd)
TrackerInfo.addDataSupport(fbb, tracker.trackerDataType.getSolarType())
return TrackerInfo.endTrackerInfo(fbb)
}
fun createTrackerPosition(fbb: FlatBufferBuilder, tracker: Tracker): Int = createVec3(fbb, tracker.position)
fun createTrackerRotation(fbb: FlatBufferBuilder, tracker: Tracker): Int = createQuat(fbb, tracker.getRawRotation())
fun createTrackerAcceleration(fbb: FlatBufferBuilder, tracker: Tracker): Int = createVec3(fbb, tracker.getAcceleration())
fun createTrackerMagneticVector(fbb: FlatBufferBuilder, tracker: Tracker): Int = createVec3(fbb, tracker.getMagVector())
fun createTrackerTemperature(fbb: FlatBufferBuilder, tracker: Tracker): Int {
if (tracker.temperature == null) return 0
return Temperature.createTemperature(fbb, tracker.temperature!!)
}
fun createTrackerData(
fbb: FlatBufferBuilder,
mask: TrackerDataMaskT,
tracker: Tracker,
): Int {
val trackerInfosOffset = createTrackerInfos(fbb, mask.info, tracker)
val trackerIdOffset = createTrackerId(fbb, tracker)
var stayAlignedOffset = 0
if (mask.stayAligned) {
stayAlignedOffset =
createTrackerStayAlignedTracker(fbb, tracker.stayAligned)
}
TrackerData.startTrackerData(fbb)
TrackerData.addTrackerId(fbb, trackerIdOffset)
if (trackerInfosOffset != 0) TrackerData.addInfo(fbb, trackerInfosOffset)
if (mask.status) TrackerData.addStatus(fbb, tracker.status.id + 1)
if (mask.position && tracker.hasPosition) {
TrackerData.addPosition(
fbb,
createTrackerPosition(fbb, tracker),
)
}
if (mask.rotation && tracker.hasRotation) {
TrackerData.addRotation(
fbb,
createTrackerRotation(fbb, tracker),
)
}
if (mask.linearAcceleration && tracker.hasAcceleration) {
TrackerData
.addLinearAcceleration(
fbb,
createTrackerAcceleration(fbb, tracker),
)
}
if (mask.temp) {
val trackerTemperatureOffset = createTrackerTemperature(fbb, tracker)
if (trackerTemperatureOffset != 0) {
TrackerData.addTemp(
fbb,
trackerTemperatureOffset,
)
}
}
if (tracker.allowMounting && tracker.hasRotation) {
if (mask.rotationReferenceAdjusted) {
TrackerData
.addRotationReferenceAdjusted(
fbb,
createQuat(fbb, tracker.getRotation()),
)
}
if (mask.rotationIdentityAdjusted) {
TrackerData
.addRotationIdentityAdjusted(
fbb,
createQuat(fbb, tracker.getIdentityAdjustedRotation()),
)
}
} else if (tracker.allowReset && tracker.hasRotation) {
if (mask.rotationReferenceAdjusted) {
TrackerData
.addRotationReferenceAdjusted(
fbb,
createQuat(fbb, tracker.getRotation()),
)
}
if (mask.rotationIdentityAdjusted) {
TrackerData
.addRotationIdentityAdjusted(
fbb,
createQuat(fbb, tracker.getRawRotation()),
)
}
}
if (mask.tps) {
TrackerData.addTps(fbb, tracker.tps.toInt())
}
if (mask.rawMagneticVector && tracker.magStatus == MagnetometerStatus.ENABLED) {
TrackerData.addRawMagneticVector(
fbb,
createTrackerMagneticVector(fbb, tracker),
)
}
if (mask.stayAligned) {
TrackerData.addStayAligned(fbb, stayAlignedOffset)
}
return TrackerData.endTrackerData(fbb)
}
fun createTrackersData(
fbb: FlatBufferBuilder,
mask: DeviceDataMaskT,
device: Device,
): Int {
if (mask.trackerData == null) return 0
val trackersOffsets: MutableList<Int> = ArrayList()
device
.trackers
.forEach { (_: Int, value: Tracker) ->
trackersOffsets
.add(createTrackerData(fbb, mask.trackerData, value))
}
DeviceData.startTrackersVector(fbb, trackersOffsets.size)
trackersOffsets.forEach(
Consumer { offset: Int ->
DeviceData.addTrackers(
fbb,
offset,
)
},
)
return fbb.endVector()
}
fun createDeviceData(
fbb: FlatBufferBuilder,
id: Int,
mask: DeviceDataMaskT,
device: Device,
): Int {
if (!mask.deviceData) return 0
if (device.trackers.isEmpty()) return 0
var firstTracker = device.trackers[0]
if (firstTracker == null) {
// Not actually the "first" tracker, but do we care?
firstTracker = device.trackers.entries.iterator().next().value
}
val tracker: Tracker = firstTracker
HardwareStatus.startHardwareStatus(fbb)
HardwareStatus.addErrorStatus(fbb, tracker.status.id)
if (tracker.batteryVoltage != null) {
HardwareStatus.addBatteryVoltage(fbb, tracker.batteryVoltage!!)
}
if (tracker.batteryLevel != null) {
HardwareStatus.addBatteryPctEstimate(fbb, tracker.batteryLevel!!.toInt())
}
if (tracker.ping != null) {
HardwareStatus.addPing(fbb, tracker.ping!!)
}
if (tracker.signalStrength != null) {
HardwareStatus.addRssi(fbb, tracker.signalStrength!!.toShort())
}
if (tracker.packetLoss != null) {
HardwareStatus.addPacketLoss(fbb, tracker.packetLoss!!)
}
if (tracker.packetsLost != null) {
HardwareStatus.addPacketsLost(fbb, tracker.packetsLost!!)
}
if (tracker.packetsReceived != null) {
HardwareStatus.addPacketsReceived(fbb, tracker.packetsReceived!!)
}
val hardwareDataOffset = HardwareStatus.endHardwareStatus(fbb)
val hardwareInfoOffset = createHardwareInfo(fbb, device)
val trackersOffset = createTrackersData(fbb, mask, device)
val nameOffset = if (device.name != null) {
fbb.createString(device.name)
} else {
0
}
DeviceData.startDeviceData(fbb)
DeviceData.addCustomName(fbb, nameOffset)
DeviceData.addId(fbb, DeviceId.createDeviceId(fbb, id))
DeviceData.addHardwareStatus(fbb, hardwareDataOffset)
DeviceData.addHardwareInfo(fbb, hardwareInfoOffset)
DeviceData.addTrackers(fbb, trackersOffset)
return DeviceData.endDeviceData(fbb)
}
fun createSyntheticTrackersData(
fbb: FlatBufferBuilder,
trackerDataMaskT: TrackerDataMaskT?,
trackers: MutableList<Tracker>,
): Int {
if (trackerDataMaskT == null) return 0
val trackerOffsets: MutableList<Int> = ArrayList()
trackers
.forEach(
Consumer { tracker: Tracker ->
trackerOffsets
.add(createTrackerData(fbb, trackerDataMaskT, tracker))
},
)
DataFeedUpdate.startSyntheticTrackersVector(fbb, trackerOffsets.size)
trackerOffsets.forEach(
(
Consumer { tracker: Int ->
DataFeedUpdate.addSyntheticTrackers(
fbb,
tracker,
)
}
),
)
return fbb.endVector()
}
fun createDevicesData(
fbb: FlatBufferBuilder,
deviceDataMaskT: DeviceDataMaskT?,
devices: MutableList<Device>,
): Int {
if (deviceDataMaskT == null) return 0
val devicesDataOffsets = IntArray(devices.size)
for (i in devices.indices) {
val device = devices[i]
devicesDataOffsets[i] =
createDeviceData(fbb, device.id, deviceDataMaskT, device)
}
return DataFeedUpdate.createDevicesVector(fbb, devicesDataOffsets)
}
fun createBonesData(
fbb: FlatBufferBuilder,
shouldSend: Boolean,
bones: MutableList<Bone>,
): Int {
if (!shouldSend) {
return 0
}
val boneOffsets = IntArray(bones.size)
for (i in bones.indices) {
val bi = bones[i]
val headPosG =
bi.getPosition()
val rotG =
bi.getGlobalRotation()
val length = bi.length
solarxr_protocol.data_feed.Bone.startBone(fbb)
val rotGOffset = createQuat(fbb, rotG)
solarxr_protocol.data_feed.Bone.addRotationG(fbb, rotGOffset)
val headPosGOffset = Vec3f
.createVec3f(fbb, headPosG.x, headPosG.y, headPosG.z)
solarxr_protocol.data_feed.Bone.addHeadPositionG(fbb, headPosGOffset)
solarxr_protocol.data_feed.Bone.addBodyPart(fbb, bi.boneType.bodyPart)
solarxr_protocol.data_feed.Bone.addBoneLength(fbb, length)
boneOffsets[i] = solarxr_protocol.data_feed.Bone.endBone(fbb)
}
return DataFeedUpdate.createBonesVector(fbb, boneOffsets)
}
fun createStayAlignedPose(
fbb: FlatBufferBuilder,
humanSkeleton: HumanSkeleton,
): Int {
val relaxedPose = RelaxedPose.fromTrackers(humanSkeleton)
StayAlignedPose.startStayAlignedPose(fbb)
StayAlignedPose.addUpperLegAngleInDeg(fbb, relaxedPose.upperLeg.toDeg())
StayAlignedPose.addLowerLegAngleInDeg(fbb, relaxedPose.lowerLeg.toDeg())
StayAlignedPose.addFootAngleInDeg(fbb, relaxedPose.foot.toDeg())
return StayAlignedPose.endStayAlignedPose(fbb)
}
fun createTrackerStayAlignedTracker(
fbb: FlatBufferBuilder,
state: StayAlignedTrackerState,
): Int {
StayAlignedTracker.startStayAlignedTracker(fbb)
StayAlignedTracker.addYawCorrectionInDeg(fbb, state.yawCorrection.toDeg())
StayAlignedTracker.addLockedErrorInDeg(
fbb,
state.yawErrors.lockedError.toL2Norm().toDeg(),
)
StayAlignedTracker.addCenterErrorInDeg(
fbb,
state.yawErrors.centerError.toL2Norm().toDeg(),
)
StayAlignedTracker.addNeighborErrorInDeg(
fbb,
state.yawErrors.neighborError.toL2Norm().toDeg(),
)
StayAlignedTracker.addLocked(
fbb,
state.restDetector.state == RestDetector.State.AT_REST,
)
return StayAlignedTracker.endStayAlignedTracker(fbb)
}
fun createServerGuard(fbb: FlatBufferBuilder, serverGuards: ServerGuards): Int = solarxr_protocol.data_feed.server.ServerGuards.createServerGuards(
fbb,
serverGuards.canDoMounting,
serverGuards.canDoYawReset,
serverGuards.canDoUserHeightCalibration,
)

View File

@@ -1,50 +0,0 @@
package dev.slimevr.protocol.datafeed
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.guards.ServerGuards
import dev.slimevr.tracking.processor.skeleton.HumanSkeleton
import dev.slimevr.tracking.processor.stayaligned.poses.RelaxedPose
import dev.slimevr.tracking.processor.stayaligned.trackers.RestDetector
import dev.slimevr.tracking.processor.stayaligned.trackers.StayAlignedTrackerState
import solarxr_protocol.data_feed.stay_aligned.StayAlignedPose
import solarxr_protocol.data_feed.stay_aligned.StayAlignedTracker
object DataFeedBuilderKotlin {
fun createStayAlignedPose(
fbb: FlatBufferBuilder,
humanSkeleton: HumanSkeleton,
): Int {
val relaxedPose = RelaxedPose.fromTrackers(humanSkeleton)
StayAlignedPose.startStayAlignedPose(fbb)
StayAlignedPose.addUpperLegAngleInDeg(fbb, relaxedPose.upperLeg.toDeg())
StayAlignedPose.addLowerLegAngleInDeg(fbb, relaxedPose.lowerLeg.toDeg())
StayAlignedPose.addFootAngleInDeg(fbb, relaxedPose.foot.toDeg())
return StayAlignedPose.endStayAlignedPose(fbb)
}
fun createTrackerStayAlignedTracker(
fbb: FlatBufferBuilder,
state: StayAlignedTrackerState,
): Int {
StayAlignedTracker.startStayAlignedTracker(fbb)
StayAlignedTracker.addYawCorrectionInDeg(fbb, state.yawCorrection.toDeg())
StayAlignedTracker.addLockedErrorInDeg(fbb, state.yawErrors.lockedError.toL2Norm().toDeg())
StayAlignedTracker.addCenterErrorInDeg(fbb, state.yawErrors.centerError.toL2Norm().toDeg())
StayAlignedTracker.addNeighborErrorInDeg(fbb, state.yawErrors.neighborError.toL2Norm().toDeg())
StayAlignedTracker.addLocked(fbb, state.restDetector.state == RestDetector.State.AT_REST)
return StayAlignedTracker.endStayAlignedTracker(fbb)
}
fun createServerGuard(fbb: FlatBufferBuilder, serverGuards: ServerGuards): Int = solarxr_protocol.data_feed.server.ServerGuards.createServerGuards(
fbb,
serverGuards.canDoMounting,
serverGuards.canDoYawReset,
serverGuards.canDoUserHeightCalibration,
)
}

View File

@@ -1,192 +0,0 @@
package dev.slimevr.protocol.datafeed;
import com.google.flatbuffers.FlatBufferBuilder;
import dev.slimevr.protocol.DataFeed;
import dev.slimevr.protocol.GenericConnection;
import dev.slimevr.protocol.ProtocolAPI;
import dev.slimevr.protocol.ProtocolHandler;
import dev.slimevr.tracking.trackers.Tracker;
import io.eiren.util.logging.LogManager;
import solarxr_protocol.MessageBundle;
import solarxr_protocol.data_feed.*;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
public class DataFeedHandler extends ProtocolHandler<DataFeedMessageHeader> {
private final ProtocolAPI api;
public DataFeedHandler(ProtocolAPI api) {
this.api = api;
registerPacketListener(DataFeedMessage.StartDataFeed, this::onStartDataFeed);
registerPacketListener(DataFeedMessage.PollDataFeed, this::onPollDataFeedRequest);
this.api.server.addOnTick(this::sendDataFeedUpdate);
}
private void onStartDataFeed(GenericConnection conn, DataFeedMessageHeader header) {
StartDataFeed req = (StartDataFeed) header.message(new StartDataFeed());
if (req == null)
return;
int dataFeeds = req.dataFeedsLength();
List<DataFeed> feedList = conn.getContext().getDataFeedList();
synchronized (feedList) {
feedList.clear();
for (int i = 0; i < dataFeeds; i++) {
// Using the object api here because we
// need to copy from the buffer, anyway let's
// do it from here and send the reference to an arraylist
DataFeedConfigT config = req.dataFeeds(i).unpack();
feedList.add(new DataFeed(config));
}
}
}
private void onPollDataFeedRequest(
GenericConnection conn,
DataFeedMessageHeader messageHeader
) {
PollDataFeed req = (PollDataFeed) messageHeader.message(new PollDataFeed());
if (req == null)
return;
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
int messageOffset = this.buildDatafeed(fbb, req.config().unpack(), 0);
DataFeedMessageHeader.startDataFeedMessageHeader(fbb);
DataFeedMessageHeader.addMessage(fbb, messageOffset);
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate);
int headerOffset = DataFeedMessageHeader.endDataFeedMessageHeader(fbb);
MessageBundle.startDataFeedMsgsVector(fbb, 1);
MessageBundle.addDataFeedMsgs(fbb, headerOffset);
int datafeedMessagesOffset = fbb.endVector();
int packet = createMessage(fbb, datafeedMessagesOffset);
fbb.finish(packet);
conn.send(fbb.dataBuffer());
}
public int buildDatafeed(FlatBufferBuilder fbb, DataFeedConfigT config, int index) {
int devicesOffset = DataFeedBuilder
.createDevicesData(
fbb,
config.getDataMask(),
this.api.server.deviceManager
.getDevices()
);
// Synthetic tracker is computed tracker apparently
int trackersOffset = DataFeedBuilder
.createSyntheticTrackersData(
fbb,
config.getSyntheticTrackersMask(),
this.api.server
.getAllTrackers()
.stream()
.filter(Tracker::isComputed)
.collect(Collectors.toList())
);
var h = this.api.server.humanPoseManager;
int bonesOffset = DataFeedBuilder
.createBonesData(
fbb,
config.getBoneMask(),
h.getAllBones()
);
int stayAlignedPoseOffset = 0;
if (config.getStayAlignedPoseMask()) {
stayAlignedPoseOffset = DataFeedBuilderKotlin.INSTANCE
.createStayAlignedPose(fbb, this.api.server.humanPoseManager.skeleton);
}
int serverGuardsOffset = 0;
if (config.getServerGuardsMask()) {
serverGuardsOffset = DataFeedBuilderKotlin.INSTANCE
.createServerGuard(fbb, this.api.server.getServerGuards());
}
return DataFeedUpdate
.createDataFeedUpdate(
fbb,
devicesOffset,
trackersOffset,
bonesOffset,
stayAlignedPoseOffset,
index,
serverGuardsOffset
);
}
public void sendDataFeedUpdate() {
long currTime = System.currentTimeMillis();
this.api.getAPIServers().forEach((server) -> server.getAPIConnections().forEach((conn) -> {
FlatBufferBuilder fbb = null;
List<DataFeed> feedList = conn.getContext().getDataFeedList();
synchronized (feedList) {
int configsCount = feedList.size();
int[] data = new int[configsCount];
for (int index = 0; index < configsCount; index++) {
DataFeed feed = feedList.get(index);
Long lastTimeSent = feed.getTimeLastSent();
DataFeedConfigT configT = feed.getConfig();
if (currTime - lastTimeSent > configT.getMinimumTimeSinceLast()) {
if (fbb == null) {
// That way we create a buffer only when needed
fbb = new FlatBufferBuilder(300);
}
int messageOffset = this.buildDatafeed(fbb, configT, index);
DataFeedMessageHeader.startDataFeedMessageHeader(fbb);
DataFeedMessageHeader.addMessage(fbb, messageOffset);
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate);
data[index] = DataFeedMessageHeader.endDataFeedMessageHeader(fbb);
feed.setTimeLastSent(currTime);
int messages = MessageBundle.createDataFeedMsgsVector(fbb, data);
int packet = createMessage(fbb, messages);
fbb.finish(packet);
conn.send(fbb.dataBuffer());
}
}
}
}));
}
@Override
public void onMessage(GenericConnection conn, DataFeedMessageHeader message) {
BiConsumer<GenericConnection, DataFeedMessageHeader> consumer = this.handlers[message
.messageType()];
if (consumer != null)
consumer.accept(conn, message);
else
LogManager
.info(
"[ProtocolAPI] Unhandled Datafeed packet received id: " + message.messageType()
);
}
@Override
public int messagesCount() {
return DataFeedMessage.names.length;
}
public int createMessage(FlatBufferBuilder fbb, int datafeedMessagesOffset) {
MessageBundle.startMessageBundle(fbb);
if (datafeedMessagesOffset > -1)
MessageBundle.addDataFeedMsgs(fbb, datafeedMessagesOffset);
return MessageBundle.endMessageBundle(fbb);
}
}

View File

@@ -0,0 +1,177 @@
package dev.slimevr.protocol.datafeed
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.protocol.DataFeed
import dev.slimevr.protocol.GenericConnection
import dev.slimevr.protocol.ProtocolAPI
import dev.slimevr.protocol.ProtocolAPIServer
import dev.slimevr.protocol.ProtocolHandler
import dev.slimevr.tracking.trackers.Tracker
import io.eiren.util.logging.LogManager
import solarxr_protocol.MessageBundle
import solarxr_protocol.data_feed.DataFeedConfigT
import solarxr_protocol.data_feed.DataFeedMessage
import solarxr_protocol.data_feed.DataFeedMessageHeader
import solarxr_protocol.data_feed.DataFeedUpdate
import solarxr_protocol.data_feed.PollDataFeed
import solarxr_protocol.data_feed.StartDataFeed
import java.util.function.Consumer
import java.util.stream.Collectors
class DataFeedHandler(private val api: ProtocolAPI) : ProtocolHandler<DataFeedMessageHeader>() {
init {
registerPacketListener(DataFeedMessage.StartDataFeed, ::onStartDataFeed)
registerPacketListener(DataFeedMessage.PollDataFeed, ::onPollDataFeedRequest)
this.api.server.addOnTick { this.sendDataFeedUpdate() }
}
private fun onStartDataFeed(conn: GenericConnection, header: DataFeedMessageHeader) {
val req = header.message(StartDataFeed()) as StartDataFeed? ?: return
val dataFeeds = req.dataFeedsLength()
val feedList = conn.context.dataFeedList
synchronized(feedList) {
feedList.clear()
for (i in 0..<dataFeeds) {
// Using the object api here because we
// need to copy from the buffer, anyway let's
// do it from here and send the reference to an arraylist
val config = req.dataFeeds(i).unpack()
feedList.add(DataFeed(config))
}
}
}
private fun onPollDataFeedRequest(
conn: GenericConnection,
messageHeader: DataFeedMessageHeader,
) {
val req = messageHeader.message(PollDataFeed()) as PollDataFeed? ?: return
val fbb = FlatBufferBuilder(300)
val messageOffset = this.buildDatafeed(fbb, req.config().unpack(), 0)
DataFeedMessageHeader.startDataFeedMessageHeader(fbb)
DataFeedMessageHeader.addMessage(fbb, messageOffset)
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate)
val headerOffset = DataFeedMessageHeader.endDataFeedMessageHeader(fbb)
MessageBundle.startDataFeedMsgsVector(fbb, 1)
MessageBundle.addDataFeedMsgs(fbb, headerOffset)
val datafeedMessagesOffset = fbb.endVector()
val packet = createMessage(fbb, datafeedMessagesOffset)
fbb.finish(packet)
conn.send(fbb.dataBuffer())
}
fun buildDatafeed(fbb: FlatBufferBuilder, config: DataFeedConfigT, index: Int): Int {
val devicesOffset = createDevicesData(
fbb,
config.dataMask,
this.api.server.deviceManager
.devices,
)
// Synthetic tracker is computed tracker apparently
val trackersOffset = createSyntheticTrackersData(
fbb,
config.syntheticTrackersMask,
this.api.server
.allTrackers
.stream()
.filter(Tracker::isComputed)
.collect(Collectors.toList()),
)
val h = this.api.server.humanPoseManager
val bonesOffset =
createBonesData(
fbb,
config.boneMask,
h.allBones.toMutableList(),
)
var stayAlignedPoseOffset = 0
if (config.stayAlignedPoseMask) {
stayAlignedPoseOffset = createStayAlignedPose(fbb, this.api.server.humanPoseManager.skeleton)
}
var serverGuardsOffset = 0
if (config.serverGuardsMask) {
serverGuardsOffset = createServerGuard(fbb, this.api.server.serverGuards)
}
return DataFeedUpdate
.createDataFeedUpdate(
fbb,
devicesOffset,
trackersOffset,
bonesOffset,
stayAlignedPoseOffset,
index,
serverGuardsOffset,
)
}
fun sendDataFeedUpdate() {
val currTime = System.currentTimeMillis()
this.api.apiServers.forEach(
Consumer { server: ProtocolAPIServer ->
server.apiConnections.forEach { conn: GenericConnection ->
var fbb: FlatBufferBuilder? = null
val feedList = conn.context.dataFeedList
synchronized(feedList) {
val configsCount = feedList.size
val data = IntArray(configsCount)
for (index in 0..<configsCount) {
val feed = feedList[index]
val lastTimeSent = feed.timeLastSent
val configT = feed.config
if (currTime - lastTimeSent > configT.minimumTimeSinceLast) {
if (fbb == null) {
// That way we create a buffer only when needed
fbb = FlatBufferBuilder(300)
}
val messageOffset = this.buildDatafeed(fbb, configT, index)
DataFeedMessageHeader.startDataFeedMessageHeader(fbb)
DataFeedMessageHeader.addMessage(fbb, messageOffset)
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate)
data[index] = DataFeedMessageHeader.endDataFeedMessageHeader(fbb)
feed.timeLastSent = currTime
val messages = MessageBundle.createDataFeedMsgsVector(fbb, data)
val packet = createMessage(fbb, messages)
fbb.finish(packet)
conn.send(fbb.dataBuffer())
}
}
}
}
},
)
}
override fun onMessage(conn: GenericConnection, message: DataFeedMessageHeader) {
val consumer = this.handlers[message.messageType().toInt()]
if (consumer != null) {
consumer.accept(conn, message)
} else {
LogManager
.info(
"[ProtocolAPI] Unhandled Datafeed packet received id: " + message.messageType(),
)
}
}
override fun messagesCount(): Int = DataFeedMessage.names.size
fun createMessage(fbb: FlatBufferBuilder, datafeedMessagesOffset: Int): Int {
MessageBundle.startMessageBundle(fbb)
if (datafeedMessagesOffset > -1) MessageBundle.addDataFeedMsgs(fbb, datafeedMessagesOffset)
return MessageBundle.endMessageBundle(fbb)
}
}

View File

@@ -1,45 +0,0 @@
package dev.slimevr.protocol.pubsub;
import solarxr_protocol.pub_sub.TopicIdT;
import java.util.Objects;
// This class is so the HashMap referencing the TopicId as key works
// it needs a unique hashcode based on the topicId and also an equals function
// because equals hashcode does not mean equals strings
public class HashedTopicId {
private final TopicIdT inner;
private final int hashcode;
public HashedTopicId(TopicIdT topicIdT) {
this.inner = topicIdT;
this.hashcode = (inner.getAppName()
+ "."
+ inner.getOrganization()
+ "."
+ inner.getTopic()).hashCode();
}
public TopicIdT getInner() {
return inner;
}
@Override
public int hashCode() {
return hashcode;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
HashedTopicId that = (HashedTopicId) o;
return Objects.equals(inner.getOrganization(), that.getInner().getOrganization())
&& Objects.equals(inner.getAppName(), that.getInner().getAppName())
&& Objects.equals(inner.getTopic(), that.getInner().getTopic());
}
}

View File

@@ -0,0 +1,29 @@
package dev.slimevr.protocol.pubsub
import solarxr_protocol.pub_sub.TopicIdT
// This class is so the HashMap referencing the TopicId as key works
// it needs a unique hashcode based on the topicId and also an equals function
// because equals hashcode does not mean equals strings
class HashedTopicId(val inner: TopicIdT) {
private val hashcode: Int = (
(
inner.appName +
"." +
inner.organization +
"." +
inner.topic
)
).hashCode()
override fun hashCode(): Int = hashcode
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
val that = other as HashedTopicId
return inner.organization == that.inner.organization &&
inner.appName == that.inner.appName &&
inner.topic == that.inner.topic
}
}

View File

@@ -1,185 +0,0 @@
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.*;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
public class PubSubHandler extends ProtocolHandler<PubSubHeader> {
private final ProtocolAPI api;
// Two ways maps for faster reading when handling lots of packets
public HashMap<HashedTopicId, Integer> topicsHandle = new HashMap<>();
public HashMap<Integer, HashedTopicId> handleTopics = new HashMap<>();
public AtomicInteger nextLocalHandle = new AtomicInteger();
public PubSubHandler(ProtocolAPI api) {
super();
this.api = api;
registerPacketListener(PubSubUnion.SubscriptionRequest, this::onSubscriptionRequest);
registerPacketListener(PubSubUnion.TopicHandleRequest, this::onTopicHandleRequest);
registerPacketListener(PubSubUnion.Message, this::onTopicMessage);
}
private int getTopicHandle(TopicIdT topicIdT) {
HashedTopicId hashedTopicId = new HashedTopicId(topicIdT);
Integer handleT = topicsHandle.get(hashedTopicId);
// if no handle exists for this topic id we create one and return it
// anyway
if (handleT == null) {
handleT = nextLocalHandle.incrementAndGet();
topicsHandle.put(hashedTopicId, handleT);
handleTopics.put(handleT, hashedTopicId);
}
return handleT;
}
public void onSubscriptionRequest(GenericConnection conn, PubSubHeader messageHeader) {
SubscriptionRequest req = (SubscriptionRequest) messageHeader.u(new SubscriptionRequest());
if (req == null)
return;
int subHandle = -1;
if (req.topicType() == Topic.TopicHandle) {
TopicHandle handle = (TopicHandle) req.topic(new TopicHandle());
if (handle != null && handleTopics.containsKey(handle.id()))
subHandle = handle.id();
} else if (req.topicType() == Topic.TopicId) {
TopicId topicId = (TopicId) req.topic(new TopicId());
if (topicId != null)
subHandle = getTopicHandle(topicId.unpack());
}
assert subHandle != -1;
final int finalSubHandle = subHandle;
Optional<Integer> first = conn
.getContext()
.getSubscribedTopics()
.stream()
.filter((handle) -> handle == finalSubHandle)
.findFirst();
if (!first.isPresent()) {
conn.getContext().getSubscribedTopics().add(finalSubHandle);
}
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
int topicIdOffset = TopicId.pack(fbb, handleTopics.get(finalSubHandle).getInner());
int topicHandleOffset = TopicHandle.createTopicHandle(fbb, finalSubHandle);
int outbound = createMessage(
fbb,
PubSubUnion.TopicMapping,
TopicMapping.createTopicMapping(fbb, topicIdOffset, topicHandleOffset)
);
fbb.finish(outbound);
conn.send(fbb.dataBuffer());
}
public void onTopicHandleRequest(GenericConnection conn, PubSubHeader messageHeader) {
TopicHandleRequest req = (TopicHandleRequest) messageHeader.u(new TopicHandleRequest());
if (req == null)
return;
TopicHandleRequestT topicRequest = req.unpack();
int handle = getTopicHandle(topicRequest.getId());
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
int topicIdOffset = TopicId.pack(fbb, topicRequest.getId());
int topicHandleOffset = TopicHandle.createTopicHandle(fbb, handle);
int outbound = createMessage(
fbb,
PubSubUnion.TopicMapping,
TopicMapping.createTopicMapping(fbb, topicIdOffset, topicHandleOffset)
);
fbb.finish(outbound);
conn.send(fbb.dataBuffer());
}
public void onTopicMessage(GenericConnection c, PubSubHeader messageHeader) {
Message req = (Message) messageHeader.u(new Message());
if (req == null)
return;
MessageT messageT = req.unpack();
int subHandle = 1;
if (messageT.getTopic().getType() == Topic.TopicHandle) {
subHandle = messageT.getTopic().asTopicHandle().getId();
} else if (messageT.getTopic().getType() == Topic.TopicId) {
subHandle = getTopicHandle(messageT.getTopic().asTopicId());
}
assert subHandle != -1;
int finalSubHandle = subHandle;
this.api.getAPIServers().forEach((server) -> {
server.getAPIConnections().forEach((conn) -> {
// Make sure that we are not sending a message to ourselves
// And check that the receiver has subscribed to the topic
if (
!conn.getConnectionId().equals(c.getConnectionId())
&& conn.getContext().getSubscribedTopics().contains(finalSubHandle)
) {
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
int outbound = createMessage(
fbb,
PubSubUnion.Message,
Message.pack(fbb, messageT)
);
fbb.finish(outbound);
conn.send(fbb.dataBuffer());
}
});
});
}
@Override
public void onMessage(GenericConnection conn, PubSubHeader message) {
BiConsumer<GenericConnection, PubSubHeader> consumer = this.handlers[message.uType()];
if (consumer != null)
consumer.accept(conn, message);
else
LogManager
.info("[ProtocolAPI] Unhandled PubSub packet received id: " + message.uType());
}
@Override
public int messagesCount() {
return PubSubUnion.names.length;
}
public int createMessage(FlatBufferBuilder fbb, byte messageType, int messageOffset) {
int[] data = new int[1];
data[0] = PubSubHeader.createPubSubHeader(fbb, messageType, messageOffset);
int messages = MessageBundle.createPubSubMsgsVector(fbb, data);
MessageBundle.startMessageBundle(fbb);
MessageBundle.addPubSubMsgs(fbb, messages);
return MessageBundle.endMessageBundle(fbb);
}
}

View File

@@ -0,0 +1,170 @@
package dev.slimevr.protocol.pubsub
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.protocol.GenericConnection
import dev.slimevr.protocol.ProtocolAPI
import dev.slimevr.protocol.ProtocolAPIServer
import dev.slimevr.protocol.ProtocolHandler
import io.eiren.util.logging.LogManager
import solarxr_protocol.MessageBundle
import solarxr_protocol.pub_sub.Message
import solarxr_protocol.pub_sub.PubSubHeader
import solarxr_protocol.pub_sub.PubSubUnion
import solarxr_protocol.pub_sub.SubscriptionRequest
import solarxr_protocol.pub_sub.Topic
import solarxr_protocol.pub_sub.TopicHandle
import solarxr_protocol.pub_sub.TopicHandleRequest
import solarxr_protocol.pub_sub.TopicId
import solarxr_protocol.pub_sub.TopicIdT
import solarxr_protocol.pub_sub.TopicMapping
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
class PubSubHandler(private val api: ProtocolAPI) : ProtocolHandler<PubSubHeader>() {
// Two ways maps for faster reading when handling lots of packets
var topicsHandle: HashMap<HashedTopicId, Int> = HashMap()
var handleTopics: HashMap<Int, HashedTopicId> = HashMap()
var nextLocalHandle: AtomicInteger = AtomicInteger()
init {
registerPacketListener(PubSubUnion.SubscriptionRequest, ::onSubscriptionRequest)
registerPacketListener(PubSubUnion.TopicHandleRequest, ::onTopicHandleRequest)
registerPacketListener(PubSubUnion.Message, ::onTopicMessage)
}
private fun getTopicHandle(topicIdT: TopicIdT): Int {
val hashedTopicId = HashedTopicId(topicIdT)
var handleT = topicsHandle.get(hashedTopicId)
// if no handle exists for this topic id we create one and return it
// anyway
if (handleT == null) {
handleT = nextLocalHandle.incrementAndGet()
topicsHandle[hashedTopicId] = handleT
handleTopics[handleT] = hashedTopicId
}
return handleT
}
fun onSubscriptionRequest(conn: GenericConnection, messageHeader: PubSubHeader) {
val req =
messageHeader.u(SubscriptionRequest()) as SubscriptionRequest? ?: return
var subHandle = -1
if (req.topicType() == Topic.TopicHandle) {
val handle = req.topic(TopicHandle()) as TopicHandle?
if (handle != null && handleTopics.containsKey(handle.id())) subHandle = handle.id()
} else if (req.topicType() == Topic.TopicId) {
val topicId = req.topic(TopicId()) as TopicId?
if (topicId != null) subHandle = getTopicHandle(topicId.unpack())
}
assert(subHandle != -1)
val finalSubHandle = subHandle
val first = conn
.context
.subscribedTopics
.stream()
.filter { handle: Int -> handle == finalSubHandle }
.findFirst()
if (!first.isPresent) {
conn.context.subscribedTopics.add(finalSubHandle)
}
val fbb = FlatBufferBuilder(32)
val topicIdOffset = TopicId.pack(fbb, handleTopics.get(finalSubHandle)!!.inner)
val topicHandleOffset = TopicHandle.createTopicHandle(fbb, finalSubHandle)
val outbound = createMessage(
fbb,
PubSubUnion.TopicMapping,
TopicMapping.createTopicMapping(fbb, topicIdOffset, topicHandleOffset),
)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
fun onTopicHandleRequest(conn: GenericConnection, messageHeader: PubSubHeader) {
val req = messageHeader.u(TopicHandleRequest()) as TopicHandleRequest? ?: return
val topicRequest = req.unpack()
val handle = getTopicHandle(topicRequest.id)
val fbb = FlatBufferBuilder(32)
val topicIdOffset = TopicId.pack(fbb, topicRequest.id)
val topicHandleOffset = TopicHandle.createTopicHandle(fbb, handle)
val outbound = createMessage(
fbb,
PubSubUnion.TopicMapping,
TopicMapping.createTopicMapping(fbb, topicIdOffset, topicHandleOffset),
)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
fun onTopicMessage(c: GenericConnection, messageHeader: PubSubHeader) {
val req = messageHeader.u(Message()) as Message? ?: return
val messageT = req.unpack()
var subHandle = 1
if (messageT.topic.type == Topic.TopicHandle) {
subHandle = messageT.topic.asTopicHandle().id
} else if (messageT.topic.type == Topic.TopicId) {
subHandle = getTopicHandle(messageT.topic.asTopicId())
}
assert(subHandle != -1)
val finalSubHandle = subHandle
this.api.apiServers.forEach(
Consumer { server: ProtocolAPIServer ->
server.apiConnections.forEach { conn: GenericConnection ->
// Make sure that we are not sending a message to ourselves
// And check that the receiver has subscribed to the topic
if (conn.connectionId != c.connectionId &&
conn.context.subscribedTopics
.contains(finalSubHandle)
) {
val fbb = FlatBufferBuilder(32)
val outbound = createMessage(
fbb,
PubSubUnion.Message,
Message.pack(fbb, messageT),
)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
}
},
)
}
override fun onMessage(conn: GenericConnection, message: PubSubHeader) {
val consumer = this.handlers[message.uType().toInt()]
if (consumer != null) {
consumer.accept(conn, message)
} else {
LogManager
.info("[ProtocolAPI] Unhandled PubSub packet received id: " + message.uType())
}
}
override fun messagesCount(): Int = PubSubUnion.names.size
fun createMessage(fbb: FlatBufferBuilder, messageType: Byte, messageOffset: Int): Int {
val data = IntArray(1)
data[0] = PubSubHeader.createPubSubHeader(fbb, messageType, messageOffset)
val messages = MessageBundle.createPubSubMsgsVector(fbb, data)
MessageBundle.startMessageBundle(fbb)
MessageBundle.addPubSubMsgs(fbb, messages)
return MessageBundle.endMessageBundle(fbb)
}
}

View File

@@ -1,28 +0,0 @@
package dev.slimevr.protocol.rpc;
import com.google.flatbuffers.FlatBufferBuilder;
import dev.slimevr.tracking.processor.HumanPoseManager;
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets;
import solarxr_protocol.rpc.SkeletonConfigResponse;
import solarxr_protocol.rpc.SkeletonPart;
public class RPCBuilder {
public static int createSkeletonConfig(
FlatBufferBuilder fbb,
HumanPoseManager humanPoseManager
) {
int[] partsOffsets = new int[SkeletonConfigOffsets.values().length];
for (int index = 0; index < SkeletonConfigOffsets.values().length; index++) {
SkeletonConfigOffsets val = SkeletonConfigOffsets.values[index];
int part = SkeletonPart
.createSkeletonPart(fbb, val.id, humanPoseManager.getOffset(val));
partsOffsets[index] = part;
}
int parts = SkeletonConfigResponse.createSkeletonPartsVector(fbb, partsOffsets);
return SkeletonConfigResponse.createSkeletonConfigResponse(fbb, parts, 0);
}
}

View File

@@ -6,15 +6,15 @@ import dev.slimevr.config.config
import dev.slimevr.protocol.GenericConnection
import dev.slimevr.protocol.ProtocolAPI
import dev.slimevr.protocol.ProtocolHandler
import dev.slimevr.protocol.datafeed.DataFeedBuilder
import dev.slimevr.protocol.datafeed.createTrackerId
import dev.slimevr.protocol.rpc.autobone.RPCAutoBoneHandler
import dev.slimevr.protocol.rpc.firmware.RPCFirmwareUpdateHandler
import dev.slimevr.protocol.rpc.games.vrchat.RPCVRChatHandler
import dev.slimevr.protocol.rpc.reset.RPCResetHandler
import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler
import dev.slimevr.protocol.rpc.settings.RPCSettingsBuilder
import dev.slimevr.protocol.rpc.settings.RPCSettingsHandler
import dev.slimevr.protocol.rpc.settings.createSettingsResponse
import dev.slimevr.protocol.rpc.setup.RPCHandshakeHandler
import dev.slimevr.protocol.rpc.setup.RPCTapSetupHandler
import dev.slimevr.protocol.rpc.setup.RPCUtil.getLocalIp
@@ -460,7 +460,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
}
val tracker = api.server.getTrackerById(req.trackerId().unpack()) ?: return
val trackerId = DataFeedBuilder.createTrackerId(fbb, tracker)
val trackerId = createTrackerId(fbb, tracker)
val response = MagToggleResponse.createMagToggleResponse(
fbb,
trackerId,
@@ -499,7 +499,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
// Don't apply magnetometer setting if use magnetometer global setting is not enabled
if (!api.server.configManager.vrConfig.server.useMagnetometerOnAllTrackers) {
val fbb = FlatBufferBuilder(32)
val trackerId = DataFeedBuilder.createTrackerId(fbb, tracker)
val trackerId = createTrackerId(fbb, tracker)
val response = MagToggleResponse.createMagToggleResponse(
fbb,
trackerId,
@@ -516,7 +516,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
}
val fbb = FlatBufferBuilder(32)
val trackerId = DataFeedBuilder.createTrackerId(fbb, tracker)
val trackerId = createTrackerId(fbb, tracker)
val response = MagToggleResponse.createMagToggleResponse(
fbb,
trackerId,
@@ -607,7 +607,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
fun sendSettingsChangedResponse(conn: GenericConnection, messageHeader: RpcMessageHeader?) {
val fbb = FlatBufferBuilder(32)
val settings = RPCSettingsBuilder.createSettingsResponse(fbb, api.server)
val settings = createSettingsResponse(fbb, api.server)
val outbound = createRPCMessage(fbb, RpcMessage.SettingsResponse, settings, messageHeader)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())

View File

@@ -50,8 +50,8 @@ class RPCAutoBoneHandler(
) {
val req = messageHeader
.message(AutoBoneProcessRequest()) as AutoBoneProcessRequest
if (conn.context.useAutoBone()) return
conn.context.setUseAutoBone(true)
if (conn.context.useAutoBone) return
conn.context.useAutoBone = true
api.server
.autoBoneHandler
.startProcessByType(getById(req.processType()))
@@ -67,7 +67,7 @@ class RPCAutoBoneHandler(
success: Boolean,
) {
forAllListeners { conn ->
if (!conn.context.useAutoBone()) {
if (!conn.context.useAutoBone) {
return@forAllListeners
}
@@ -95,7 +95,7 @@ class RPCAutoBoneHandler(
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
if (completed) {
conn.context.setUseAutoBone(false)
conn.context.useAutoBone = false
}
}
}
@@ -106,7 +106,7 @@ class RPCAutoBoneHandler(
override fun onAutoBoneEpoch(epoch: Epoch) {
forAllListeners { conn ->
if (!conn.context.useAutoBone()) {
if (!conn.context.useAutoBone) {
return@forAllListeners
}

View File

@@ -39,24 +39,39 @@ class RPCResetHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) : ResetL
}
if (req.resetType() == ResetType.Yaw) {
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (resetsConfig.yawResetDelay * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (resetsConfig.yawResetDelay * 1000).toLong(), bodyParts.toList())
resetsConfig.yawResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (delay * 1000).toLong())
} else {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
}
}
if (req.resetType() == ResetType.Full) {
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (resetsConfig.fullResetDelay * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (resetsConfig.fullResetDelay * 1000).toLong(), bodyParts.toList())
resetsConfig.fullResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (delay * 1000).toLong())
} else {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
}
}
if (req.resetType() == ResetType.Mounting) {
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (resetsConfig.mountingResetDelay * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (resetsConfig.mountingResetDelay * 1000).toLong(), bodyParts.toList())
resetsConfig.mountingResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (delay * 1000).toLong())
} else {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
}
}
}
@@ -108,11 +123,11 @@ class RPCResetHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) : ResetL
fun forAllListeners(action: Consumer<in GenericConnection?>?) {
this.api
.getAPIServers()
.apiServers
.forEach(
Consumer { server: ProtocolAPIServer? ->
server!!
.getAPIConnections()
Consumer { server: ProtocolAPIServer ->
server
.apiConnections
.forEach(action)
},
)

View File

@@ -1,87 +0,0 @@
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 dev.slimevr.serial.SerialPort;
import solarxr_protocol.rpc.*;
import java.util.function.Consumer;
public class RPCProvisioningHandler implements ProvisioningListener {
public RPCHandler rpcHandler;
public ProtocolAPI api;
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.provisioningHandler.addListener(this);
}
public void onStartWifiProvisioningRequest(
GenericConnection conn,
RpcMessageHeader messageHeader
) {
StartWifiProvisioningRequest req = (StartWifiProvisioningRequest) messageHeader
.message(new StartWifiProvisioningRequest());
if (req == null)
return;
this.api.server.provisioningHandler.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.provisioningHandler.stop();
}
@Override
public void onProvisioningStatusChange(ProvisioningStatus status, SerialPort port) {
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
WifiProvisioningStatusResponse.startWifiProvisioningStatusResponse(fbb);
WifiProvisioningStatusResponse.addStatus(fbb, status.id);
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)
);
}
}

View File

@@ -0,0 +1,68 @@
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.ProtocolAPIServer
import dev.slimevr.protocol.rpc.RPCHandler
import dev.slimevr.serial.ProvisioningListener
import dev.slimevr.serial.ProvisioningStatus
import dev.slimevr.serial.SerialPort
import solarxr_protocol.rpc.*
import java.util.function.Consumer
class RPCProvisioningHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) : ProvisioningListener {
init {
rpcHandler.registerPacketListener(RpcMessage.StartWifiProvisioningRequest, ::onStartWifiProvisioningRequest)
rpcHandler.registerPacketListener(RpcMessage.StopWifiProvisioningRequest, ::onStopWifiProvisioningRequest)
this.api.server.provisioningHandler.addListener(this)
}
fun onStartWifiProvisioningRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(StartWifiProvisioningRequest()) as StartWifiProvisioningRequest?
if (req == null) return
this.api.server.provisioningHandler.start(req.ssid(), req.password(), req.port())
conn.context.useProvisioning = true
}
fun onStopWifiProvisioningRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(StopWifiProvisioningRequest()) as StopWifiProvisioningRequest?
if (req == null) return
conn.context.useProvisioning = false
this.api.server.provisioningHandler.stop()
}
override fun onProvisioningStatusChange(status: ProvisioningStatus, port: SerialPort?) {
val fbb = FlatBufferBuilder(32)
WifiProvisioningStatusResponse.startWifiProvisioningStatusResponse(fbb)
WifiProvisioningStatusResponse.addStatus(fbb, status.id)
val update = WifiProvisioningStatusResponse.endWifiProvisioningStatusResponse(fbb)
val outbound = rpcHandler
.createRPCMessage(fbb, RpcMessage.WifiProvisioningStatusResponse, update)
fbb.finish(outbound)
this.forAllListeners(Consumer { conn: GenericConnection -> conn.send(fbb.dataBuffer()) })
}
private fun forAllListeners(action: Consumer<in GenericConnection?>?) {
this.api
.apiServers
.forEach(
Consumer { server: ProtocolAPIServer ->
server
.apiConnections
.filter { conn: GenericConnection -> conn.context.useProvisioning }
.forEach(action)
},
)
}
}

View File

@@ -1,303 +0,0 @@
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.SerialListener;
import dev.slimevr.serial.SerialPort;
import io.eiren.util.logging.LogManager;
import org.jetbrains.annotations.NotNull;
import solarxr_protocol.rpc.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
public class RPCSerialHandler implements SerialListener {
public RPCHandler rpcHandler;
public ProtocolAPI api;
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.SerialTrackerGetWifiScanRequest,
this::onSerialTrackerGetWifiScanRequest
);
rpcHandler
.registerPacketListener(
RpcMessage.SerialTrackerCustomCommandRequest,
this::onSerialTrackerCustomCommandRequest
);
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.serialHandler.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, boolean server) {
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 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) {
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.serialHandler.rebootRequest();
}
public void onSerialTrackerGetInfoRequest(
GenericConnection conn,
RpcMessageHeader messageHeader
) {
SerialTrackerGetInfoRequest req = (SerialTrackerGetInfoRequest) messageHeader
.message(new SerialTrackerGetInfoRequest());
if (req == null)
return;
this.api.server.serialHandler.infoRequest();
}
public void onSerialTrackerFactoryResetRequest(
GenericConnection conn,
RpcMessageHeader messageHeader
) {
SerialTrackerFactoryResetRequest req = (SerialTrackerFactoryResetRequest) messageHeader
.message(new SerialTrackerFactoryResetRequest());
if (req == null)
return;
this.api.server.serialHandler.factoryResetRequest();
}
public void onSerialTrackerGetWifiScanRequest(
GenericConnection conn,
RpcMessageHeader messageHeader
) {
SerialTrackerGetWifiScanRequest req = (SerialTrackerGetWifiScanRequest) messageHeader
.message(new SerialTrackerGetWifiScanRequest());
if (req == null)
return;
this.api.server.serialHandler.wifiScanRequest();
}
public void onSerialTrackerCustomCommandRequest(
GenericConnection conn,
RpcMessageHeader messageHeader
) {
SerialTrackerCustomCommandRequest req = (SerialTrackerCustomCommandRequest) messageHeader
.message(new SerialTrackerCustomCommandRequest());
if (req == null || req.command() == null)
return;
this.api.server.serialHandler.customCommandRequest(Objects.requireNonNull(req.command()));
}
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.serialHandler.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.serialHandler.isConnected()
)
return;
this.api.server.serialHandler.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);
this.api.server.queueTask(() -> {
try {
this.api.server.serialHandler.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.serialHandler.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.serialHandler.closeSerial();
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
SerialUpdateResponse.startSerialUpdateResponse(fbb);
SerialUpdateResponse.addClosed(fbb, !this.api.server.serialHandler.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)
);
}
@Override
public void onSerialDeviceDeleted(@NotNull SerialPort port) {
}
}

View File

@@ -0,0 +1,263 @@
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.ProtocolAPIServer
import dev.slimevr.protocol.rpc.RPCHandler
import dev.slimevr.serial.SerialListener
import dev.slimevr.serial.SerialPort
import io.eiren.util.logging.LogManager
import solarxr_protocol.rpc.*
import java.util.*
import java.util.function.Consumer
class RPCSerialHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) : SerialListener {
init {
rpcHandler.registerPacketListener(RpcMessage.SerialTrackerRebootRequest, ::onSerialTrackerRebootRequest)
rpcHandler.registerPacketListener(RpcMessage.SerialTrackerGetInfoRequest, ::onSerialTrackerGetInfoRequest)
rpcHandler.registerPacketListener(RpcMessage.SerialTrackerFactoryResetRequest, ::onSerialTrackerFactoryResetRequest)
rpcHandler.registerPacketListener(RpcMessage.SerialTrackerGetWifiScanRequest, ::onSerialTrackerGetWifiScanRequest)
rpcHandler.registerPacketListener(RpcMessage.SerialTrackerCustomCommandRequest, ::onSerialTrackerCustomCommandRequest)
rpcHandler.registerPacketListener(RpcMessage.SetWifiRequest, ::onSetWifiRequest)
rpcHandler.registerPacketListener(RpcMessage.OpenSerialRequest, ::onOpenSerialRequest)
rpcHandler.registerPacketListener(RpcMessage.CloseSerialRequest, ::onCloseSerialRequest)
rpcHandler.registerPacketListener(RpcMessage.SerialDevicesRequest, ::onRequestSerialDevices)
this.api.server.serialHandler.addListener(this)
}
override fun onSerialDisconnected() {
val fbb = FlatBufferBuilder(32)
SerialUpdateResponse.startSerialUpdateResponse(fbb)
SerialUpdateResponse.addClosed(fbb, true)
val update = SerialUpdateResponse.endSerialUpdateResponse(fbb)
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update)
fbb.finish(outbound)
this.forAllListeners(
Consumer { conn: GenericConnection ->
conn.send(fbb.dataBuffer())
conn.context.useSerial = false
},
)
}
override fun onSerialLog(str: String, server: Boolean) {
val fbb = FlatBufferBuilder(32)
val logOffset = fbb.createString(str)
SerialUpdateResponse.startSerialUpdateResponse(fbb)
SerialUpdateResponse.addLog(fbb, logOffset)
val update = SerialUpdateResponse.endSerialUpdateResponse(fbb)
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update)
fbb.finish(outbound)
this.forAllListeners(
Consumer { conn: GenericConnection ->
conn.send(fbb.dataBuffer())
},
)
}
override fun onNewSerialDevice(port: SerialPort) {
val fbb = FlatBufferBuilder(32)
val portOffset = fbb.createString(port.portLocation)
val nameOffset = fbb.createString(port.descriptivePortName)
val deviceOffset = SerialDevice.createSerialDevice(fbb, portOffset, nameOffset)
val newSerialOffset = NewSerialDeviceResponse
.createNewSerialDeviceResponse(fbb, deviceOffset)
val outbound = rpcHandler
.createRPCMessage(fbb, RpcMessage.NewSerialDeviceResponse, newSerialOffset)
fbb.finish(outbound)
this.api
.apiServers
.forEach(
Consumer { server: ProtocolAPIServer ->
server
.apiConnections
.forEach { conn: GenericConnection ->
conn.send(fbb.dataBuffer())
}
},
)
}
override fun onSerialConnected(port: SerialPort) {
val fbb = FlatBufferBuilder(32)
SerialUpdateResponse.startSerialUpdateResponse(fbb)
SerialUpdateResponse.addClosed(fbb, false)
val update = SerialUpdateResponse.endSerialUpdateResponse(fbb)
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update)
fbb.finish(outbound)
this.forAllListeners(
Consumer { conn: GenericConnection ->
conn.send(fbb.dataBuffer())
},
)
}
fun onSerialTrackerRebootRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(SerialTrackerRebootRequest()) as SerialTrackerRebootRequest?
if (req == null) return
this.api.server.serialHandler.rebootRequest()
}
fun onSerialTrackerGetInfoRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(SerialTrackerGetInfoRequest()) as SerialTrackerGetInfoRequest?
if (req == null) return
this.api.server.serialHandler.infoRequest()
}
fun onSerialTrackerFactoryResetRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(SerialTrackerFactoryResetRequest()) as SerialTrackerFactoryResetRequest?
if (req == null) return
this.api.server.serialHandler.factoryResetRequest()
}
fun onSerialTrackerGetWifiScanRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(SerialTrackerGetWifiScanRequest()) as SerialTrackerGetWifiScanRequest?
if (req == null) return
this.api.server.serialHandler.wifiScanRequest()
}
fun onSerialTrackerCustomCommandRequest(
conn: GenericConnection,
messageHeader: RpcMessageHeader,
) {
val req = messageHeader
.message(SerialTrackerCustomCommandRequest()) as SerialTrackerCustomCommandRequest?
if (req == null || req.command() == null) return
this.api.server.serialHandler.customCommandRequest(Objects.requireNonNull(req.command()))
}
private fun onRequestSerialDevices(conn: GenericConnection, messageHeader: RpcMessageHeader) {
val req = messageHeader
.message(SerialDevicesRequest()) as SerialDevicesRequest?
if (req == null) return
val fbb = FlatBufferBuilder(32)
val devicesOffsets: MutableList<Int> = ArrayList()
try {
this.api.server.serialHandler.knownPorts.forEach { port: SerialPort ->
val portOffset = fbb.createString(port.portLocation)
val nameOffset = fbb.createString(port.descriptivePortName)
devicesOffsets.add(SerialDevice.createSerialDevice(fbb, portOffset, nameOffset))
}
} catch (e: Throwable) {
LogManager.severe("Using serial ports is not supported on this platform", e)
}
SerialDevicesResponse.startDevicesVector(fbb, devicesOffsets.size)
devicesOffsets.forEach(Consumer { offset: Int -> SerialDevicesResponse.addDevices(fbb, offset) })
val devices = fbb.endVector()
val serialDeviceOffsets = SerialDevicesResponse.createSerialDevicesResponse(fbb, devices)
val outbound = rpcHandler
.createRPCMessage(fbb, RpcMessage.SerialDevicesResponse, serialDeviceOffsets)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
fun onSetWifiRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
val req = messageHeader.message(SetWifiRequest()) as SetWifiRequest? ?: return
if (req.password() == null || req.ssid() == null || !this.api.server.serialHandler.isConnected) {
return
}
this.api.server.serialHandler.setWifi(req.ssid(), req.password())
}
fun onOpenSerialRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
val req =
messageHeader.message(OpenSerialRequest()) as OpenSerialRequest? ?: return
conn.context.useSerial = true
this.api.server.queueTask {
try {
this.api.server.serialHandler.openSerial(req.port(), req.auto())
} catch (e: Exception) {
LogManager.severe("Unable to open serial port", e)
} catch (e: Throwable) {
LogManager.severe(
"Using serial ports is not supported on this platform",
e,
)
}
val fbb = FlatBufferBuilder(32)
SerialUpdateResponse.startSerialUpdateResponse(fbb)
SerialUpdateResponse.addClosed(
fbb,
!this.api.server.serialHandler.isConnected,
)
val update = SerialUpdateResponse.endSerialUpdateResponse(fbb)
val outbound = rpcHandler
.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
}
fun onCloseSerialRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
val req = messageHeader
.message(CloseSerialRequest()) as CloseSerialRequest?
if (req == null) return
conn.context.useSerial = false
this.api.server.serialHandler.closeSerial()
val fbb = FlatBufferBuilder(32)
SerialUpdateResponse.startSerialUpdateResponse(fbb)
SerialUpdateResponse.addClosed(fbb, !this.api.server.serialHandler.isConnected)
val update = SerialUpdateResponse.endSerialUpdateResponse(fbb)
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SerialUpdateResponse, update)
fbb.finish(outbound)
conn.send(fbb.dataBuffer())
}
fun forAllListeners(action: Consumer<in GenericConnection?>?) {
this.api
.apiServers
.forEach(
Consumer { server: ProtocolAPIServer ->
server
.apiConnections
.filter { conn: GenericConnection -> conn.context.useSerial }
.forEach(action)
},
)
}
override fun onSerialDeviceDeleted(port: SerialPort) {
}
}

View File

@@ -1,423 +0,0 @@
package dev.slimevr.protocol.rpc.settings;
import com.google.flatbuffers.FlatBufferBuilder;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.ISteamVRBridge;
import dev.slimevr.config.*;
import dev.slimevr.filtering.TrackerFilters;
import dev.slimevr.tracking.processor.HumanPoseManager;
import dev.slimevr.tracking.processor.config.SkeletonConfigToggles;
import dev.slimevr.tracking.processor.config.SkeletonConfigValues;
import dev.slimevr.tracking.trackers.TrackerRole;
import solarxr_protocol.rpc.*;
import solarxr_protocol.rpc.settings.*;
public class RPCSettingsBuilder {
public static int createOSCRouterSettings(
FlatBufferBuilder fbb,
OSCConfig config
) {
int addressStringOffset = fbb.createString(config.getAddress());
int oscSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.getEnabled(),
config.getPortIn(),
config.getPortOut(),
addressStringOffset
);
OSCRouterSettings.startOSCRouterSettings(fbb);
OSCRouterSettings.addOscSettings(fbb, oscSettingOffset);
return OSCRouterSettings.endOSCRouterSettings(fbb);
}
public static int createVRCOSCSettings(
FlatBufferBuilder fbb,
VRCOSCConfig config
) {
int addressStringOffset = fbb.createString(config.getAddress());
int generalSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.getEnabled(),
config.getPortIn(),
config.getPortOut(),
addressStringOffset
);
int oscSettingOffset = 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.RIGHT_KNEE, false),
config.getOSCTrackerRole(TrackerRole.LEFT_FOOT, false)
&& config.getOSCTrackerRole(TrackerRole.RIGHT_FOOT, false),
config.getOSCTrackerRole(TrackerRole.LEFT_ELBOW, false)
&& config.getOSCTrackerRole(TrackerRole.RIGHT_ELBOW, false),
config.getOSCTrackerRole(TrackerRole.LEFT_HAND, false)
&& config.getOSCTrackerRole(TrackerRole.RIGHT_HAND, false)
);
VRCOSCSettings.startVRCOSCSettings(fbb);
VRCOSCSettings.addOscSettings(fbb, generalSettingOffset);
VRCOSCSettings.addTrackers(fbb, oscSettingOffset);
VRCOSCSettings.addOscqueryEnabled(fbb, config.getOscqueryEnabled());
return VRCOSCSettings.endVRCOSCSettings(fbb);
}
public static int createVMCOSCSettings(
FlatBufferBuilder fbb,
VMCConfig config
) {
int addressStringOffset = fbb.createString(config.getAddress());
int generalSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.getEnabled(),
config.getPortIn(),
config.getPortOut(),
addressStringOffset
);
String vrmJson = config.getVrmJson();
int vrmJsonOffset = 0;
if (vrmJson != null)
vrmJsonOffset = fbb.createString(vrmJson);
VMCOSCSettings.startVMCOSCSettings(fbb);
VMCOSCSettings.addOscSettings(fbb, generalSettingOffset);
if (vrmJson != null)
VMCOSCSettings.addVrmJson(fbb, vrmJsonOffset);
VMCOSCSettings.addAnchorHip(fbb, config.getAnchorHip());
VMCOSCSettings.addMirrorTracking(fbb, config.getMirrorTracking());
return VMCOSCSettings.endVMCOSCSettings(fbb);
}
public static int createFilterSettings(
FlatBufferBuilder fbb,
FiltersConfig filtersConfig
) {
return FilteringSettings
.createFilteringSettings(
fbb,
TrackerFilters.getByConfigkey(filtersConfig.getType()).getId(),
filtersConfig.getAmount()
);
}
public static int createDriftCompensationSettings(
FlatBufferBuilder fbb,
DriftCompensationConfig driftCompensationConfig
) {
return DriftCompensationSettings
.createDriftCompensationSettings(
fbb,
driftCompensationConfig.getEnabled(),
driftCompensationConfig.getPrediction(),
driftCompensationConfig.getAmount(),
driftCompensationConfig.getMaxResets()
);
}
public static int createTapDetectionSettings(
FlatBufferBuilder fbb,
TapDetectionConfig tapDetectionConfig
) {
return TapDetectionSettings
.createTapDetectionSettings(
fbb,
tapDetectionConfig.getFullResetDelay(),
tapDetectionConfig.getFullResetEnabled(),
tapDetectionConfig.getFullResetTaps(),
tapDetectionConfig.getYawResetDelay(),
tapDetectionConfig.getYawResetEnabled(),
tapDetectionConfig.getYawResetTaps(),
tapDetectionConfig.getMountingResetDelay(),
tapDetectionConfig.getMountingResetEnabled(),
tapDetectionConfig.getMountingResetTaps(),
tapDetectionConfig.getSetupMode(),
tapDetectionConfig.getNumberTrackersOverThreshold()
);
}
public static int createSteamVRSettings(FlatBufferBuilder fbb, ISteamVRBridge bridge) {
int steamvrTrackerSettings = 0;
if (bridge != null) {
steamvrTrackerSettings = SteamVRTrackersSetting
.createSteamVRTrackersSetting(
fbb,
bridge.getShareSetting(TrackerRole.WAIST),
bridge.getShareSetting(TrackerRole.CHEST),
bridge.getAutomaticSharedTrackers(),
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),
bridge.getShareSetting(TrackerRole.LEFT_HAND),
bridge.getShareSetting(TrackerRole.RIGHT_HAND)
);
}
return steamvrTrackerSettings;
}
public static int createModelSettings(
FlatBufferBuilder fbb,
HumanPoseManager humanPoseManager,
LegTweaksConfig legTweaksConfig,
SkeletonConfig skeletonConfig
) {
int togglesOffset = ModelToggles
.createModelToggles(
fbb,
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_PELVIS_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD),
humanPoseManager.getToggle(SkeletonConfigToggles.FLOOR_CLIP),
humanPoseManager.getToggle(SkeletonConfigToggles.SKATING_CORRECTION),
humanPoseManager.getToggle(SkeletonConfigToggles.TOE_SNAP),
humanPoseManager.getToggle(SkeletonConfigToggles.FOOT_PLANT),
humanPoseManager.getToggle(SkeletonConfigToggles.SELF_LOCALIZATION),
humanPoseManager.getToggle(SkeletonConfigToggles.USE_POSITION),
humanPoseManager.getToggle(SkeletonConfigToggles.ENFORCE_CONSTRAINTS),
humanPoseManager.getToggle(SkeletonConfigToggles.CORRECT_CONSTRAINTS)
);
int ratiosOffset = ModelRatios
.createModelRatios(
fbb,
humanPoseManager.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.KNEE_ANKLE_AVERAGING)
);
int legTweaksOffset = LegTweaksSettings
.createLegTweaksSettings(
fbb,
legTweaksConfig.getCorrectionStrength()
);
int skeletonConfigOffset = SkeletonHeight
.createSkeletonHeight(
fbb,
skeletonConfig.getHmdHeight(),
skeletonConfig.getFloorHeight()
);
return ModelSettings
.createModelSettings(
fbb,
togglesOffset,
ratiosOffset,
legTweaksOffset,
skeletonConfigOffset
);
}
public static int createAutoBoneSettings(FlatBufferBuilder fbb, AutoBoneConfig autoBoneConfig) {
return AutoBoneSettings
.createAutoBoneSettings(
fbb,
autoBoneConfig.getCursorIncrement(),
autoBoneConfig.getMinDataDistance(),
autoBoneConfig.getMaxDataDistance(),
autoBoneConfig.getNumEpochs(),
autoBoneConfig.getPrintEveryNumEpochs(),
autoBoneConfig.getInitialAdjustRate(),
autoBoneConfig.getAdjustRateDecay(),
autoBoneConfig.getSlideErrorFactor(),
autoBoneConfig.getOffsetSlideErrorFactor(),
autoBoneConfig.getFootHeightOffsetErrorFactor(),
autoBoneConfig.getBodyProportionErrorFactor(),
autoBoneConfig.getHeightErrorFactor(),
autoBoneConfig.getPositionErrorFactor(),
autoBoneConfig.getPositionOffsetErrorFactor(),
autoBoneConfig.getCalcInitError(),
autoBoneConfig.getRandomizeFrameOrder(),
autoBoneConfig.getScaleEachStep(),
autoBoneConfig.getSampleCount(),
autoBoneConfig.getSampleRateMs(),
autoBoneConfig.getSaveRecordings(),
autoBoneConfig.getUseSkeletonHeight(),
autoBoneConfig.getRandSeed()
);
}
/**
* Writes values from AutoBoneSettings to an AutoBoneConfig.
*
* @param autoBoneSettings The settings to read from.
* @param autoBoneConfig The config to write to.
* @return The autoBoneConfig parameter.
*/
public static AutoBoneConfig readAutoBoneSettings(
AutoBoneSettings autoBoneSettings,
AutoBoneConfig autoBoneConfig
) {
if (autoBoneSettings.hasCursorIncrement()) {
autoBoneConfig.setCursorIncrement(autoBoneSettings.cursorIncrement());
}
if (autoBoneSettings.hasMinDataDistance()) {
autoBoneConfig.setMinDataDistance(autoBoneSettings.minDataDistance());
}
if (autoBoneSettings.hasMaxDataDistance()) {
autoBoneConfig.setMaxDataDistance(autoBoneSettings.maxDataDistance());
}
if (autoBoneSettings.hasNumEpochs()) {
autoBoneConfig.setNumEpochs(autoBoneSettings.numEpochs());
}
if (autoBoneSettings.hasPrintEveryNumEpochs()) {
autoBoneConfig.setPrintEveryNumEpochs(autoBoneSettings.printEveryNumEpochs());
}
if (autoBoneSettings.hasInitialAdjustRate()) {
autoBoneConfig.setInitialAdjustRate(autoBoneSettings.initialAdjustRate());
}
if (autoBoneSettings.hasAdjustRateDecay()) {
autoBoneConfig.setAdjustRateDecay(autoBoneSettings.adjustRateDecay());
}
if (autoBoneSettings.hasSlideErrorFactor()) {
autoBoneConfig.setSlideErrorFactor(autoBoneSettings.slideErrorFactor());
}
if (autoBoneSettings.hasOffsetSlideErrorFactor()) {
autoBoneConfig.setOffsetSlideErrorFactor(autoBoneSettings.offsetSlideErrorFactor());
}
if (autoBoneSettings.hasFootHeightOffsetErrorFactor()) {
autoBoneConfig
.setFootHeightOffsetErrorFactor(autoBoneSettings.footHeightOffsetErrorFactor());
}
if (autoBoneSettings.hasBodyProportionErrorFactor()) {
autoBoneConfig
.setBodyProportionErrorFactor(autoBoneSettings.bodyProportionErrorFactor());
}
if (autoBoneSettings.hasHeightErrorFactor()) {
autoBoneConfig.setHeightErrorFactor(autoBoneSettings.heightErrorFactor());
}
if (autoBoneSettings.hasPositionErrorFactor()) {
autoBoneConfig.setPositionErrorFactor(autoBoneSettings.positionErrorFactor());
}
if (autoBoneSettings.hasPositionOffsetErrorFactor()) {
autoBoneConfig
.setPositionOffsetErrorFactor(autoBoneSettings.positionOffsetErrorFactor());
}
if (autoBoneSettings.hasCalcInitError()) {
autoBoneConfig.setCalcInitError(autoBoneSettings.calcInitError());
}
if (autoBoneSettings.hasRandomizeFrameOrder()) {
autoBoneConfig.setRandomizeFrameOrder(autoBoneSettings.randomizeFrameOrder());
}
if (autoBoneSettings.hasScaleEachStep()) {
autoBoneConfig.setScaleEachStep(autoBoneSettings.scaleEachStep());
}
if (autoBoneSettings.hasSampleCount()) {
autoBoneConfig.setSampleCount(autoBoneSettings.sampleCount());
}
if (autoBoneSettings.hasSampleRateMs()) {
autoBoneConfig.setSampleRateMs(autoBoneSettings.sampleRateMs());
}
if (autoBoneSettings.hasSaveRecordings()) {
autoBoneConfig.setSaveRecordings(autoBoneSettings.saveRecordings());
}
if (autoBoneSettings.hasUseSkeletonHeight()) {
autoBoneConfig.setUseSkeletonHeight(autoBoneSettings.useSkeletonHeight());
}
if (autoBoneSettings.hasRandSeed()) {
autoBoneConfig.setRandSeed(autoBoneSettings.randSeed());
}
return autoBoneConfig;
}
public static int createArmsResetModeSettings(
FlatBufferBuilder fbb,
ResetsConfig resetsConfig
) {
return ResetsSettings
.createResetsSettings(
fbb,
resetsConfig.getResetMountingFeet(),
resetsConfig.getMode().getId(),
resetsConfig.getYawResetSmoothTime(),
resetsConfig.getSaveMountingReset(),
resetsConfig.getResetHmdPitch()
);
}
public static int createSettingsResponse(FlatBufferBuilder fbb, VRServer server) {
ISteamVRBridge bridge = server.getVRBridge(ISteamVRBridge.class);
return SettingsResponse
.createSettingsResponse(
fbb,
RPCSettingsBuilder.createSteamVRSettings(fbb, bridge),
RPCSettingsBuilder
.createFilterSettings(
fbb,
server.configManager.getVrConfig().getFilters()
),
RPCSettingsBuilder
.createDriftCompensationSettings(
fbb,
server.configManager.getVrConfig().getDriftCompensation()
),
RPCSettingsBuilder
.createOSCRouterSettings(
fbb,
server.configManager.getVrConfig().getOscRouter()
),
RPCSettingsBuilder
.createVRCOSCSettings(
fbb,
server.configManager.getVrConfig().getVrcOSC()
),
RPCSettingsBuilder
.createVMCOSCSettings(
fbb,
server.configManager.getVrConfig().getVMC()
),
RPCSettingsBuilder
.createModelSettings(
fbb,
server.humanPoseManager,
server.configManager.getVrConfig().getLegTweaks(),
server.configManager.getVrConfig().getSkeleton()
),
RPCSettingsBuilder
.createTapDetectionSettings(
fbb,
server.configManager.getVrConfig().getTapDetection()
),
RPCSettingsBuilder
.createAutoBoneSettings(
fbb,
server.configManager.getVrConfig().getAutoBone()
),
RPCSettingsBuilder
.createArmsResetModeSettings(
fbb,
server.configManager.getVrConfig().getResetsConfig()
),
RPCSettingsBuilderKotlin.INSTANCE
.createStayAlignedSettings(
fbb,
server.configManager.getVrConfig().getStayAlignedConfig()
),
RPCSettingsBuilderKotlin.INSTANCE
.createHIDSettings(
fbb,
server.configManager.getVrConfig().getHidConfig()
)
);
}
}

View File

@@ -0,0 +1,458 @@
package dev.slimevr.protocol.rpc.settings
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.VRServer
import dev.slimevr.bridge.ISteamVRBridge
import dev.slimevr.config.AutoBoneConfig
import dev.slimevr.config.DriftCompensationConfig
import dev.slimevr.config.FiltersConfig
import dev.slimevr.config.HIDConfig
import dev.slimevr.config.LegTweaksConfig
import dev.slimevr.config.OSCConfig
import dev.slimevr.config.ResetsConfig
import dev.slimevr.config.SkeletonConfig
import dev.slimevr.config.StayAlignedConfig
import dev.slimevr.config.TapDetectionConfig
import dev.slimevr.config.VMCConfig
import dev.slimevr.config.VRCOSCConfig
import dev.slimevr.filtering.TrackerFilters.Companion.getByConfigkey
import dev.slimevr.tracking.processor.HumanPoseManager
import dev.slimevr.tracking.processor.config.SkeletonConfigToggles
import dev.slimevr.tracking.processor.config.SkeletonConfigValues
import dev.slimevr.tracking.trackers.TrackerRole
import solarxr_protocol.rpc.AutoBoneSettings
import solarxr_protocol.rpc.DriftCompensationSettings
import solarxr_protocol.rpc.FilteringSettings
import solarxr_protocol.rpc.HIDSettings
import solarxr_protocol.rpc.OSCRouterSettings
import solarxr_protocol.rpc.OSCSettings
import solarxr_protocol.rpc.OSCTrackersSetting
import solarxr_protocol.rpc.ResetsSettings
import solarxr_protocol.rpc.SettingsResponse
import solarxr_protocol.rpc.StayAlignedSettings
import solarxr_protocol.rpc.SteamVRTrackersSetting
import solarxr_protocol.rpc.TapDetectionSettings
import solarxr_protocol.rpc.VMCOSCSettings
import solarxr_protocol.rpc.VRCOSCSettings
import solarxr_protocol.rpc.settings.LegTweaksSettings
import solarxr_protocol.rpc.settings.ModelRatios
import solarxr_protocol.rpc.settings.ModelSettings
import solarxr_protocol.rpc.settings.ModelToggles
import solarxr_protocol.rpc.settings.SkeletonHeight
fun createOSCRouterSettings(
fbb: FlatBufferBuilder,
config: OSCConfig,
): Int {
val addressStringOffset = fbb.createString(config.address)
val oscSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.enabled,
config.portIn,
config.portOut,
addressStringOffset,
)
OSCRouterSettings.startOSCRouterSettings(fbb)
OSCRouterSettings.addOscSettings(fbb, oscSettingOffset)
return OSCRouterSettings.endOSCRouterSettings(fbb)
}
fun createVRCOSCSettings(
fbb: FlatBufferBuilder,
config: VRCOSCConfig,
): Int {
val addressStringOffset = fbb.createString(config.address)
val generalSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.enabled,
config.portIn,
config.portOut,
addressStringOffset,
)
val oscSettingOffset = 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.RIGHT_KNEE, false),
config.getOSCTrackerRole(TrackerRole.LEFT_FOOT, false) &&
config.getOSCTrackerRole(TrackerRole.RIGHT_FOOT, false),
config.getOSCTrackerRole(TrackerRole.LEFT_ELBOW, false) &&
config.getOSCTrackerRole(TrackerRole.RIGHT_ELBOW, false),
config.getOSCTrackerRole(TrackerRole.LEFT_HAND, false) &&
config.getOSCTrackerRole(TrackerRole.RIGHT_HAND, false),
)
VRCOSCSettings.startVRCOSCSettings(fbb)
VRCOSCSettings.addOscSettings(fbb, generalSettingOffset)
VRCOSCSettings.addTrackers(fbb, oscSettingOffset)
VRCOSCSettings.addOscqueryEnabled(fbb, config.oscqueryEnabled)
return VRCOSCSettings.endVRCOSCSettings(fbb)
}
fun createVMCOSCSettings(
fbb: FlatBufferBuilder,
config: VMCConfig,
): Int {
val addressStringOffset = fbb.createString(config.address)
val generalSettingOffset = OSCSettings
.createOSCSettings(
fbb,
config.enabled,
config.portIn,
config.portOut,
addressStringOffset,
)
val vrmJson = config.vrmJson
var vrmJsonOffset = 0
if (vrmJson != null) vrmJsonOffset = fbb.createString(vrmJson)
VMCOSCSettings.startVMCOSCSettings(fbb)
VMCOSCSettings.addOscSettings(fbb, generalSettingOffset)
if (vrmJson != null) VMCOSCSettings.addVrmJson(fbb, vrmJsonOffset)
VMCOSCSettings.addAnchorHip(fbb, config.anchorHip)
VMCOSCSettings.addMirrorTracking(fbb, config.mirrorTracking)
return VMCOSCSettings.endVMCOSCSettings(fbb)
}
fun createFilterSettings(
fbb: FlatBufferBuilder,
filtersConfig: FiltersConfig,
): Int = FilteringSettings
.createFilteringSettings(
fbb,
getByConfigkey(filtersConfig.type)!!.id,
filtersConfig.amount,
)
fun createDriftCompensationSettings(
fbb: FlatBufferBuilder,
driftCompensationConfig: DriftCompensationConfig,
): Int = DriftCompensationSettings
.createDriftCompensationSettings(
fbb,
driftCompensationConfig.enabled,
driftCompensationConfig.prediction,
driftCompensationConfig.amount,
driftCompensationConfig.maxResets,
)
fun createTapDetectionSettings(
fbb: FlatBufferBuilder,
tapDetectionConfig: TapDetectionConfig,
): Int = TapDetectionSettings
.createTapDetectionSettings(
fbb,
tapDetectionConfig.fullResetDelay,
tapDetectionConfig.fullResetEnabled,
tapDetectionConfig.fullResetTaps,
tapDetectionConfig.yawResetDelay,
tapDetectionConfig.yawResetEnabled,
tapDetectionConfig.yawResetTaps,
tapDetectionConfig.mountingResetDelay,
tapDetectionConfig.mountingResetEnabled,
tapDetectionConfig.mountingResetTaps,
tapDetectionConfig.setupMode,
tapDetectionConfig.numberTrackersOverThreshold,
)
fun createSteamVRSettings(fbb: FlatBufferBuilder, bridge: ISteamVRBridge?): Int {
var steamvrTrackerSettings = 0
if (bridge != null) {
steamvrTrackerSettings = SteamVRTrackersSetting
.createSteamVRTrackersSetting(
fbb,
bridge.getShareSetting(TrackerRole.WAIST),
bridge.getShareSetting(TrackerRole.CHEST),
bridge.getAutomaticSharedTrackers(),
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),
bridge.getShareSetting(TrackerRole.LEFT_HAND),
bridge.getShareSetting(TrackerRole.RIGHT_HAND),
)
}
return steamvrTrackerSettings
}
fun createModelSettings(
fbb: FlatBufferBuilder,
humanPoseManager: HumanPoseManager,
legTweaksConfig: LegTweaksConfig,
skeletonConfig: SkeletonConfig,
): Int {
val togglesOffset = ModelToggles
.createModelToggles(
fbb,
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_PELVIS_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL),
humanPoseManager.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD),
humanPoseManager.getToggle(SkeletonConfigToggles.FLOOR_CLIP),
humanPoseManager.getToggle(SkeletonConfigToggles.SKATING_CORRECTION),
humanPoseManager.getToggle(SkeletonConfigToggles.TOE_SNAP),
humanPoseManager.getToggle(SkeletonConfigToggles.FOOT_PLANT),
humanPoseManager.getToggle(SkeletonConfigToggles.SELF_LOCALIZATION),
humanPoseManager.getToggle(SkeletonConfigToggles.USE_POSITION),
humanPoseManager.getToggle(SkeletonConfigToggles.ENFORCE_CONSTRAINTS),
humanPoseManager.getToggle(SkeletonConfigToggles.CORRECT_CONSTRAINTS),
)
val ratiosOffset = ModelRatios
.createModelRatios(
fbb,
humanPoseManager.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_HIP_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.WAIST_FROM_CHEST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_FROM_CHEST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_FROM_WAIST_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.HIP_LEGS_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.KNEE_TRACKER_ANKLE_AVERAGING),
humanPoseManager.getValue(SkeletonConfigValues.KNEE_ANKLE_AVERAGING),
)
val legTweaksOffset = LegTweaksSettings
.createLegTweaksSettings(
fbb,
legTweaksConfig.correctionStrength,
)
val skeletonConfigOffset = SkeletonHeight
.createSkeletonHeight(
fbb,
skeletonConfig.hmdHeight,
skeletonConfig.floorHeight,
)
return ModelSettings
.createModelSettings(
fbb,
togglesOffset,
ratiosOffset,
legTweaksOffset,
skeletonConfigOffset,
)
}
fun createAutoBoneSettings(
fbb: FlatBufferBuilder,
autoBoneConfig: AutoBoneConfig,
): Int = AutoBoneSettings
.createAutoBoneSettings(
fbb,
autoBoneConfig.cursorIncrement,
autoBoneConfig.minDataDistance,
autoBoneConfig.maxDataDistance,
autoBoneConfig.numEpochs,
autoBoneConfig.printEveryNumEpochs,
autoBoneConfig.initialAdjustRate,
autoBoneConfig.adjustRateDecay,
autoBoneConfig.slideErrorFactor,
autoBoneConfig.offsetSlideErrorFactor,
autoBoneConfig.footHeightOffsetErrorFactor,
autoBoneConfig.bodyProportionErrorFactor,
autoBoneConfig.heightErrorFactor,
autoBoneConfig.positionErrorFactor,
autoBoneConfig.positionOffsetErrorFactor,
autoBoneConfig.calcInitError,
autoBoneConfig.randomizeFrameOrder,
autoBoneConfig.scaleEachStep,
autoBoneConfig.sampleCount,
autoBoneConfig.sampleRateMs,
autoBoneConfig.saveRecordings,
autoBoneConfig.useSkeletonHeight,
autoBoneConfig.randSeed,
)
/**
* Writes values from AutoBoneSettings to an AutoBoneConfig.
*
* @param autoBoneSettings The settings to read from.
* @param autoBoneConfig The config to write to.
* @return The autoBoneConfig parameter.
*/
fun readAutoBoneSettings(
autoBoneSettings: AutoBoneSettings,
autoBoneConfig: AutoBoneConfig,
): AutoBoneConfig {
if (autoBoneSettings.hasCursorIncrement()) {
autoBoneConfig.cursorIncrement = autoBoneSettings.cursorIncrement()
}
if (autoBoneSettings.hasMinDataDistance()) {
autoBoneConfig.minDataDistance = autoBoneSettings.minDataDistance()
}
if (autoBoneSettings.hasMaxDataDistance()) {
autoBoneConfig.maxDataDistance = autoBoneSettings.maxDataDistance()
}
if (autoBoneSettings.hasNumEpochs()) {
autoBoneConfig.numEpochs = autoBoneSettings.numEpochs()
}
if (autoBoneSettings.hasPrintEveryNumEpochs()) {
autoBoneConfig.printEveryNumEpochs = autoBoneSettings.printEveryNumEpochs()
}
if (autoBoneSettings.hasInitialAdjustRate()) {
autoBoneConfig.initialAdjustRate = autoBoneSettings.initialAdjustRate()
}
if (autoBoneSettings.hasAdjustRateDecay()) {
autoBoneConfig.adjustRateDecay = autoBoneSettings.adjustRateDecay()
}
if (autoBoneSettings.hasSlideErrorFactor()) {
autoBoneConfig.slideErrorFactor = autoBoneSettings.slideErrorFactor()
}
if (autoBoneSettings.hasOffsetSlideErrorFactor()) {
autoBoneConfig.offsetSlideErrorFactor =
autoBoneSettings.offsetSlideErrorFactor()
}
if (autoBoneSettings.hasFootHeightOffsetErrorFactor()) {
autoBoneConfig
.footHeightOffsetErrorFactor =
autoBoneSettings.footHeightOffsetErrorFactor()
}
if (autoBoneSettings.hasBodyProportionErrorFactor()) {
autoBoneConfig
.bodyProportionErrorFactor = autoBoneSettings.bodyProportionErrorFactor()
}
if (autoBoneSettings.hasHeightErrorFactor()) {
autoBoneConfig.heightErrorFactor = autoBoneSettings.heightErrorFactor()
}
if (autoBoneSettings.hasPositionErrorFactor()) {
autoBoneConfig.positionErrorFactor = autoBoneSettings.positionErrorFactor()
}
if (autoBoneSettings.hasPositionOffsetErrorFactor()) {
autoBoneConfig
.positionOffsetErrorFactor = autoBoneSettings.positionOffsetErrorFactor()
}
if (autoBoneSettings.hasCalcInitError()) {
autoBoneConfig.calcInitError = autoBoneSettings.calcInitError()
}
if (autoBoneSettings.hasRandomizeFrameOrder()) {
autoBoneConfig.randomizeFrameOrder = autoBoneSettings.randomizeFrameOrder()
}
if (autoBoneSettings.hasScaleEachStep()) {
autoBoneConfig.scaleEachStep = autoBoneSettings.scaleEachStep()
}
if (autoBoneSettings.hasSampleCount()) {
autoBoneConfig.sampleCount = autoBoneSettings.sampleCount()
}
if (autoBoneSettings.hasSampleRateMs()) {
autoBoneConfig.sampleRateMs = autoBoneSettings.sampleRateMs()
}
if (autoBoneSettings.hasSaveRecordings()) {
autoBoneConfig.saveRecordings = autoBoneSettings.saveRecordings()
}
if (autoBoneSettings.hasUseSkeletonHeight()) {
autoBoneConfig.useSkeletonHeight = autoBoneSettings.useSkeletonHeight()
}
if (autoBoneSettings.hasRandSeed()) {
autoBoneConfig.randSeed = autoBoneSettings.randSeed()
}
return autoBoneConfig
}
fun createArmsResetModeSettings(
fbb: FlatBufferBuilder,
resetsConfig: ResetsConfig,
): Int = ResetsSettings
.createResetsSettings(
fbb,
resetsConfig.resetMountingFeet,
resetsConfig.mode.id,
resetsConfig.yawResetSmoothTime,
resetsConfig.saveMountingReset,
resetsConfig.resetHmdPitch,
)
fun createSettingsResponse(fbb: FlatBufferBuilder, server: VRServer): Int {
val bridge = server.getVRBridge(ISteamVRBridge::class.java)
return SettingsResponse
.createSettingsResponse(
fbb,
createSteamVRSettings(fbb, bridge),
createFilterSettings(
fbb,
server.configManager.vrConfig.filters,
),
createDriftCompensationSettings(
fbb,
server.configManager.vrConfig.driftCompensation,
),
createOSCRouterSettings(
fbb,
server.configManager.vrConfig.oscRouter,
),
createVRCOSCSettings(
fbb,
server.configManager.vrConfig.vrcOSC,
),
createVMCOSCSettings(
fbb,
server.configManager.vrConfig.vmc,
),
createModelSettings(
fbb,
server.humanPoseManager,
server.configManager.vrConfig.legTweaks,
server.configManager.vrConfig.skeleton,
),
createTapDetectionSettings(
fbb,
server.configManager.vrConfig.tapDetection,
),
createAutoBoneSettings(
fbb,
server.configManager.vrConfig.autoBone,
),
createArmsResetModeSettings(
fbb,
server.configManager.vrConfig.resetsConfig,
),
createStayAlignedSettings(
fbb,
server.configManager.vrConfig.stayAlignedConfig,
),
createHIDSettings(fbb, server.configManager.vrConfig.hidConfig),
)
}
fun createStayAlignedSettings(
fbb: FlatBufferBuilder,
config: StayAlignedConfig,
): Int = StayAlignedSettings
.createStayAlignedSettings(
fbb,
config.enabled,
false, // deprecated
config.hideYawCorrection,
config.standingRelaxedPose.enabled,
config.standingRelaxedPose.upperLegAngleInDeg,
config.standingRelaxedPose.lowerLegAngleInDeg,
config.standingRelaxedPose.footAngleInDeg,
config.sittingRelaxedPose.enabled,
config.sittingRelaxedPose.upperLegAngleInDeg,
config.sittingRelaxedPose.lowerLegAngleInDeg,
config.sittingRelaxedPose.footAngleInDeg,
config.flatRelaxedPose.enabled,
config.flatRelaxedPose.upperLegAngleInDeg,
config.flatRelaxedPose.lowerLegAngleInDeg,
config.flatRelaxedPose.footAngleInDeg,
config.setupComplete,
)
fun createHIDSettings(
fbb: FlatBufferBuilder,
config: HIDConfig,
): Int = HIDSettings
.createHIDSettings(
fbb,
config.trackersOverHID,
)

View File

@@ -1,43 +0,0 @@
package dev.slimevr.protocol.rpc.settings
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.config.HIDConfig
import dev.slimevr.config.StayAlignedConfig
import solarxr_protocol.rpc.HIDSettings
import solarxr_protocol.rpc.StayAlignedSettings
object RPCSettingsBuilderKotlin {
fun createStayAlignedSettings(
fbb: FlatBufferBuilder,
config: StayAlignedConfig,
): Int = StayAlignedSettings
.createStayAlignedSettings(
fbb,
config.enabled,
false, // deprecated
config.hideYawCorrection,
config.standingRelaxedPose.enabled,
config.standingRelaxedPose.upperLegAngleInDeg,
config.standingRelaxedPose.lowerLegAngleInDeg,
config.standingRelaxedPose.footAngleInDeg,
config.sittingRelaxedPose.enabled,
config.sittingRelaxedPose.upperLegAngleInDeg,
config.sittingRelaxedPose.lowerLegAngleInDeg,
config.sittingRelaxedPose.footAngleInDeg,
config.flatRelaxedPose.enabled,
config.flatRelaxedPose.upperLegAngleInDeg,
config.flatRelaxedPose.lowerLegAngleInDeg,
config.flatRelaxedPose.footAngleInDeg,
config.setupComplete,
)
fun createHIDSettings(
fbb: FlatBufferBuilder,
config: HIDConfig,
): Int = HIDSettings
.createHIDSettings(
fbb,
config.trackersOverHID,
)
}

View File

@@ -18,24 +18,9 @@ import kotlin.math.*
class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
init {
rpcHandler.registerPacketListener(RpcMessage.SettingsRequest) { conn: GenericConnection, messageHeader: RpcMessageHeader? ->
this.onSettingsRequest(
conn,
messageHeader,
)
}
rpcHandler
.registerPacketListener(
RpcMessage.ChangeSettingsRequest,
) { conn: GenericConnection?, messageHeader: RpcMessageHeader ->
this.onChangeSettingsRequest(
conn,
messageHeader,
)
}
rpcHandler.registerPacketListener(RpcMessage.SettingsResetRequest) { conn: GenericConnection, messageHeader: RpcMessageHeader? ->
this.onSettingsResetRequest(conn, messageHeader)
}
rpcHandler.registerPacketListener(RpcMessage.SettingsRequest, ::onSettingsRequest)
rpcHandler.registerPacketListener(RpcMessage.ChangeSettingsRequest, ::onChangeSettingsRequest)
rpcHandler.registerPacketListener(RpcMessage.SettingsResetRequest, ::onSettingsResetRequest)
}
fun onSettingsRequest(conn: GenericConnection, messageHeader: RpcMessageHeader?) {
@@ -331,7 +316,7 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
.vrConfig
.autoBone
RPCSettingsBuilder.readAutoBoneSettings(autoBoneSettings, autoBoneConfig)
readAutoBoneSettings(autoBoneSettings, autoBoneConfig)
}
if (req.resetsSettings() != null) {
@@ -391,7 +376,7 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
val settings = SettingsResponse
.createSettingsResponse(
fbb,
RPCSettingsBuilder.createSteamVRSettings(fbb, bridge), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
createSteamVRSettings(fbb, bridge), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
)
val outbound =
rpcHandler.createRPCMessage(fbb, RpcMessage.SettingsResponse, settings)

View File

@@ -3,7 +3,7 @@ package dev.slimevr.protocol.rpc.setup
import com.google.flatbuffers.FlatBufferBuilder
import dev.slimevr.protocol.GenericConnection
import dev.slimevr.protocol.ProtocolAPI
import dev.slimevr.protocol.datafeed.DataFeedBuilder
import dev.slimevr.protocol.datafeed.createTrackerId
import dev.slimevr.protocol.rpc.RPCHandler
import dev.slimevr.setup.TapSetupListener
import dev.slimevr.tracking.trackers.Tracker
@@ -20,7 +20,7 @@ class RPCTapSetupHandler(
override fun onStarted(tracker: Tracker) {
val fbb = FlatBufferBuilder(32)
val idOffset = DataFeedBuilder.createTrackerId(fbb, tracker)
val idOffset = createTrackerId(fbb, tracker)
val update = TapDetectionSetupNotification.createTapDetectionSetupNotification(fbb, idOffset)
val outbound =
rpcHandler.createRPCMessage(fbb, RpcMessage.TapDetectionSetupNotification, update)

View File

@@ -117,6 +117,9 @@ class Tracker @JvmOverloads constructor(
var signalStrength: Int? = null
var temperature: Float? = null
var button: Int? = null
var packetsReceived: Int? = null
var packetsLost: Int? = null
var packetLoss: Float? = null
var customName: String? = null
var magStatus: MagnetometerStatus = magStatus
private set

View File

@@ -155,6 +155,10 @@ class HIDCommon {
var svr_status: Int? = null
// var status: Int? = null // raw status from tracker
var rssi: Int? = null
var packets_received: Int? = null
var packets_lost: Int? = null
var windows_hit: Int? = null
var windows_missed: Int? = null
// Tracker packets
when (packetType) {
@@ -209,6 +213,10 @@ class HIDCommon {
3 -> { // status
svr_status = dataReceived[i + 2].toUByte().toInt()
// status = dataReceived[i + 3].toUByte().toInt()
packets_received = dataReceived[i + 4].toUByte().toInt()
packets_lost = dataReceived[i + 5].toUByte().toInt()
windows_hit = dataReceived[i + 6].toUByte().toInt()
windows_missed = dataReceived[i + 7].toUByte().toInt()
rssi = dataReceived[i + 15].toUByte().toInt()
}
@@ -309,6 +317,11 @@ class HIDCommon {
if (rssi != null) {
tracker.signalStrength = -rssi
}
if (packets_received != null && packets_lost != null) {
tracker.packetsReceived = packets_received
tracker.packetsLost = packets_lost
tracker.packetLoss = if (packets_lost == 0) 0.0f else packets_lost.toFloat() / (packets_received + packets_lost).toFloat()
}
// Assign rotation and acceleration
if (packetType == 1 || packetType == 4) {

View File

@@ -216,7 +216,7 @@ class TrackingChecklistManager(private val vrServer: VRServer) : VRCConfigListen
}
}
val hmd =
assignedTrackers.firstOrNull { it.isHmd && !it.isInternal && it.status.sendData }
vrServer.allTrackers.firstOrNull { it.status != TrackerStatus.DISCONNECTED && it.isHmd && !it.isInternal && it.status.sendData }
val assignedHmd = hmd == null || vrServer.humanPoseManager.skeleton.headTracker != null
updateValidity(TrackingChecklistStepId.UNASSIGNED_HMD, assignedHmd) {
if (!assignedHmd) {

View File

@@ -9,6 +9,7 @@ import org.java_websocket.WebSocket;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.jetbrains.annotations.NotNull;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -101,7 +102,7 @@ public class WebsocketAPI extends WebSocketServer implements ProtocolAPIServer {
}
@Override
public Stream<GenericConnection> getAPIConnections() {
public @NotNull Stream<GenericConnection> getApiConnections() {
return this.getConnections().stream().map(conn -> {
var c = conn.<WebsocketConnection>getAttachment();
return (GenericConnection) c;

View File

@@ -141,7 +141,7 @@ public class UnixSocketRpcBridge implements dev.slimevr.bridge.Bridge,
}
@Override
public java.util.stream.Stream<GenericConnection> getAPIConnections() {
public java.util.stream.Stream<GenericConnection> getApiConnections() {
return this.selector
.keys()
.stream()

View File

@@ -190,7 +190,7 @@ class DesktopSerialHandler :
}
override fun write(buff: ByteArray) {
LogManager.info("[SerialHandler] WRITING $buff")
LogManager.info("[SerialHandler] WRITING ${buff.toString(Charsets.UTF_8)}")
currentPort?.outputStream?.write(buff)
}