mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Solarxr start
This commit is contained in:
@@ -62,15 +62,21 @@ dependencies {
|
||||
implementation("com.google.flatbuffers:flatbuffers-java:22.10.26")
|
||||
|
||||
implementation("com.illposed.osc:javaosc-core:0.9")
|
||||
implementation("org.java-websocket:Java-WebSocket:1.+")
|
||||
implementation("com.melloware:jintellitype:1.+")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("com.mayakapps.kache:kache:2.1.1")
|
||||
implementation("io.ktor:ktor-network:3.0.0")
|
||||
implementation("io.ktor:ktor-utils:3.0.3")
|
||||
implementation("io.klogging:klogging:0.11.7")
|
||||
|
||||
val ktor_version = "3.4.1"
|
||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-utils:$ktor_version")
|
||||
|
||||
|
||||
|
||||
api("com.github.loucass003:EspflashKotlin:v0.11.0")
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ object AppLogger {
|
||||
fromMinLevel(Level.INFO) {
|
||||
toSink("stdout")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
server/core/src/main/java/dev/slimevr/solarxr/server.kt
Normal file
59
server/core/src/main/java/dev/slimevr/solarxr/server.kt
Normal file
@@ -0,0 +1,59 @@
|
||||
package dev.slimevr.solarxr
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readBytes
|
||||
import solarxr_protocol.MessageBundle
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
const val SOLARXR_PORT = 21110;
|
||||
|
||||
fun onSolarXRMessage(message: ByteBuffer) {
|
||||
val messageBundle = MessageBundle.getRootAsMessageBundle(message)
|
||||
|
||||
|
||||
for (index in 0..<messageBundle.dataFeedMsgsLength) {
|
||||
val header = messageBundle.dataFeedMsgs(index) ?: error("WIERD?")
|
||||
println("HEADER: ${header.message()}")
|
||||
// this.dataFeedHandler.onMessage(conn, header)
|
||||
}
|
||||
|
||||
for (index in 0..<messageBundle.rpcMsgsLength) {
|
||||
val header = messageBundle.rpcMsgs(index)
|
||||
// this.rpcHandler.onMessage(conn, header)
|
||||
}
|
||||
|
||||
for (index in 0..<messageBundle.pubSubMsgsLength) {
|
||||
val header = messageBundle.pubSubMsgs(index)
|
||||
// this.pubSubHandler.onMessage(conn, header)
|
||||
}
|
||||
}
|
||||
|
||||
fun createSolarXRWebsocketServer() {
|
||||
embeddedServer(Netty, port = SOLARXR_PORT) {
|
||||
install(WebSockets)
|
||||
|
||||
routing {
|
||||
webSocket {
|
||||
println("Client Connected!")
|
||||
|
||||
for (frame in incoming) {
|
||||
when (frame) {
|
||||
is Frame.Binary -> {
|
||||
val data = frame.readBytes()
|
||||
onSolarXRMessage(frame.buffer)
|
||||
println("Received Binary Packet: ${data.size} bytes")
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
||||
|
||||
6
server/core/src/main/java/dev/slimevr/solarxr/solarxr.kt
Normal file
6
server/core/src/main/java/dev/slimevr/solarxr/solarxr.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
package dev.slimevr.solarxr
|
||||
|
||||
|
||||
data class SolarXRConnectionState(
|
||||
val id: Int,
|
||||
)
|
||||
@@ -37,7 +37,7 @@ val DeviceStatsModule = DeviceModule(
|
||||
reducer = { s, a -> if (a is DeviceActions.Update) a.transform(s) else s },
|
||||
observer = {
|
||||
it.state.onEach { state ->
|
||||
AppLogger.device.info("Device state changed {State}", state)
|
||||
// AppLogger.device.info("Device state changed", state)
|
||||
}.launchIn(it.scope)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -55,7 +55,9 @@ data class Tracker(
|
||||
val TrackerInfosModule = TrackerModule(
|
||||
reducer = { s, a -> if (a is TrackerActions.Update) a.transform(s) else s },
|
||||
observer = {
|
||||
it.state.onEach { state -> AppLogger.tracker.info("Tracker state changed {State}", state) }.launchIn(it.scope)
|
||||
it.state.onEach { state ->
|
||||
// AppLogger.tracker.info("Tracker state changed {State}", state)
|
||||
}.launchIn(it.scope)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ data class UDPConnection(
|
||||
val context: UDPConnectionContext,
|
||||
val serverContext: VRServer,
|
||||
val packetEvents: PacketDispatcher,
|
||||
val send: (Packet) -> Unit,
|
||||
val send: (UDPPacket) -> Unit,
|
||||
val getDevice: () -> Device?,
|
||||
val getTracker: (sensorId: Int) -> Tracker?,
|
||||
)
|
||||
@@ -88,19 +88,15 @@ val PacketModule = UDPConnectionModule(
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - state.lastPacket > 5000 && packet.packetNumber == 0L) {
|
||||
it.context.scope.launch {
|
||||
it.context.dispatch(
|
||||
UDPConnectionActions.LastPacket(
|
||||
packetNum = 0,
|
||||
time = now
|
||||
)
|
||||
it.context.dispatch(
|
||||
UDPConnectionActions.LastPacket(
|
||||
packetNum = 0,
|
||||
time = now
|
||||
)
|
||||
AppLogger.udp.info("Reconnecting")
|
||||
}
|
||||
)
|
||||
AppLogger.udp.info("Reconnecting")
|
||||
} else if (packet.packetNumber < state.lastPacketNum) {
|
||||
it.context.scope.launch {
|
||||
AppLogger.udp.warn("WARN: Received packet with wrong packet number")
|
||||
}
|
||||
AppLogger.udp.warn("WARN: Received packet with wrong packet number")
|
||||
return@onAny
|
||||
} else {
|
||||
it.context.scope.launch {
|
||||
@@ -139,14 +135,12 @@ val PingModule = UDPConnectionModule(
|
||||
}
|
||||
|
||||
// listen for the pong
|
||||
it.packetEvents.on<PingPong> { paket ->
|
||||
it.packetEvents.on<PingPong> { packet ->
|
||||
val state = it.context.state.value
|
||||
val deviceId = state.deviceId ?: return@on
|
||||
|
||||
if (paket.data.pingId != state.lastPing.id + 1) {
|
||||
it.context.scope.launch {
|
||||
AppLogger.udp.warn("Ping ID does not match, ignoring ${paket.data.pingId} != ${state.lastPing.id + 1}")
|
||||
}
|
||||
if (packet.data.pingId != state.lastPing.id + 1) {
|
||||
AppLogger.udp.warn("Ping ID does not match, ignoring ${packet.data.pingId} != ${state.lastPing.id + 1}")
|
||||
return@on
|
||||
}
|
||||
|
||||
@@ -154,17 +148,15 @@ val PingModule = UDPConnectionModule(
|
||||
|
||||
val device = it.serverContext.getDevice(deviceId) ?: return@on
|
||||
|
||||
it.context.scope.launch {
|
||||
it.context.dispatch(
|
||||
UDPConnectionActions.ReceivedPong(
|
||||
id = paket.data.pingId,
|
||||
duration = ping
|
||||
)
|
||||
it.context.dispatch(
|
||||
UDPConnectionActions.ReceivedPong(
|
||||
id = packet.data.pingId,
|
||||
duration = ping
|
||||
)
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(ping = ping)
|
||||
})
|
||||
}
|
||||
)
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(ping = ping)
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -195,16 +187,14 @@ val HandshakeModule = UDPConnectionModule(
|
||||
serverContext = it.serverContext,
|
||||
)
|
||||
|
||||
it.context.scope.launch {
|
||||
it.serverContext.context.dispatch(
|
||||
VRServerActions.NewDevice(
|
||||
deviceId = deviceId,
|
||||
context = newDevice
|
||||
)
|
||||
it.serverContext.context.dispatch(
|
||||
VRServerActions.NewDevice(
|
||||
deviceId = deviceId,
|
||||
context = newDevice
|
||||
)
|
||||
it.context.dispatch(UDPConnectionActions.Handshake(deviceId))
|
||||
it.send(Handshake())
|
||||
}
|
||||
)
|
||||
it.context.dispatch(UDPConnectionActions.Handshake(deviceId))
|
||||
it.send(Handshake())
|
||||
} else {
|
||||
it.send(Handshake())
|
||||
}
|
||||
@@ -217,24 +207,20 @@ val DeviceStatsModule = UDPConnectionModule(
|
||||
it.packetEvents.on<BatteryLevel> { event ->
|
||||
val device = it.getDevice() ?: return@on
|
||||
|
||||
device.context.scope.launch {
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(
|
||||
batteryLevel = event.data.level,
|
||||
batteryVoltage = event.data.voltage
|
||||
)
|
||||
})
|
||||
}
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(
|
||||
batteryLevel = event.data.level,
|
||||
batteryVoltage = event.data.voltage
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it.packetEvents.on<SignalStrength> { event ->
|
||||
val device = it.getDevice() ?: return@on
|
||||
|
||||
device.context.scope.launch {
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(signalStrength = event.data.signal)
|
||||
})
|
||||
}
|
||||
device.context.dispatch(DeviceActions.Update {
|
||||
copy(signalStrength = event.data.signal)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -249,9 +235,9 @@ val SensorInfoModule = UDPConnectionModule(
|
||||
else -> s
|
||||
}
|
||||
},
|
||||
observer = {
|
||||
it.packetEvents.on<SensorInfo> { event ->
|
||||
val tracker = it.getTracker(event.data.sensorId)
|
||||
observer = { observerContext ->
|
||||
observerContext.packetEvents.on<SensorInfo> { event ->
|
||||
val tracker = observerContext.getTracker(event.data.sensorId)
|
||||
|
||||
val action = TrackerActions.Update {
|
||||
copy(
|
||||
@@ -261,41 +247,38 @@ val SensorInfoModule = UDPConnectionModule(
|
||||
}
|
||||
|
||||
if (tracker != null) {
|
||||
tracker.context.scope.launch {
|
||||
tracker.context.dispatch(action)
|
||||
}
|
||||
tracker.context.dispatch(action)
|
||||
} else {
|
||||
val device = it.getDevice()
|
||||
|
||||
val device = observerContext.getDevice()
|
||||
?: error("invalid state - a device should exist at this point")
|
||||
val deviceState = device.context.state.value
|
||||
val trackerId = it.serverContext.nextHandle()
|
||||
val trackerId = observerContext.serverContext.nextHandle()
|
||||
val newTracker = createTracker(
|
||||
id = trackerId,
|
||||
hardwareId = "${deviceState.address}:${event.data.sensorId}",
|
||||
sensorType = event.data.imuType,
|
||||
deviceId = deviceState.id,
|
||||
origin = DeviceOrigin.UDP,
|
||||
serverContext = it.serverContext,
|
||||
scope = it.serverContext.context.scope
|
||||
serverContext = observerContext.serverContext,
|
||||
scope = observerContext.serverContext.context.scope
|
||||
)
|
||||
|
||||
it.serverContext.context.scope.launch {
|
||||
it.serverContext.context.dispatch(
|
||||
VRServerActions.NewTracker(
|
||||
trackerId = trackerId,
|
||||
context = newTracker
|
||||
observerContext.serverContext.context.dispatch(
|
||||
VRServerActions.NewTracker(
|
||||
trackerId = trackerId,
|
||||
context = newTracker
|
||||
)
|
||||
)
|
||||
observerContext.context.dispatch(
|
||||
UDPConnectionActions.AssignTracker(
|
||||
trackerId = TrackerIdNum(
|
||||
id = trackerId,
|
||||
trackerNum = event.data.sensorId
|
||||
)
|
||||
)
|
||||
it.context.dispatch(
|
||||
UDPConnectionActions.AssignTracker(
|
||||
trackerId = TrackerIdNum(
|
||||
id = trackerId,
|
||||
trackerNum = event.data.sensorId
|
||||
)
|
||||
)
|
||||
)
|
||||
newTracker.context.dispatch(action)
|
||||
}
|
||||
)
|
||||
newTracker.context.dispatch(action)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -355,7 +338,7 @@ fun createUDPConnectionContext(
|
||||
context = context,
|
||||
serverContext = serverContext,
|
||||
dispatcher,
|
||||
send = { packet: Packet ->
|
||||
send = { packet: UDPPacket ->
|
||||
scope.launch {
|
||||
val bytePacket = buildPacket {
|
||||
writePacket(this, packet)
|
||||
|
||||
@@ -5,13 +5,15 @@ import dev.slimevr.tracker.TrackerStatus
|
||||
import io.github.axisangles.ktmath.Quaternion
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import io.ktor.utils.io.core.remaining
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.io.Sink
|
||||
import kotlinx.io.Source
|
||||
import kotlinx.io.readByteArray
|
||||
import kotlinx.io.readFloat
|
||||
import kotlinx.io.readString
|
||||
import kotlinx.io.readUByte
|
||||
import kotlinx.io.writeFloat
|
||||
import kotlinx.io.writeUByte
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -63,15 +65,15 @@ enum class PacketType(val id: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Packet {
|
||||
sealed interface UDPPacket {
|
||||
fun write(dst: Sink) {}
|
||||
}
|
||||
|
||||
sealed interface SensorSpecificPacket : Packet {
|
||||
sealed interface SensorSpecificPacket : UDPPacket {
|
||||
val sensorId: Int
|
||||
}
|
||||
|
||||
data object Heartbeat : Packet
|
||||
data object Heartbeat : UDPPacket
|
||||
|
||||
data class Handshake(
|
||||
val boardType: Int = 0,
|
||||
@@ -80,7 +82,7 @@ data class Handshake(
|
||||
val protocolVersion: Int = 0,
|
||||
val firmware: String? = null,
|
||||
val macString: String? = null
|
||||
) : Packet {
|
||||
) : UDPPacket {
|
||||
override fun write(dst: Sink) {
|
||||
dst.writeByte(PacketType.HANDSHAKE.id.toByte())
|
||||
dst.write("Hey OVR =D 5".toByteArray(Charsets.US_ASCII))
|
||||
@@ -117,16 +119,16 @@ data class Accel(val acceleration: Vector3 = Vector3.NULL, override val sensorId
|
||||
}
|
||||
}
|
||||
|
||||
data class PingPong(val pingId: Int = 0) : Packet {
|
||||
data class PingPong(val pingId: Int = 0) : UDPPacket {
|
||||
override fun write(dst: Sink) { dst.writeInt(pingId) }
|
||||
companion object { fun read(src: Source) = PingPong(src.readInt()) }
|
||||
}
|
||||
|
||||
data class Serial(val serial: String = "") : Packet {
|
||||
data class Serial(val serial: String = "") : UDPPacket {
|
||||
companion object { fun read(src: Source) = Serial(src.readString(src.readInt().toLong())) }
|
||||
}
|
||||
|
||||
data class BatteryLevel(val voltage: Float = 0f, val level: Float = 0f) : Packet {
|
||||
data class BatteryLevel(val voltage: Float = 0f, val level: Float = 0f) : UDPPacket {
|
||||
companion object {
|
||||
fun read(src: Source): BatteryLevel {
|
||||
val f = src.readSafeFloat()
|
||||
@@ -211,11 +213,11 @@ data class Temperature(override val sensorId: Int = 0, val temp: Float = 0f) : S
|
||||
}
|
||||
}
|
||||
|
||||
data class UserActionPacket(val type: Int = 0) : Packet {
|
||||
data class UserActionPacket(val type: Int = 0) : UDPPacket {
|
||||
companion object { fun read(src: Source) = UserActionPacket(src.readU8()) }
|
||||
}
|
||||
|
||||
data class FeatureFlags(val firmwareFeatures: ByteArray = byteArrayOf()) : Packet {
|
||||
data class FeatureFlags(val firmwareFeatures: ByteArray = byteArrayOf()) : UDPPacket {
|
||||
companion object { fun read(src: Source) = FeatureFlags(src.readByteArray(src.remaining.toInt())) }
|
||||
}
|
||||
|
||||
@@ -261,7 +263,7 @@ data class PositionPacket(override val sensorId: Int = 0, val position: Vector3
|
||||
}
|
||||
}
|
||||
|
||||
data class ProtocolChange(val targetProtocol: Int = 0, val targetVersion: Int = 0) : Packet {
|
||||
data class ProtocolChange(val targetProtocol: Int = 0, val targetVersion: Int = 0) : UDPPacket {
|
||||
override fun write(dst: Sink) {
|
||||
dst.writeUByte(targetProtocol.toUByte())
|
||||
dst.writeUByte(targetVersion.toUByte())
|
||||
@@ -269,7 +271,7 @@ data class ProtocolChange(val targetProtocol: Int = 0, val targetVersion: Int =
|
||||
companion object { fun read(src: Source) = ProtocolChange(src.readU8(), src.readU8()) }
|
||||
}
|
||||
|
||||
fun readPacket(type: PacketType, src: Source): Packet = when (type) {
|
||||
fun readPacket(type: PacketType, src: Source): UDPPacket = when (type) {
|
||||
PacketType.HEARTBEAT -> Heartbeat
|
||||
PacketType.HANDSHAKE -> Handshake.read(src)
|
||||
PacketType.ROTATION -> Rotation.read(src)
|
||||
@@ -295,7 +297,7 @@ fun readPacket(type: PacketType, src: Source): Packet = when (type) {
|
||||
PacketType.PROTOCOL_CHANGE -> ProtocolChange.read(src)
|
||||
}
|
||||
|
||||
fun writePacket(dst: Sink, packet: Packet) {
|
||||
fun writePacket(dst: Sink, packet: UDPPacket) {
|
||||
val type = when (packet) {
|
||||
is Heartbeat -> PacketType.HEARTBEAT
|
||||
is Handshake -> PacketType.HANDSHAKE
|
||||
@@ -314,34 +316,38 @@ fun writePacket(dst: Sink, packet: Packet) {
|
||||
packet.write(dst)
|
||||
}
|
||||
|
||||
data class PacketEvent<out T : Packet>(
|
||||
data class PacketEvent<out T : UDPPacket>(
|
||||
val data: T,
|
||||
val packetNumber: Long,
|
||||
)
|
||||
|
||||
class PacketDispatcher {
|
||||
val listeners = mutableMapOf<KClass<out Packet>, MutableList<(PacketEvent<Packet>) -> Unit>>()
|
||||
private val globalListeners = mutableListOf<(PacketEvent<Packet>) -> Unit>()
|
||||
val listeners = mutableMapOf<KClass<out UDPPacket>, MutableList<suspend (PacketEvent<UDPPacket>) -> Unit>>()
|
||||
val globalListeners = mutableListOf<suspend (PacketEvent<UDPPacket>) -> Unit>()
|
||||
val mutex = Mutex()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Packet> on(crossinline callback: (PacketEvent<T>) -> Unit) {
|
||||
val list = listeners.getOrPut(T::class) { mutableListOf() }
|
||||
synchronized(list) {
|
||||
list.add { callback(it as PacketEvent<T>) }
|
||||
inline fun <reified T : UDPPacket> on(crossinline callback: suspend (PacketEvent<T>) -> Unit) {
|
||||
runBlocking {
|
||||
mutex.withLock {
|
||||
val list = listeners.getOrPut(T::class) { mutableListOf() }
|
||||
list.add { callback(it as PacketEvent<T>) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onAny(callback: (PacketEvent<Packet>) -> Unit) {
|
||||
synchronized(globalListeners) { globalListeners.add(callback) }
|
||||
fun onAny(callback: suspend (PacketEvent<UDPPacket>) -> Unit) {
|
||||
runBlocking {
|
||||
mutex.withLock { globalListeners.add(callback) }
|
||||
}
|
||||
}
|
||||
|
||||
fun emit(event: PacketEvent<Packet>) {
|
||||
synchronized(globalListeners) {
|
||||
globalListeners.forEach { it(event) }
|
||||
}
|
||||
val list = listeners[event.data::class] ?: return
|
||||
synchronized(list) {
|
||||
list.forEach { it(event) }
|
||||
suspend fun emit(event: PacketEvent<UDPPacket>) {
|
||||
val targets = mutex.withLock {
|
||||
val specific = listeners[event.data::class]?.toList() ?: emptyList()
|
||||
val global = globalListeners.toList()
|
||||
global + specific
|
||||
}
|
||||
targets.forEach { it(event) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ package dev.slimevr.desktop
|
||||
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.config.createConfig
|
||||
import dev.slimevr.solarxr.createSolarXRWebsocketServer
|
||||
import dev.slimevr.tracker.udp.createUDPTrackerServer
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main(args: Array<String>) = runBlocking {
|
||||
val config = createConfig(this)
|
||||
val server = VRServer.create(this)
|
||||
|
||||
createUDPTrackerServer(server, config)
|
||||
|
||||
launch {
|
||||
createUDPTrackerServer(server, config)
|
||||
}
|
||||
launch {
|
||||
createSolarXRWebsocketServer()
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user