Compare commits

..

1 Commits

Author SHA1 Message Date
Eiren Rain
12354f0a95 Implement crude CSV tracker data collection 2026-01-27 10:45:48 +01:00
26 changed files with 275 additions and 372 deletions

View File

@@ -116,9 +116,3 @@ licensed under `GPL-v3`.
## Discord
We use discord *a lot* to coordinate and discuss development. Come join us at
https://discord.gg/SlimeVR!
## Use of AI
We DO NOT accept contributions that are generated with AI (for example, "vibe-coding").
If you do use AI, and you believe your usage of AI is reasonable, you must clearly disclose
how you used AI in your submission.

View File

@@ -662,8 +662,6 @@ settings-general-fk_settings-skeleton_settings-impute_hip_from_waist_legs = Impu
settings-general-fk_settings-skeleton_settings-interp_hip_legs = Average the hip's yaw and roll with the legs'
settings-general-fk_settings-skeleton_settings-interp_knee_tracker_ankle = Average the knee trackers' yaw and roll with the ankles'
settings-general-fk_settings-skeleton_settings-interp_knee_ankle = Average the knees' yaw and roll with the ankles'
settings-general-fk_settings-skeleton_settings-fingers_mitten = Fingers Mitten Mode
settings-general-fk_settings-self_localization-title = Mocap mode
settings-general-fk_settings-self_localization-description = Mocap Mode allows the skeleton to roughly track its own position without a headset or other trackers. Note that this requires feet and head trackers to work and is still experimental.

View File

@@ -18,8 +18,6 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="7522", MODE="0660
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="7523", MODE="0660", TAG+="uaccess"
# CH341
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="5523", MODE="0660", TAG+="uaccess"
# CH343
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D3", MODE="0660", TAG+="uaccess"
# CH9102x
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D4", MODE="0660", TAG+="uaccess"
@@ -28,10 +26,8 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D4", MODE="0660
SUBSYSTEMS=="usb", ATTRS{idVendor}=="10C4", ATTRS{idProduct}=="EA60", MODE="0660", TAG+="uaccess"
## Espressif
# ESP32-S3 / ESP32-C3 / ESP32-C5 / ESP32-C6 / ESP32-C61 / ESP32-H2 / ESP32-P4
# ESP32-C3
SUBSYSTEMS=="usb", ATTRS{idVendor}=="303A", ATTRS{idProduct}=="1001", MODE="0660", TAG+="uaccess"
# ESP32-S2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="303A", ATTRS{idProduct}=="0002", MODE="0660", TAG+="uaccess"
## FTDI
# FT232BM/L/Q, FT245BM/L/Q

View File

