Compare commits

..

14 Commits

Author SHA1 Message Date
Erimel
8d91838fab Create toggle 2026-03-02 19:48:26 -05:00
Sapphire
0236a05f26 Allow showing 'Up to date' version for third-party trackers (#1756) 2026-02-25 23:08:31 +01:00
Butterscotch!
28deb357da Revert TrackerResetsHandler & add comments (#1748) 2026-02-20 11:26:23 +01:00
jabberrock
4d93f87a01 Clarify AI policy on contributions (#1752) 2026-02-17 03:59:11 +03:00
Sapphire
88adfce242 Hide tracker firmware version when board type is unknown (#1721) 2026-02-09 21:58:36 +01:00
Sapphire
3d02795dbc Don't trigger timeout when Wi-Fi provisioning is done (#1725)
Co-authored-by: lucas lelievre <loucass003@gmail.com>
2026-02-09 21:57:56 +01:00
Sapphire
7ff50f78eb Don't ask for full reset on timeout with manual or saved mounting (#1727) 2026-02-02 18:06:38 +01:00
Sapphire
0e3aaf105c Display more accurate info for OpenVR devices (#1731) 2026-02-02 18:06:17 +01:00
sctanf
f638540886 TrackerBattery disable tooltip while charging (#1733) 2026-02-02 18:05:50 +01:00
Sapphire
343d69d690 Support feet mounting reset over Protobuf bridge (#1737) 2026-02-02 18:05:31 +01:00
H3
e2d7d354c6 Add ESP32-S2 to USB-Serial Accept-List (#1730)
Co-authored-by: unlogisch04 <98281608+unlogisch04@users.noreply.github.com>
2026-01-29 22:54:37 +03:00
sctanf
cc6f297b92 Re-add missing battery runtime estimate to DataFeedBuilder (#1732) 2026-01-29 14:00:18 +04:00
H114514191981
2add43e71a add CH343 support (#1568)
Co-authored-by: unlogisch04 <98281608+unlogisch04@users.noreply.github.com>
2026-01-28 10:39:25 +02:00
Sapphire
0a493ac345 Fix scheduled resets triggering immediately when delay is unspecified (#1724) 2026-01-25 07:08:25 +02:00
26 changed files with 374 additions and 277 deletions

View File

@@ -116,3 +116,9 @@ 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,6 +662,8 @@ 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,6 +18,8 @@ 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"
@@ -26,8 +28,10 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D4", MODE="0660
SUBSYSTEMS=="usb", ATTRS{idVendor}=="10C4", ATTRS{idProduct}=="EA60", MODE="0660", TAG+="uaccess"
## Espressif
# ESP32-C3
# ESP32-S3 / ESP32-C3 / ESP32-C5 / ESP32-C6 / ESP32-C61 / ESP32-H2 / ESP32-P4
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,6 +75,7 @@ export type SettingsForm = {
usePosition: boolean;
enforceConstraints: boolean;
correctConstraints: boolean;
fingersMitten: boolean;
};
ratios: {
imputeWaistFromChestHip: number;
@@ -134,6 +135,7 @@ const defaultValues: SettingsForm = {
usePosition: true,
enforceConstraints: true,
correctConstraints: true,
fingersMitten: false,
},
ratios: {
imputeWaistFromChestHip: 0.3,
@@ -231,6 +233,7 @@ 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;
}
@@ -1036,6 +1039,15 @@ 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,34 +223,38 @@ export function TrackerSettingsPage() {
whitespace="whitespace-pre-wrap"
textAlign="text-end"
>
v{tracker?.device?.hardwareInfo?.firmwareVersion}
{tracker?.device?.hardwareInfo?.firmwareVersion
? `v${tracker?.device?.hardwareInfo?.firmwareVersion}`
: '--'}
</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>
{!!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>
{!updateUnavailable && (
<Tooltip
@@ -289,7 +293,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">
@@ -317,10 +321,11 @@ export function TrackerSettingsPage() {
<div className="flex justify-between">
<Typography>{l10n.getString('tracker-infos-url')}</Typography>
<Typography>
udp://
{IPv4.fromNumber(
tracker?.device?.hardwareInfo?.ipAddress?.addr || 0
).toString()}
{tracker?.device?.hardwareInfo?.ipAddress?.addr
? `udp://${IPv4.fromNumber(
tracker?.device?.hardwareInfo?.ipAddress?.addr || 0
).toString()}`
: '--'}
</Typography>
</div>
<div className="flex justify-between">

View File

@@ -129,9 +129,6 @@ 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')
) {
@@ -143,6 +140,14 @@ 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,7 +32,6 @@ 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
@@ -126,8 +125,6 @@ class VRServer @JvmOverloads constructor(
val serverGuards = ServerGuards()
val trackerLogger = TrackerLogger()
init {
// UwU
deviceManager = DeviceManager(this)
@@ -182,8 +179,6 @@ class VRServer @JvmOverloads constructor(
registerTracker(tracker)
}
addNewTrackerConsumer(trackerLogger)
instance = this
}
@@ -264,7 +259,6 @@ class VRServer @JvmOverloads constructor(
}
vrcOSCHandler.update()
vMCHandler.update()
trackerLogger.update()
// final long time = System.currentTimeMillis() - start;
try {
sleep(1) // 1000Hz

View File

@@ -349,6 +349,9 @@ 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,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, ((req.delay() ?: resetsConfig.yawResetDelay) * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersYaw(RESET_SOURCE_NAME, ((req.delay() ?: 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, ((req.delay() ?: resetsConfig.fullResetDelay) * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersFull(RESET_SOURCE_NAME, ((req.delay() ?: 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, ((req.delay() ?: resetsConfig.mountingResetDelay) * 1000).toLong())
val delay = if (req.hasDelay()) {
req.delay()
} else {
api.server.scheduleResetTrackersMounting(RESET_SOURCE_NAME, ((req.delay() ?: 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())
}
}
}

View File

@@ -209,6 +209,7 @@ 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,6 +241,7 @@ 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)
if (!isRunning || provisioningStatus == ProvisioningStatus.DONE)
return;
provisioningTick();
}

View File

@@ -30,14 +30,18 @@ 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-C3
// ESP32-S3 / ESP32-C3 / ESP32-C5 / ESP32-C6 / ESP32-C61 / ESP32-H2 / ESP32-P4
Pair(0x303A, 0x1001),
// ESP32-S2
Pair(0x303A, 0x0002),
// / FTDI
// FT232BM/L/Q, FT245BM/L/Q
// FT232RL/Q, FT245RL/Q

View File

@@ -17,7 +17,8 @@ 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),;
CORRECT_CONSTRAINTS(13, "Correct Constraints", "correctConstraints", true),
FINGERS_MITTEN(14, "Fingers Mitten", "fingersMitten", true),;
public static final SkeletonConfigToggles[] values = values();
private static final Map<String, SkeletonConfigToggles> byStringVal = new HashMap<>();

View File

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

View File

@@ -120,8 +120,6 @@ 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,8 +24,6 @@ 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
@@ -54,6 +52,16 @@ 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
@@ -180,9 +188,12 @@ class TrackerResetsHandler(val tracker: Tracker) {
/**
* Get the reference adjusted 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)
// 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)
/**
* Converts raw or filtered rotation into reference- and
@@ -191,17 +202,22 @@ class TrackerResetsHandler(val tracker: Tracker) {
*/
private fun adjustToReference(rotation: Quaternion): Quaternion {
var rot = rotation
// Correct for global pitch/roll offset
rot *= attachmentFix
// Correct for global yaw offset without affecting local yaw so we can change this
// later without invalidating local yaw offset corrections
// Align heading axis with bone space
if (!tracker.isHmd || tracker.trackerPosition != TrackerPosition.HEAD) {
rot = mountingOrientation.inv() * rot * mountingOrientation
rot *= mountingOrientation
}
rot = mountRotFix.inv() * rot * mountRotFix
// T-pose global correction
// Heading correction assuming manual orientation is correct
rot = gyroFix * rot
// Align attitude axes with bone space
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?
rot *= tposeDownFix
// Align local yaw with reference
// More heading correction
rot = yawFix * rot
rot = constraintFix * rot
return rot
@@ -211,6 +227,8 @@ 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
@@ -265,23 +283,15 @@ class TrackerResetsHandler(val tracker: Tracker) {
lastResetQuaternion = oldRot
// Adjust raw rotation to mountingOrientation
val rotation = tracker.getRawRotation()
val mountingAdjustedRotation = tracker.getRawRotation() * mountingOrientation
// Gyrofix
val gyroFix = if (tracker.allowMounting || (tracker.trackerPosition == TrackerPosition.HEAD && !tracker.isHmd)) {
if (tracker.isComputed) {
fixGyroscope(rotation)
if (tracker.allowMounting || (tracker.trackerPosition == TrackerPosition.HEAD && !tracker.isHmd)) {
gyroFix = if (tracker.isComputed) {
fixGyroscope(tracker.getRawRotation())
} else {
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)
}
fixGyroscope(mountingAdjustedRotation * tposeDownFix)
}
} else {
Quaternion.IDENTITY
}
// Mounting for computed trackers
@@ -296,7 +306,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(rotation).inv() * rotation
val rotBuf = getYawQuaternion(tracker.getRawRotation()).inv() * tracker.getRawRotation()
// Isolate pitch
Quaternion(rotBuf.w, -rotBuf.x, 0f, 0f).unit()
} else {
@@ -304,7 +314,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
Quaternion.IDENTITY
}
} else {
(gyroFix * rotation).inv()
fixAttachment(mountingAdjustedRotation)
}
// Rotate attachmentFix by 180 degrees as a workaround for t-pose (down)
@@ -316,7 +326,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
// Don't adjust yaw if head and computed
if (tracker.trackerPosition != TrackerPosition.HEAD || !tracker.isComputed) {
yawFix = gyroFix * reference.project(Vector3.POS_Y).unit()
yawFix = fixYaw(mountingAdjustedRotation, reference)
tracker.yawResetSmoothing.reset()
}
@@ -360,7 +370,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
lastResetQuaternion = oldRot
val yawFixOld = yawFix
yawFix = fixYaw(tracker.getRawRotation(), reference)
yawFix = fixYaw(tracker.getRawRotation() * mountingOrientation, reference)
tracker.yawResetSmoothing.reset()
makeIdentityAdjustmentQuatsYaw()
@@ -399,9 +409,9 @@ class TrackerResetsHandler(val tracker: Tracker) {
constraintFix = Quaternion.IDENTITY
// Get the current calibrated rotation
var rotBuf = adjustToDrift(tracker.getRawRotation())
var rotBuf = adjustToDrift(tracker.getRawRotation() * mountingOrientation)
rotBuf = gyroFix * rotBuf
rotBuf *= attachmentFix
rotBuf = mountingOrientation.inv() * rotBuf * mountingOrientation
rotBuf = yawFix * rotBuf
// Adjust buffer to reference
@@ -457,22 +467,14 @@ class TrackerResetsHandler(val tracker: Tracker) {
mountRotFix = Quaternion.IDENTITY
}
// 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 fixGyroscope(sensorRotation: Quaternion): Quaternion = getYawQuaternion(sensorRotation).inv()
private fun fixAttachment(sensorRotation: Quaternion): Quaternion = (gyroFix * sensorRotation).inv()
private fun fixYaw(sensorRotation: Quaternion, reference: Quaternion): Quaternion {
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.
var rot = gyroFix * sensorRotation
rot *= attachmentFix
rot = mountRotFix.inv() * (rot * mountRotFix)
rot = getYawQuaternion(rot)
return rot.inv() * reference.project(Vector3.POS_Y).unit()
}
@@ -483,7 +485,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, order: EulerOrder = EulerOrder.YZX): Quaternion = EulerAngles(order, 0f, rot.toEulerAngles(order).y, 0f).toQuaternion().twinNearest(rot)
private fun getYawQuaternion(rot: Quaternion): Quaternion = EulerAngles(EulerOrder.YZX, 0f, rot.toEulerAngles(EulerOrder.YZX).y, 0f).toQuaternion().twinNearest(rot)
private fun makeIdentityAdjustmentQuatsFull() {
val sensorRotation = tracker.getRawRotation()

View File

@@ -321,8 +321,6 @@ 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,6 +11,7 @@ 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.*
@@ -199,7 +200,8 @@ 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 needFullReset = (!resetMountingCompleted && !vrServer.serverGuards.canDoMounting) || trackerRequireReset.isNotEmpty()
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()
updateValidity(TrackingChecklistStepId.FULL_RESET, !needFullReset) {
it.enabled = imuTrackers.isNotEmpty()
if (trackerRequireReset.isNotEmpty()) {

View File

@@ -1,129 +0,0 @@
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
-> mountRot * Quaternion.SLIMEVR.FRONT
TrackerPosition.LEFT_UPPER_LEG,
TrackerPosition.RIGHT_UPPER_LEG,
-> mountRot * Quaternion.SLIMEVR.FRONT
-> mountRot
else -> mountRot * Quaternion.SLIMEVR.FRONT
else -> mountRot
}
val actualMounting = tracker.resetsHandler.mountRotFix

View File

@@ -7,6 +7,7 @@ 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
@@ -218,6 +219,11 @@ 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,6 +3558,20 @@ 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();
}
/**
@@ -3588,6 +3602,7 @@ public final class ProtobufMessages {
private TrackerAdded() {
trackerSerial_ = "";
trackerName_ = "";
manufacturer_ = "";
}
public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
@@ -3713,6 +3728,48 @@ 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
@@ -3742,6 +3799,9 @@ 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);
}
@@ -3766,6 +3826,9 @@ 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;
@@ -3801,6 +3864,11 @@ public final class ProtobufMessages {
!= other.getTrackerRole()
)
return false;
if (
!getManufacturer()
.equals(other.getManufacturer())
)
return false;
if (!getUnknownFields().equals(other.getUnknownFields()))
return false;
return true;
@@ -3821,6 +3889,8 @@ 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;
@@ -3993,6 +4063,7 @@ public final class ProtobufMessages {
trackerSerial_ = "";
trackerName_ = "";
trackerRole_ = 0;
manufacturer_ = "";
return this;
}
@@ -4044,6 +4115,9 @@ public final class ProtobufMessages {
if (((from_bitField0_ & 0x00000008) != 0)) {
result.trackerRole_ = trackerRole_;
}
if (((from_bitField0_ & 0x00000010) != 0)) {
result.manufacturer_ = manufacturer_;
}
}
@java.lang.Override
@@ -4083,6 +4157,11 @@ 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;
@@ -4130,6 +4209,11 @@ 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
@@ -4398,6 +4482,93 @@ 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)
}
@@ -8872,53 +9043,55 @@ 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\"f\n\014T"
"try\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"|\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\"\374\002\n\rTrackerStatus\022\022\n"
"\014tracker_role\030\004 \001(\005\022\024\n\014manufacturer\030\005 \001("
+
"\ntracker_id\030\001 \001(\005\022.\n\006status\030\002 \001(\0162\036.mess"
"\t\"\374\002\n\rTrackerStatus\022\022\n\ntracker_id\030\001 \001(\005\022"
+
"ages.TrackerStatus.Status\0221\n\005extra\030\003 \003(\013"
".\n\006status\030\002 \001(\0162\036.messages.TrackerStatus"
+
"2\".messages.TrackerStatus.ExtraEntry\022;\n\n"
".Status\0221\n\005extra\030\003 \003(\0132\".messages.Tracke"
+
"confidence\030\004 \001(\0162\".messages.TrackerStatu"
"rStatus.ExtraEntry\022;\n\nconfidence\030\004 \001(\0162\""
+
"s.ConfidenceH\000\210\001\001\032,\n\nExtraEntry\022\013\n\003key\030\001"
".messages.TrackerStatus.ConfidenceH\000\210\001\001\032"
+
" \001(\t\022\r\n\005value\030\002 \001(\t:\0028\001\"E\n\006Status\022\020\n\014DIS"
",\n\nExtraEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001("
+
"CONNECTED\020\000\022\006\n\002OK\020\001\022\010\n\004BUSY\020\002\022\t\n\005ERROR\020\003"
"\t:\0028\001\"E\n\006Status\022\020\n\014DISCONNECTED\020\000\022\006\n\002OK\020"
+
"\022\014\n\010OCCLUDED\020\004\"3\n\nConfidence\022\006\n\002NO\020\000\022\007\n\003"
"\001\022\010\n\004BUSY\020\002\022\t\n\005ERROR\020\003\022\014\n\010OCCLUDED\020\004\"3\n\n"
+
"LOW\020\001\022\n\n\006MEDIUM\020\005\022\010\n\004HIGH\020\nB\r\n\013_confiden"
"Confidence\022\006\n\002NO\020\000\022\007\n\003LOW\020\001\022\n\n\006MEDIUM\020\005\022"
+
"ce\"I\n\007Battery\022\022\n\ntracker_id\030\001 \001(\005\022\025\n\rbat"
"\010\n\004HIGH\020\nB\r\n\013_confidence\"I\n\007Battery\022\022\n\nt"
+
"tery_level\030\002 \001(\002\022\023\n\013is_charging\030\003 \001(\010\"\241\002"
"racker_id\030\001 \001(\005\022\025\n\rbattery_level\030\002 \001(\002\022\023"
+
"\n\017ProtobufMessage\022&\n\010position\030\001 \001(\0132\022.me"
"\n\013is_charging\030\003 \001(\010\"\241\002\n\017ProtobufMessage\022"
+
"ssages.PositionH\000\022+\n\013user_action\030\002 \001(\0132\024"
"&\n\010position\030\001 \001(\0132\022.messages.PositionH\000\022"
+
".messages.UserActionH\000\022/\n\rtracker_added\030"
"+\n\013user_action\030\002 \001(\0132\024.messages.UserActi"
+
"\003 \001(\0132\026.messages.TrackerAddedH\000\0221\n\016track"
"onH\000\022/\n\rtracker_added\030\003 \001(\0132\026.messages.T"
+
"er_status\030\004 \001(\0132\027.messages.TrackerStatus"
"rackerAddedH\000\0221\n\016tracker_status\030\004 \001(\0132\027."
+
"H\000\022$\n\007battery\030\005 \001(\0132\021.messages.BatteryH\000"
"messages.TrackerStatusH\000\022$\n\007battery\030\005 \001("
+
"\022$\n\007version\030\006 \001(\0132\021.messages.VersionH\000B\t"
"\0132\021.messages.BatteryH\000\022$\n\007version\030\006 \001(\0132"
+
"\n\007messageB2\n\034dev.slimevr.desktop.platfor"
"\021.messages.VersionH\000B\t\n\007messageB2\n\034dev.s"
+
"mB\020ProtobufMessagesH\003b\006proto3"
"limevr.desktop.platformB\020ProtobufMessage"
+
"sH\003b\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(
@@ -8957,7 +9130,8 @@ 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", }
new java.lang.String[] { "TrackerId", "TrackerSerial", "TrackerName", "TrackerRole",
"Manufacturer", }
);
internal_static_messages_TrackerStatus_descriptor = getDescriptor()
.getMessageTypes()

View File

@@ -147,23 +147,13 @@ abstract class SteamVRBridge(
val device = instance.deviceManager
.createDevice(
trackerAdded.trackerName,
trackerAdded.trackerSerial,
"OpenVR", // TODO : We need the manufacturer
null,
trackerAdded.manufacturer.ifEmpty { "OpenVR" },
)
// Display name, needsReset and isHmd
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
}
val displayName: String = trackerAdded.trackerName
val isHmd = trackerAdded.trackerId == 0
// trackerPosition
val role = getById(trackerAdded.trackerRole)