diff --git a/gui/src/components/tracker/DeviceLogs.tsx b/gui/src/components/tracker/DeviceLogs.tsx
new file mode 100644
index 0000000000..3e8cce0392
--- /dev/null
+++ b/gui/src/components/tracker/DeviceLogs.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useRef, memo, Fragment } from 'react';
+
+type LogProps = {
+ messages: string[]
+};
+
+function Log({ messages } : LogProps) {
+ const preRef = useRef(null);
+ const lastScrollTopRef = useRef(0);
+
+ useEffect(() => {
+ const pre = preRef.current;
+ const lastScrollTop = lastScrollTopRef.current;
+ // Scroll to the latest message if either:
+ // - We were looking at the latest message previously; or
+ // - The scroll height shrunk, e.g. when logs are cleared due to device reconnecting
+ if (pre && (pre.scrollTop >= lastScrollTop || lastScrollTop >= pre.scrollHeight)) {
+ pre.scrollTop = pre.scrollHeight;
+ lastScrollTopRef.current = pre.scrollTop;
+ }
+ }, [messages]);
+
+ return (
+
+ {messages.length === 0 && (
+ <>[No log messages]>
+ )}
+ {messages.length > 0 &&
+ messages.map((msg, index) => (
+
+ {msg}
+
+
+ ))}
+
+ )
+}
+
+// Only render if the log messages have actually changed
+const LogMemo = memo(Log, (a, b) => {
+ return (
+ a.messages.length === b.messages.length &&
+ a.messages.every((e, i) => b.messages[i] === e));
+});
+
+export {
+ LogMemo as DeviceLog
+};
diff --git a/gui/src/components/tracker/TrackerSettings.tsx b/gui/src/components/tracker/TrackerSettings.tsx
index 8a1f1f7a57..8c17e1044a 100644
--- a/gui/src/components/tracker/TrackerSettings.tsx
+++ b/gui/src/components/tracker/TrackerSettings.tsx
@@ -35,6 +35,7 @@ import { TrackerCard } from './TrackerCard';
import { Quaternion } from 'three';
import { useAppContext } from '@/hooks/app';
import { MagnetometerToggleSetting } from '@/components/settings/pages/MagnetometerToggleSetting';
+import { DeviceLog } from './DeviceLogs';
const rotationsLabels: [Quaternion, string][] = [
[rotationToQuatMap.BACK, 'tracker-rotation-back'],
@@ -469,6 +470,12 @@ export function TrackerSettingsPage() {
)}
+
+
+ Device Logs
+
+
+
diff --git a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java
index 3eb480af0d..299b456398 100644
--- a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java
+++ b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java
@@ -259,6 +259,21 @@ public static int createTrackersData(
return fbb.endVector();
}
+ public static int createLogMessagesData(
+ FlatBufferBuilder fbb,
+ Device device
+ ) {
+ List messages = device.getLogMessages();
+
+ int numMessages = messages.size();
+ int[] messageOffsets = new int[numMessages];
+ for (int i = 0; i < numMessages; ++i) {
+ messageOffsets[i] = fbb.createString(messages.get(i));
+ }
+
+ return DeviceData.createLogMessagesVector(fbb, messageOffsets);
+ }
+
public static int createDeviceData(
FlatBufferBuilder fbb,
int id,
@@ -306,12 +321,15 @@ public static int createDeviceData(
? fbb.createString(device.getName())
: 0;
+ int logMessagesOffset = DataFeedBuilder.createLogMessagesData(fbb, device);
+
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);
+ DeviceData.addLogMessages(fbb, logMessagesOffset);
return DeviceData.endDeviceData(fbb);
}
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt
index 4fca49688a..d360ca6067 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt
@@ -25,6 +25,8 @@ open class Device(val magSupport: Boolean = false) {
open val hardwareIdentifier: String = "Unknown"
+ open var logMessages: MutableList = mutableListOf()
+
val isOpenVrDevice: Boolean
get() = manufacturer == "OpenVR"
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FeatureFlags.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FeatureFlags.kt
index 640af58078..5abbd02422 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FeatureFlags.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FeatureFlags.kt
@@ -52,6 +52,9 @@ enum class ServerFeatureFlags {
- `PACKET_ROTATION_AND_ACCELERATION` = 23 (0x17). */
PROTOCOL_BUNDLE_COMPACT_SUPPORT,
+ /** Server can receive log messages: `PACKET_LOG` = 102 (0x66). */
+ PROTOCOL_LOG_SUPPORT,
+
// Add new flags here
BITS_TOTAL, ;
@@ -60,6 +63,7 @@ enum class ServerFeatureFlags {
val flagsEnabled: Set = setOf(
PROTOCOL_BUNDLE_SUPPORT,
PROTOCOL_BUNDLE_COMPACT_SUPPORT,
+ PROTOCOL_LOG_SUPPORT,
// Add enabled flags here
)
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
index d97def7b9a..8a16553a59 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
@@ -181,6 +181,7 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
connection
}
connection.firmwareFeatures = FirmwareFeatures()
+ connection.logMessages.clear()
bb.limit(bb.capacity())
bb.rewind()
parser.writeHandshakeResponse(bb, connection)
@@ -561,6 +562,11 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
LogManager.info("[TrackerServer] Acknowledged config change on ${connection.descriptiveName} (${trackers.map { it.trackerNum }.joinToString()}). Config changed on ${packet.configType}")
}
+ is UDPPacket26Log -> {
+ if (connection == null) return
+ connection.logMessages.add(packet.message)
+ }
+
is UDPPacket200ProtocolChange -> {}
}
}
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPPacket.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPPacket.kt
index 1f40d979e7..da1724de28 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPPacket.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPPacket.kt
@@ -378,6 +378,13 @@ data class UDPPacket25SetConfigFlag(
}
}
+data class UDPPacket26Log(var message: String = "") : UDPPacket(26) {
+ override fun readData(buf: ByteBuffer) {
+ val length = buf.get().toUByte().toInt()
+ message = readASCIIString(buf, length)
+ }
+}
+
data class UDPPacket200ProtocolChange(
var targetProtocol: Int = 0,
var targetProtocolVersion: Int = 0,
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPProtocolParser.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPProtocolParser.kt
index 9f359226c9..6e7331501b 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPProtocolParser.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/UDPProtocolParser.kt
@@ -115,6 +115,7 @@ class UDPProtocolParser {
PACKET_USER_ACTION -> UDPPacket21UserAction()
PACKET_FEATURE_FLAGS -> UDPPacket22FeatureFlags()
PACKET_ACK_CONFIG_CHANGE -> UDPPacket24AckConfigChange()
+ PACKET_LOG -> UDPPacket26Log()
PACKET_PROTOCOL_CHANGE -> UDPPacket200ProtocolChange()
else -> null
}
@@ -150,6 +151,7 @@ class UDPProtocolParser {
const val PACKET_ROTATION_AND_ACCELERATION = 23
const val PACKET_ACK_CONFIG_CHANGE = 24
const val PACKET_SET_CONFIG_FLAG = 25
+ const val PACKET_LOG = 26
const val PACKET_BUNDLE = 100
const val PACKET_BUNDLE_COMPACT = 101
const val PACKET_PROTOCOL_CHANGE = 200