@@ -75,7 +75,6 @@ export type SettingsForm = {
usePosition: boolean;
enforceConstraints: boolean;
correctConstraints: boolean;
fingersMitten: boolean;
};
ratios: {
imputeWaistFromChestHip: number;
@@ -135,7 +134,6 @@ const defaultValues: SettingsForm = {
usePosition: true,
enforceConstraints: true,
correctConstraints: true,
fingersMitten: false,
},
ratios: {
imputeWaistFromChestHip: 0.3,
@@ -233,7 +231,6 @@ export function GeneralSettings() {
toggles.usePosition = values.toggles.usePosition;
toggles.enforceConstraints = values.toggles.enforceConstraints;
toggles.correctConstraints = values.toggles.correctConstraints;
toggles.fingersMitten = values.toggles.fingersMitten;
modelSettings.toggles = toggles;
}
@@ -1039,15 +1036,6 @@ export function GeneralSettings() {
'settings-general-fk_settings-skeleton_settings-extended_knees_model'
)}
/>
<CheckBox
variant="toggle"
outlined
control={control}
name="toggles.fingersMitten"
label={l10n.getString(
'settings-general-fk_settings-skeleton_settings-fingers_mitten'
)}
/>
</div>
<div className="flex flex-col">
<div className="flex flex-col pt-2 pb-3 gap-2">

View File

@@ -38,7 +38,7 @@ export function TrackerBattery({
return (
<Tooltip
disabled={charging || !runtime || debug}
disabled={!charging && (!runtime || debug)}
preferedDirection="left"
content=<Typography>{percentFormatter.format(value)}</Typography>
>

View File

@@ -199,7 +199,7 @@ export function TrackerSettingsPage() {
shakeHighlight={false}
/>
)}
{tracker?.device?.hardwareInfo?.hardwareIdentifier != 'Unknown' && (
{
<div className="flex flex-col bg-background-70 p-3 rounded-lg gap-2">
<Typography
variant="section-title"
@@ -223,38 +223,34 @@ export function TrackerSettingsPage() {
whitespace="whitespace-pre-wrap"
textAlign="text-end"
>
{tracker?.device?.hardwareInfo?.firmwareVersion
? `v${tracker?.device?.hardwareInfo?.firmwareVersion}`
: '--'}
v{tracker?.device?.hardwareInfo?.firmwareVersion}
</Typography>
</div>
{!!tracker?.device?.hardwareInfo?.officialBoardType && (
<div className="flex justify-between gap-2">
<Typography id="tracker-settings-latest-version" />
{!updateUnavailable && (
<>
{currentFirmwareRelease && (
<Typography
color={
needUpdate === 'updated'
? undefined
: 'text-accent-background-10'
}
textAlign="text-end"
whitespace="whitespace-pre-wrap"
>
{currentFirmwareRelease.name}
</Typography>
)}
</>
)}
{updateUnavailable && (
<Typography id="tracker-settings-update-unavailable-v2">
No releases found
</Typography>
)}
</div>
)}
<div className="flex justify-between gap-2">
<Typography id="tracker-settings-latest-version" />
{!updateUnavailable && (
<>
{currentFirmwareRelease && (
<Typography
color={
needUpdate === 'updated'
? undefined
: 'text-accent-background-10'
}
textAlign="text-end"
whitespace="whitespace-pre-wrap"
>
{currentFirmwareRelease.name}
</Typography>
)}
</>
)}
{updateUnavailable && (
<Typography id="tracker-settings-update-unavailable-v2">
No releases found
</Typography>
)}
</div>
</div>
{!updateUnavailable && (
<Tooltip
@@ -293,7 +289,7 @@ export function TrackerSettingsPage() {
</Tooltip>
)}
</div>
)}
}
<div className="flex flex-col bg-background-70 p-3 rounded-lg gap-2 overflow-x-auto">
<div className="flex justify-between">
@@ -321,11 +317,10 @@ export function TrackerSettingsPage() {
<div className="flex justify-between">
<Typography>{l10n.getString('tracker-infos-url')}</Typography>
<Typography>
{tracker?.device?.hardwareInfo?.ipAddress?.addr
? `udp://${IPv4.fromNumber(
tracker?.device?.hardwareInfo?.ipAddress?.addr || 0
).toString()}`
: '--'}
udp://
{IPv4.fromNumber(
tracker?.device?.hardwareInfo?.ipAddress?.addr || 0
).toString()}
</Typography>
</div>
<div className="flex justify-between">

View File

@@ -129,6 +129,9 @@ export function checkForUpdate(
if (
!device.hardwareInfo?.officialBoardType ||
![BoardType.SLIMEVR, BoardType.SLIMEVR_V1_2].includes(
device.hardwareInfo.officialBoardType
) ||
!semver.valid(currentFirmwareRelease.version) ||
!semver.valid(device.hardwareInfo.firmwareVersion?.toString() ?? 'none')
) {
@@ -140,14 +143,6 @@ export function checkForUpdate(
currentFirmwareRelease.version
);
if (
![BoardType.SLIMEVR, BoardType.SLIMEVR_V1_2].includes(
device.hardwareInfo.officialBoardType
)
) {
return canUpdate ? 'unavailable' : 'updated';
}
if (
canUpdate &&
device.hardwareStatus?.batteryPctEstimate != null &&

View File

@@ -32,6 +32,7 @@ import dev.slimevr.tracking.processor.skeleton.HumanSkeleton
import dev.slimevr.tracking.trackers.*
import dev.slimevr.tracking.trackers.udp.TrackersUDPServer
import dev.slimevr.trackingchecklist.TrackingChecklistManager
import dev.slimevr.util.TrackerLogger
import dev.slimevr.util.ann.VRServerThread
import dev.slimevr.websocketapi.WebSocketVRBridge
import io.eiren.util.ann.ThreadSafe
@@ -125,6 +126,8 @@ class VRServer @JvmOverloads constructor(
val serverGuards = ServerGuards()
val trackerLogger = TrackerLogger()
init {
// UwU
deviceManager = DeviceManager(this)
@@ -179,6 +182,8 @@ class VRServer @JvmOverloads constructor(
registerTracker(tracker)
}
addNewTrackerConsumer(trackerLogger)
instance = this
}
@@ -259,6 +264,7 @@ class VRServer @JvmOverloads constructor(
}
vrcOSCHandler.update()
vMCHandler.update()
trackerLogger.update()
// final long time = System.currentTimeMillis() - start;
try {
sleep(1) // 1000Hz

View File

@@ -349,9 +349,6 @@ fun createDeviceData(
if (tracker.packetsReceived != null) {
HardwareStatus.addPacketsReceived(fbb, tracker.packetsReceived!!)
}
if (tracker.batteryRemainingRuntime != null) {
HardwareStatus.addBatteryRuntimeEstimate(fbb, tracker.batteryRemainingRuntime!!)
}
val hardwareDataOffset = HardwareStatus.endHardwareStatus(fbb)
val hardwareInfoOffset = createHardwareInfo(fbb, device)

View File

@@ -39,39 +39,24 @@ class RPCResetHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) : ResetL
}
if (req.resetType() == ResetType.Yaw) {
val delay = if (req.hasDelay()) {
req.delay()
} else {
resetsConfig.yawResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (delay * 1000).toLong())
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.yawResetDelay) * 1000).toLong())
} else {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.yawResetDelay) * 1000).toLong(), bodyParts.toList())
}
}
if (req.resetType() == ResetType.Full) {
val delay = if (req.hasDelay()) {
req.delay()
} else {
resetsConfig.fullResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (delay * 1000).toLong())
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.fullResetDelay) * 1000).toLong())
} else {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.fullResetDelay) * 1000).toLong(), bodyParts.toList())
}
}
if (req.resetType() == ResetType.Mounting) {
val delay = if (req.hasDelay()) {
req.delay()
} else {
resetsConfig.mountingResetDelay
}
if (bodyParts.isEmpty()) {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (delay * 1000).toLong())
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.mountingResetDelay) * 1000).toLong())
} else {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, (delay * 1000).toLong(), bodyParts.toList())
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, ((req.delay() ?: resetsConfig.mountingResetDelay) * 1000).toLong(), bodyParts.toList())
}
}
}

View File

@@ -209,7 +209,6 @@ fun createModelSettings(
humanPoseManager.getToggle(SkeletonConfigToggles.USE_POSITION),
humanPoseManager.getToggle(SkeletonConfigToggles.ENFORCE_CONSTRAINTS),
humanPoseManager.getToggle(SkeletonConfigToggles.CORRECT_CONSTRAINTS),
humanPoseManager.getToggle(SkeletonConfigToggles.FINGERS_MITTEN),
)
val ratiosOffset = ModelRatios
.createModelRatios(

View File

@@ -241,7 +241,6 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
hpm.setToggle(SkeletonConfigToggles.USE_POSITION, toggles.usePosition())
hpm.setToggle(SkeletonConfigToggles.ENFORCE_CONSTRAINTS, toggles.enforceConstraints())
hpm.setToggle(SkeletonConfigToggles.CORRECT_CONSTRAINTS, toggles.correctConstraints())
hpm.setToggle(SkeletonConfigToggles.FINGERS_MITTEN, toggles.fingersMitten())
}
if (ratios != null) {

View File

@@ -36,7 +36,7 @@ public class ProvisioningHandler implements SerialListener {
this.provisioningTickTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (!isRunning || provisioningStatus == ProvisioningStatus.DONE)
if (!isRunning)
return;
provisioningTick();
}

View File

@@ -30,18 +30,14 @@ abstract class SerialHandler {
Pair(0x1A86, 0x7523),
// CH341
Pair(0x1A86, 0x5523),
// CH343
Pair(0x1A86, 0x55D3),
// CH9102x
Pair(0x1A86, 0x55D4),
// / Silabs
// CP210x
Pair(0x10C4, 0xEA60),
// / Espressif
// ESP32-S3 / ESP32-C3 / ESP32-C5 / ESP32-C6 / ESP32-C61 / ESP32-H2 / ESP32-P4
// ESP32-C3
Pair(0x303A, 0x1001),
// ESP32-S2
Pair(0x303A, 0x0002),
// / FTDI
// FT232BM/L/Q, FT245BM/L/Q
// FT232RL/Q, FT245RL/Q

View File

@@ -17,8 +17,7 @@ public enum SkeletonConfigToggles {
SELF_LOCALIZATION(10, "Self Localization", "selfLocalization", false),
USE_POSITION(11, "Use Position", "usePosition", true),
ENFORCE_CONSTRAINTS(12, "Enforce Constraints", "enforceConstraints", true),
CORRECT_CONSTRAINTS(13, "Correct Constraints", "correctConstraints", true),
FINGERS_MITTEN(14, "Fingers Mitten", "fingersMitten", true),;
CORRECT_CONSTRAINTS(13, "Correct Constraints", "correctConstraints", true),;
public static final SkeletonConfigToggles[] values = values();
private static final Map<String, SkeletonConfigToggles> byStringVal = new HashMap<>();

View File

@@ -197,7 +197,6 @@ class HumanSkeleton(
private var forceArmsFromHMD = true
private var enforceConstraints = true
private var correctConstraints = true
private var fingersMitten = true
// Ratios
private var waistFromChestHipAveraging = 0f
@@ -1205,8 +1204,6 @@ class HumanSkeleton(
SkeletonConfigToggles.ENFORCE_CONSTRAINTS -> enforceConstraints = newValue
SkeletonConfigToggles.CORRECT_CONSTRAINTS -> correctConstraints = newValue
SkeletonConfigToggles.FINGERS_MITTEN -> fingersMitten = newValue
}
}

View File

@@ -120,6 +120,8 @@ class Tracker @JvmOverloads constructor(
var packetsReceived: Int? = null
var packetsLost: Int? = null
var packetLoss: Float? = null
var windowsHit: Int? = null
var windowsMiss: Int? = null
var customName: String? = null
var magStatus: MagnetometerStatus = magStatus
private set

View File

@@ -24,6 +24,8 @@ class TrackerResetsHandler(val tracker: Tracker) {
Math.PI.toFloat(),
0f,
).toQuaternion()
private val QuarterPitch = Quaternion.rotationAroundXAxis(FastMath.HALF_PI)
private var driftAmount = 0f
private var averagedDriftQuat = Quaternion.IDENTITY
private var rotationSinceReset = Quaternion.IDENTITY
@@ -52,16 +54,6 @@ class TrackerResetsHandler(val tracker: Tracker) {
// Reference adjustment quats
/**
* Gyro fix is set by full reset. This sets the current y rotation to 0, correcting
* for initial yaw rotation and the rotation incurred by mounting orientation. This
* is a local offset in rotation and does not affect the axes of rotation.
*
* This rotation is only used to compute [attachmentFix], otherwise [yawFix] would
* correct for the same rotation.
*/
private var gyroFix = Quaternion.IDENTITY
/**
* Attachment fix is set by full reset. This sets the current x and z rotations to
* 0, correcting for initial pitch and roll rotation. This is a global offset in
@@ -188,12 +180,9 @@ class TrackerResetsHandler(val tracker: Tracker) {
/**
* Get the reference adjusted accel.
*/
// TODO: Make this actually adjusted to the corrected IMU heading. The current
// implementation for heading correction doesn't appear to be correct and may simply
// make acceleration worse, so I'm just leaving this until we work that out. The
// output of this will be world space, but with an unknown offset to heading (yaw).
// - Butterscotch
fun getReferenceAdjustedAccel(rawRot: Quaternion, accel: Vector3): Vector3 = rawRot.sandwich(accel)
// All IMU axis corrections are inverse to undo `adjustToReference` after local yaw offsets are added
// Order is VERY important here! Please be extremely careful! >~>
fun getReferenceAdjustedAccel(rawRot: Quaternion, accel: Vector3): Vector3 = (adjustToReference(rawRot) * (attachmentFix * mountingOrientation * mountRotFix * tposeDownFix).inv()).sandwich(accel)
/**
* Converts raw or filtered rotation into reference- and
@@ -202,22 +191,17 @@ class TrackerResetsHandler(val tracker: Tracker) {
*/
private fun adjustToReference(rotation: Quaternion): Quaternion {
var rot = rotation
// Align heading axis with bone space
if (!tracker.isHmd || tracker.trackerPosition != TrackerPosition.HEAD) {
rot *= mountingOrientation
}
// Heading correction assuming manual orientation is correct
rot = gyroFix * rot
// Align attitude axes with bone space
// Correct for global pitch/roll offset
rot *= attachmentFix
// Secondary heading axis alignment with bone space for automatic mounting
// Note: Applying an inverse amount of heading correction corresponding to the
// axis alignment quaternion will leave the correction to another variable
rot = mountRotFix.inv() * (rot * mountRotFix)
// More attitude axes alignment specifically for the t-pose configuration, this
// probably shouldn't be a separate variable from attachmentFix?
// Correct for global yaw offset without affecting local yaw so we can change this
// later without invalidating local yaw offset corrections
if (!tracker.isHmd || tracker.trackerPosition != TrackerPosition.HEAD) {
rot = mountingOrientation.inv() * rot * mountingOrientation
}
rot = mountRotFix.inv() * rot * mountRotFix
// T-pose global correction
rot *= tposeDownFix
// More heading correction
// Align local yaw with reference
rot = yawFix * rot
rot = constraintFix * rot
return rot
@@ -227,8 +211,6 @@ class TrackerResetsHandler(val tracker: Tracker) {
* Converts raw or filtered rotation into zero-reference-adjusted by
* applying quaternions produced after full reset and yaw reset only
*/
// This is essentially just adjustToReference but aligning to quaternion identity
// rather than to the bone.
private fun adjustToIdentity(rotation: Quaternion): Quaternion {
var rot = rotation
rot = gyroFixNoMounting * rot
@@ -283,15 +265,23 @@ class TrackerResetsHandler(val tracker: Tracker) {
lastResetQuaternion = oldRot
// Adjust raw rotation to mountingOrientation
val mountingAdjustedRotation = tracker.getRawRotation() * mountingOrientation
val rotation = tracker.getRawRotation()
// Gyrofix
if (tracker.allowMounting || (tracker.trackerPosition == TrackerPosition.HEAD && !tracker.isHmd)) {
gyroFix = if (tracker.isComputed) {
fixGyroscope(tracker.getRawRotation())
val gyroFix = if (tracker.allowMounting || (tracker.trackerPosition == TrackerPosition.HEAD && !tracker.isHmd)) {
if (tracker.isComputed) {
fixGyroscope(rotation)
} else {
fixGyroscope(mountingAdjustedRotation * tposeDownFix)
if (tracker.trackerPosition.isFoot()) {
// Feet are rotated by 90 deg pitch, this means we're relying on IMU rotation
// to be set correctly here.
fixGyroscope(rotation * tposeDownFix * QuarterPitch)
} else {
fixGyroscope(rotation * tposeDownFix)
}
}
} else {
Quaternion.IDENTITY
}
// Mounting for computed trackers
@@ -306,7 +296,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
if (resetHmdPitch) {
// Reset the HMD's pitch if it's assigned to head and resetHmdPitch is true
// Get rotation without yaw (make sure to use the raw rotation directly!)
val rotBuf = getYawQuaternion(tracker.getRawRotation()).inv() * tracker.getRawRotation()
val rotBuf = getYawQuaternion(rotation).inv() * rotation
// Isolate pitch
Quaternion(rotBuf.w, -rotBuf.x, 0f, 0f).unit()
} else {
@@ -314,7 +304,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
Quaternion.IDENTITY
}
} else {
fixAttachment(mountingAdjustedRotation)
(gyroFix * rotation).inv()
}
// Rotate attachmentFix by 180 degrees as a workaround for t-pose (down)
@@ -326,7 +316,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
// Don't adjust yaw if head and computed
if (tracker.trackerPosition != TrackerPosition.HEAD || !tracker.isComputed) {
yawFix = fixYaw(mountingAdjustedRotation, reference)
yawFix = gyroFix * reference.project(Vector3.POS_Y).unit()
tracker.yawResetSmoothing.reset()
}
@@ -370,7 +360,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
lastResetQuaternion = oldRot
val yawFixOld = yawFix
yawFix = fixYaw(tracker.getRawRotation() * mountingOrientation, reference)
yawFix = fixYaw(tracker.getRawRotation(), reference)
tracker.yawResetSmoothing.reset()
makeIdentityAdjustmentQuatsYaw()
@@ -409,9 +399,9 @@ class TrackerResetsHandler(val tracker: Tracker) {
constraintFix = Quaternion.IDENTITY
// Get the current calibrated rotation
var rotBuf = adjustToDrift(tracker.getRawRotation() * mountingOrientation)
rotBuf = gyroFix * rotBuf
var rotBuf = adjustToDrift(tracker.getRawRotation())
rotBuf *= attachmentFix
rotBuf = mountingOrientation.inv() * rotBuf * mountingOrientation
rotBuf = yawFix * rotBuf
// Adjust buffer to reference
@@ -467,14 +457,22 @@ class TrackerResetsHandler(val tracker: Tracker) {
mountRotFix = Quaternion.IDENTITY
}
private fun fixGyroscope(sensorRotation: Quaternion): Quaternion = getYawQuaternion(sensorRotation).inv()
private fun fixAttachment(sensorRotation: Quaternion): Quaternion = (gyroFix * sensorRotation).inv()
// EulerOrder.YXZ is actually better for gyroscope fix, as it can get yaw at any roll.
// Consequentially, instead of the roll being limited, the pitch is limited to
// 90 degrees from the yaw plane. This means trackers may be mounted upside down
// or with incorrectly configured IMU rotation, but we will need to compensate for
// the pitch.
private fun fixGyroscope(sensorRotation: Quaternion): Quaternion = getYawQuaternion(sensorRotation, EulerOrder.YXZ).inv()
private fun fixYaw(sensorRotation: Quaternion, reference: Quaternion): Quaternion {
var rot = gyroFix * sensorRotation
rot *= attachmentFix
rot = mountRotFix.inv() * (rot * mountRotFix)
var rot = sensorRotation * attachmentFix
// We need to fix the global yaw offset for the euler yaw calculation
if (!tracker.isHmd || tracker.trackerPosition != TrackerPosition.HEAD) {
rot = mountingOrientation.inv() * rot * mountingOrientation
}
rot = mountRotFix.inv() * rot * mountRotFix
// TODO: Get diff from ref to rot, use euler angle (YZX) yaw as output.
// This prevents pitch and roll from affecting the alignment.
rot = getYawQuaternion(rot)
return rot.inv() * reference.project(Vector3.POS_Y).unit()
}
@@ -485,7 +483,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
// In both cases, the isolated yaw value changes
// with the tracker's roll when pointing forward.
// calling twinNearest() makes sure this rotation has the wanted polarity (+-).
private fun getYawQuaternion(rot: Quaternion): Quaternion = EulerAngles(EulerOrder.YZX, 0f, rot.toEulerAngles(EulerOrder.YZX).y, 0f).toQuaternion().twinNearest(rot)
private fun getYawQuaternion(rot: Quaternion, order: EulerOrder = EulerOrder.YZX): Quaternion = EulerAngles(order, 0f, rot.toEulerAngles(order).y, 0f).toQuaternion().twinNearest(rot)
private fun makeIdentityAdjustmentQuatsFull() {
val sensorRotation = tracker.getRawRotation()

View File

@@ -321,6 +321,8 @@ class HIDCommon {
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()
tracker.windowsHit = windows_hit
tracker.windowsMiss = windows_missed
}
// Assign rotation and acceleration

View File

@@ -11,7 +11,6 @@ import dev.slimevr.tracking.trackers.Tracker
import dev.slimevr.tracking.trackers.TrackerStatus
import dev.slimevr.tracking.trackers.TrackerUtils
import dev.slimevr.tracking.trackers.udp.TrackerDataType
import io.github.axisangles.ktmath.Quaternion
import solarxr_protocol.datatypes.DeviceIdT
import solarxr_protocol.datatypes.TrackerIdT
import solarxr_protocol.rpc.*
@@ -200,8 +199,7 @@ class TrackingChecklistManager(private val vrServer: VRServer) : VRCConfigListen
}
// We ask for a full reset if you need to do mounting calibration but cant because you haven't done full reset in a while
// or if you have trackers that need reset after re-assigning
val usingSavedCalibration = vrServer.configManager.vrConfig.resetsConfig.saveMountingReset && imuTrackers.all { it.resetsHandler.mountRotFix != Quaternion.IDENTITY }
val needFullReset = (vrServer.configManager.vrConfig.resetsConfig.lastMountingMethod == MountingMethods.AUTOMATIC && !usingSavedCalibration && !resetMountingCompleted && !vrServer.serverGuards.canDoMounting) || trackerRequireReset.isNotEmpty()
val needFullReset = (!resetMountingCompleted && !vrServer.serverGuards.canDoMounting) || trackerRequireReset.isNotEmpty()
updateValidity(TrackingChecklistStepId.FULL_RESET, !needFullReset) {
it.enabled = imuTrackers.isNotEmpty()
if (trackerRequireReset.isNotEmpty()) {

View File

@@ -0,0 +1,129 @@
package dev.slimevr.util
import dev.slimevr.SLIMEVR_IDENTIFIER
import dev.slimevr.VRServer
import dev.slimevr.tracking.trackers.Tracker
import io.eiren.util.OperatingSystem
import io.eiren.util.Util
import java.io.File
import java.io.FileWriter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.function.Consumer
class TrackerLogger : Consumer<Tracker> {
var saveLogs = true
private var file: FileWriter? = null
private var lastSaved = 0L
val saveInterval = 100
fun update() {
if (saveLogs) {
if (file == null) {
val dir = OperatingSystem.resolveLogDirectory(SLIMEVR_IDENTIFIER)
?.toFile()?.absoluteFile
?: File("").absoluteFile
file = FileWriter(
File(
dir,
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").format(
LocalDateTime.now()
) + ".csv"
)
)
writeCSVHeader()
}
if(lastSaved + saveInterval < System.currentTimeMillis() && VRServer.instance.allTrackers.isNotEmpty()) {
lastSaved = System.currentTimeMillis()
var isFirst = true
VRServer.instance.allTrackers.forEach {
if(it.isInternal || it.isComputed)
return@forEach
if (!isFirst) {
file!!.write(';'.code)
}
isFirst = false
file!!.write(
"\"" + it.position.x + "\";"
+ "\"" + it.position.y + "\";"
+ "\"" + it.position.z + "\";"
+ "\"" + it.getRotation().w + "\";"
+ "\"" + it.getRotation().x + "\";"
+ "\"" + it.getRotation().y + "\";"
+ "\"" + it.getRotation().z + "\";"
+ "\"" + it.getAcceleration().x + "\";"
+ "\"" + it.getAcceleration().y + "\";"
+ "\"" + it.getAcceleration().z + "\";"
+ "\"" + it.getMagVector().x + "\";"
+ "\"" + it.getMagVector().y + "\";"
+ "\"" + it.getMagVector().z + "\";"
+ "\"" + it.batteryVoltage + "\";"
+ "\"" + it.batteryLevel + "\";"
+ "\"" + it.signalStrength + "\";"
+ "\"" + it.ping + "\";"
+ "\"" + it.packetsReceived + "\";"
+ "\"" + it.packetsLost + "\";"
+ "\"" + it.packetLoss + "\";"
+ "\"" + it.windowsHit + "\";"
+ "\"" + it.windowsMiss + "\""
)
}
file!!.write('\n'.code)
file!!.flush()
}
} else {
if(file != null) {
saveCSVAndClose()
}
}
}
private fun saveCSVAndClose() {
if (file != null) {
Util.close(file)
file = null
}
}
private fun writeCSVHeader() {
var isFirst = true
VRServer.instance.allTrackers.forEach {
if(it.isInternal || it.isComputed)
return@forEach
if (!isFirst) {
file!!.write(';'.code)
}
isFirst = false
val tid = it.id.toString()
file!!.write("\"" + tid + "_x\";"
+ "\"" + tid + "_y\";"
+ "\"" + tid + "_z\";"
+ "\"" + tid + "_qw\";"
+ "\"" + tid + "_qx\";"
+ "\"" + tid + "_qy\";"
+ "\"" + tid + "_qz\";"
+ "\"" + tid + "_ax\";"
+ "\"" + tid + "_ay\";"
+ "\"" + tid + "_az\";"
+ "\"" + tid + "_mx\";"
+ "\"" + tid + "_my\";"
+ "\"" + tid + "_mz\";"
+ "\"" + tid + "_batv\";"
+ "\"" + tid + "_batp\";"
+ "\"" + tid + "_signal\";"
+ "\"" + tid + "_ping\";"
+ "\"" + tid + "_p_rcv\";"
+ "\"" + tid + "_p_lost\";"
+ "\"" + tid + "_p_loss\";"
+ "\"" + tid + "_w_hit\";"
+ "\"" + tid + "_w_miss\"")
}
file!!.write('\n'.code)
}
override fun accept(t: Tracker) {
// New tracker appeared, re-create the log file
saveCSVAndClose()
}
}

View File

@@ -93,13 +93,13 @@ class SkeletonResetTests {
TrackerPosition.HIP,
TrackerPosition.LEFT_LOWER_LEG,
TrackerPosition.RIGHT_LOWER_LEG,
-> mountRot * Quaternion.SLIMEVR.FRONT
-> mountRot
TrackerPosition.LEFT_UPPER_LEG,
TrackerPosition.RIGHT_UPPER_LEG,
-> mountRot
-> mountRot * Quaternion.SLIMEVR.FRONT
else -> mountRot
else -> mountRot * Quaternion.SLIMEVR.FRONT
}
val actualMounting = tracker.resetsHandler.mountRotFix

View File

@@ -7,7 +7,6 @@ import dev.slimevr.desktop.platform.ProtobufMessages.*
import dev.slimevr.tracking.trackers.Tracker
import dev.slimevr.tracking.trackers.TrackerStatus
import dev.slimevr.tracking.trackers.TrackerStatus.Companion.getById
import dev.slimevr.tracking.trackers.TrackerUtils
import dev.slimevr.util.ann.VRServerThread
import io.eiren.util.ann.Synchronize
import io.eiren.util.ann.ThreadSafe
@@ -219,11 +218,6 @@ abstract class ProtobufBridge(@JvmField protected val bridgeName: String) : ISte
"mounting_reset" -> instance.resetTrackersMounting(resetSourceName)
"feet_mounting_reset" -> instance.resetTrackersMounting(
resetSourceName,
TrackerUtils.feetsBodyParts,
)
"pause_tracking" ->
instance
.togglePauseTracking(resetSourceName)

View File

@@ -3558,20 +3558,6 @@ public final class ProtobufMessages {
* @return The trackerRole.
*/
int getTrackerRole();
/**
* <code>string manufacturer = 5;</code>
*
* @return The manufacturer.
*/
java.lang.String getManufacturer();
/**
* <code>string manufacturer = 5;</code>
*
* @return The bytes for manufacturer.
*/
com.google.protobuf.ByteString getManufacturerBytes();
}
/**
@@ -3602,7 +3588,6 @@ public final class ProtobufMessages {
private TrackerAdded() {
trackerSerial_ = "";
trackerName_ = "";
manufacturer_ = "";
}
public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
@@ -3728,48 +3713,6 @@ public final class ProtobufMessages {
return trackerRole_;
}
public static final int MANUFACTURER_FIELD_NUMBER = 5;
@SuppressWarnings("serial")
private volatile java.lang.Object manufacturer_ = "";
/**
* <code>string manufacturer = 5;</code>
*
* @return The manufacturer.
*/
@java.lang.Override
public java.lang.String getManufacturer() {
java.lang.Object ref = manufacturer_;
if (ref instanceof java.lang.String) {
return (java.lang.String) ref;
} else {
com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
manufacturer_ = s;
return s;
}
}
/**
* <code>string manufacturer = 5;</code>
*
* @return The bytes for manufacturer.
*/
@java.lang.Override
public com.google.protobuf.ByteString getManufacturerBytes() {
java.lang.Object ref = manufacturer_;
if (ref instanceof java.lang.String) {
com.google.protobuf.ByteString b = com.google.protobuf.ByteString
.copyFromUtf8(
(java.lang.String) ref
);
manufacturer_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
private byte memoizedIsInitialized = -1;
@java.lang.Override
@@ -3799,9 +3742,6 @@ public final class ProtobufMessages {
if (trackerRole_ != 0) {
output.writeInt32(4, trackerRole_);
}
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(manufacturer_)) {
com.google.protobuf.GeneratedMessage.writeString(output, 5, manufacturer_);
}
getUnknownFields().writeTo(output);
}
@@ -3826,9 +3766,6 @@ public final class ProtobufMessages {
size += com.google.protobuf.CodedOutputStream
.computeInt32Size(4, trackerRole_);
}
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(manufacturer_)) {
size += com.google.protobuf.GeneratedMessage.computeStringSize(5, manufacturer_);
}
size += getUnknownFields().getSerializedSize();
memoizedSize = size;
return size;
@@ -3864,11 +3801,6 @@ public final class ProtobufMessages {
!= other.getTrackerRole()
)
return false;
if (
!getManufacturer()
.equals(other.getManufacturer())
)
return false;
if (!getUnknownFields().equals(other.getUnknownFields()))
return false;
return true;
@@ -3889,8 +3821,6 @@ public final class ProtobufMessages {
hash = (53 * hash) + getTrackerName().hashCode();
hash = (37 * hash) + TRACKER_ROLE_FIELD_NUMBER;
hash = (53 * hash) + getTrackerRole();
hash = (37 * hash) + MANUFACTURER_FIELD_NUMBER;
hash = (53 * hash) + getManufacturer().hashCode();
hash = (29 * hash) + getUnknownFields().hashCode();
memoizedHashCode = hash;
return hash;
@@ -4063,7 +3993,6 @@ public final class ProtobufMessages {
trackerSerial_ = "";
trackerName_ = "";
trackerRole_ = 0;
manufacturer_ = "";
return this;
}
@@ -4115,9 +4044,6 @@ public final class ProtobufMessages {
if (((from_bitField0_ & 0x00000008) != 0)) {
result.trackerRole_ = trackerRole_;
}
if (((from_bitField0_ & 0x00000010) != 0)) {
result.manufacturer_ = manufacturer_;
}
}
@java.lang.Override
@@ -4157,11 +4083,6 @@ public final class ProtobufMessages {
if (other.getTrackerRole() != 0) {
setTrackerRole(other.getTrackerRole());
}
if (!other.getManufacturer().isEmpty()) {
manufacturer_ = other.manufacturer_;
bitField0_ |= 0x00000010;
onChanged();
}
this.mergeUnknownFields(other.getUnknownFields());
onChanged();
return this;
@@ -4209,11 +4130,6 @@ public final class ProtobufMessages {
bitField0_ |= 0x00000008;
break;
} // case 32
case 42: {
manufacturer_ = input.readStringRequireUtf8();
bitField0_ |= 0x00000010;
break;
} // case 42
default: {
if (!super.parseUnknownField(input, extensionRegistry, tag)) {
done = true; // was an endgroup tag
@@ -4482,93 +4398,6 @@ public final class ProtobufMessages {
return this;
}
private java.lang.Object manufacturer_ = "";
/**
* <code>string manufacturer = 5;</code>
*
* @return The manufacturer.
*/
public java.lang.String getManufacturer() {
java.lang.Object ref = manufacturer_;
if (!(ref instanceof java.lang.String)) {
com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
manufacturer_ = s;
return s;
} else {
return (java.lang.String) ref;
}
}
/**
* <code>string manufacturer = 5;</code>
*
* @return The bytes for manufacturer.
*/
public com.google.protobuf.ByteString getManufacturerBytes() {
java.lang.Object ref = manufacturer_;
if (ref instanceof String) {
com.google.protobuf.ByteString b = com.google.protobuf.ByteString
.copyFromUtf8(
(java.lang.String) ref
);
manufacturer_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
/**
* <code>string manufacturer = 5;</code>
*
* @param value The manufacturer to set.
* @return This builder for chaining.
*/
public Builder setManufacturer(
java.lang.String value
) {
if (value == null) {
throw new NullPointerException();
}
manufacturer_ = value;
bitField0_ |= 0x00000010;
onChanged();
return this;
}
/**
* <code>string manufacturer = 5;</code>
*
* @return This builder for chaining.
*/
public Builder clearManufacturer() {
manufacturer_ = getDefaultInstance().getManufacturer();
bitField0_ = (bitField0_ & ~0x00000010);
onChanged();
return this;
}
/**
* <code>string manufacturer = 5;</code>
*
* @param value The bytes for manufacturer to set.
* @return This builder for chaining.
*/
public Builder setManufacturerBytes(
com.google.protobuf.ByteString value
) {
if (value == null) {
throw new NullPointerException();
}
checkByteStringIsUtf8(value);
manufacturer_ = value;
bitField0_ |= 0x00000010;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:messages.TrackerAdded)
}
@@ -9043,55 +8872,53 @@ public final class ProtobufMessages {
+
"ctionArgumentsEntry\0326\n\024ActionArgumentsEn"
+
"try\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"|\n\014T"
"try\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"f\n\014T"
+
"rackerAdded\022\022\n\ntracker_id\030\001 \001(\005\022\026\n\016track"
+
"er_serial\030\002 \001(\t\022\024\n\014tracker_name\030\003 \001(\t\022\024\n"
+
"\014tracker_role\030\004 \001(\005\022\024\n\014manufacturer\030\005 \001("
"\014tracker_role\030\004 \001(\005\"\374\002\n\rTrackerStatus\022\022\n"
+
"\t\"\374\002\n\rTrackerStatus\022\022\n\ntracker_id\030\001 \001(\005\022"
"\ntracker_id\030\001 \001(\005\022.\n\006status\030\002 \001(\0162\036.mess"
+
".\n\006status\030\002 \001(\0162\036.messages.TrackerStatus"
"ages.TrackerStatus.Status\0221\n\005extra\030\003 \003(\013"
+
".Status\0221\n\005extra\030\003 \003(\0132\".messages.Tracke"
"2\".messages.TrackerStatus.ExtraEntry\022;\n\n"
+
"rStatus.ExtraEntry\022;\n\nconfidence\030\004 \001(\0162\""
"confidence\030\004 \001(\0162\".messages.TrackerStatu"
+
".messages.TrackerStatus.ConfidenceH\000\210\001\001\032"
"s.ConfidenceH\000\210\001\001\032,\n\nExtraEntry\022\013\n\003key\030\001"
+
",\n\nExtraEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001("
" \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"E\n\006Status\022\020\n\014DIS"
+
"\t:\0028\001\"E\n\006Status\022\020\n\014DISCONNECTED\020\000\022\006\n\002OK\020"
"CONNECTED\020\000\022\006\n\002OK\020\001\022\010\n\004BUSY\020\002\022\t\n\005ERROR\020\003"
+
"\001\022\010\n\004BUSY\020\002\022\t\n\005ERROR\020\003\022\014\n\010OCCLUDED\020\004\"3\n\n"
"\022\014\n\010OCCLUDED\020\004\"3\n\nConfidence\022\006\n\002NO\020\000\022\007\n\003"
+
"Confidence\022\006\n\002NO\020\000\022\007\n\003LOW\020\001\022\n\n\006MEDIUM\020\005\022"
"LOW\020\001\022\n\n\006MEDIUM\020\005\022\010\n\004HIGH\020\nB\r\n\013_confiden"
+
"\010\n\004HIGH\020\nB\r\n\013_confidence\"I\n\007Battery\022\022\n\nt"
"ce\"I\n\007Battery\022\022\n\ntracker_id\030\001 \001(\005\022\025\n\rbat"
+
"racker_id\030\001 \001(\005\022\025\n\rbattery_level\030\002 \001(\002\022\023"
"tery_level\030\002 \001(\002\022\023\n\013is_charging\030\003 \001(\010\"\241\002"
+
"\n\013is_charging\030\003 \001(\010\"\241\002\n\017ProtobufMessage\022"
"\n\017ProtobufMessage\022&\n\010position\030\001 \001(\0132\022.me"
+
"&\n\010position\030\001 \001(\0132\022.messages.PositionH\000\022"
"ssages.PositionH\000\022+\n\013user_action\030\002 \001(\0132\024"
+
"+\n\013user_action\030\002 \001(\0132\024.messages.UserActi"
".messages.UserActionH\000\022/\n\rtracker_added\030"
+
"onH\000\022/\n\rtracker_added\030\003 \001(\0132\026.messages.T"
"\003 \001(\0132\026.messages.TrackerAddedH\000\0221\n\016track"
+
"rackerAddedH\000\0221\n\016tracker_status\030\004 \001(\0132\027."
"er_status\030\004 \001(\0132\027.messages.TrackerStatus"
+
"messages.TrackerStatusH\000\022$\n\007battery\030\005 \001("
"H\000\022$\n\007battery\030\005 \001(\0132\021.messages.BatteryH\000"
+
"\0132\021.messages.BatteryH\000\022$\n\007version\030\006 \001(\0132"
"\022$\n\007version\030\006 \001(\0132\021.messages.VersionH\000B\t"
+
"\021.messages.VersionH\000B\t\n\007messageB2\n\034dev.s"
"\n\007messageB2\n\034dev.slimevr.desktop.platfor"
+
"limevr.desktop.platformB\020ProtobufMessage"
+
"sH\003b\006proto3"
"mB\020ProtobufMessagesH\003b\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(
@@ -9130,8 +8957,7 @@ public final class ProtobufMessages {
internal_static_messages_TrackerAdded_descriptor = getDescriptor().getMessageTypes().get(4);
internal_static_messages_TrackerAdded_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_messages_TrackerAdded_descriptor,
new java.lang.String[] { "TrackerId", "TrackerSerial", "TrackerName", "TrackerRole",
"Manufacturer", }
new java.lang.String[] { "TrackerId", "TrackerSerial", "TrackerName", "TrackerRole", }
);
internal_static_messages_TrackerStatus_descriptor = getDescriptor()
.getMessageTypes()

View File

@@ -147,13 +147,23 @@ abstract class SteamVRBridge(
val device = instance.deviceManager
.createDevice(
trackerAdded.trackerName,
null,
trackerAdded.manufacturer.ifEmpty { "OpenVR" },
trackerAdded.trackerSerial,
"OpenVR", // TODO : We need the manufacturer
)
// Display name, needsReset and isHmd
val displayName: String = trackerAdded.trackerName
val isHmd = trackerAdded.trackerId == 0
val displayName: String
val isHmd = if (trackerAdded.trackerId == 0) {
displayName = if (trackerAdded.trackerName == "HMD") {
"SteamVR Driver HMD"
} else {
"Feeder App HMD"
}
true
} else {
displayName = trackerAdded.trackerName
false
}
// trackerPosition
val role = getById(trackerAdded.trackerRole)