mirror of
https://github.com/ruvnet/RuView.git
synced 2026-08-26 10:05:54 +00:00
* feat(ios): add RuView LiDAR frame protocol * feat(ios): capture ARKit scene depth for RuView * feat(ios): stream compact LiDAR frames over websocket * feat(ios): add native LiDAR capture UI * feat(ios): add RuView LiDAR app entrypoint * feat(web): add LiDAR bridge web package * feat(web): decode RuView LiDAR wire frames * feat(web): add local LiDAR websocket relay * feat(web): add LiDAR browser viewer * feat(web): render live LiDAR point cloud * fix(ios): use wall clock time for LiDAR provenance * feat(web): style LiDAR viewer * test(web): add LiDAR codec tests * docs: add iPhone LiDAR integration guide * docs(adr): define iPhone LiDAR sensor bridge * fix(ios): harden and validate LiDAR bridge * fix(ios): qualify LiDAR wire depth type
122 lines
4.3 KiB
Swift
122 lines
4.3 KiB
Swift
import SwiftUI
|
|
|
|
struct ContentView: View {
|
|
@StateObject private var capture = LiDARCaptureManager()
|
|
@State private var endpoint = "ws://HOST:8787/ws/lidar?token=TOKEN"
|
|
@State private var streaming = false
|
|
@State private var status = "Idle"
|
|
|
|
private let streamer = WebSocketStreamer()
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section("Sensor") {
|
|
HStack {
|
|
Text("State")
|
|
Spacer()
|
|
Text(stateText)
|
|
.foregroundStyle(stateColor)
|
|
}
|
|
HStack {
|
|
Text("Capture FPS")
|
|
Spacer()
|
|
Text(capture.framesPerSecond.formatted(.number.precision(.fractionLength(1))))
|
|
}
|
|
if let frame = capture.lastFrame {
|
|
HStack {
|
|
Text("Depth")
|
|
Spacer()
|
|
Text("\(frame.depth.width) x \(frame.depth.height)")
|
|
}
|
|
HStack {
|
|
Text("Sequence")
|
|
Spacer()
|
|
Text("\(frame.provenance.sequence)")
|
|
}
|
|
}
|
|
|
|
Button("Start LiDAR") {
|
|
capture.start(smoothed: false)
|
|
}
|
|
.disabled(capture.state == .running)
|
|
|
|
Button("Stop") {
|
|
capture.stop()
|
|
}
|
|
.disabled(capture.state != .running)
|
|
}
|
|
|
|
Section("RuView Stream") {
|
|
TextField("ws://host:port/ws/lidar", text: $endpoint)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
|
|
Toggle("Stream geometry", isOn: $streaming)
|
|
.onChange(of: streaming) { _, enabled in
|
|
Task {
|
|
if enabled {
|
|
do {
|
|
try await streamer.connect(to: endpoint)
|
|
status = "Connected"
|
|
} catch {
|
|
streaming = false
|
|
status = error.localizedDescription
|
|
}
|
|
} else {
|
|
await streamer.disconnect()
|
|
status = "Disconnected"
|
|
}
|
|
}
|
|
}
|
|
|
|
Text(status)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Section("Privacy") {
|
|
Text("This implementation transmits depth geometry, confidence, camera intrinsics, and device pose. RGB camera frames are not transmitted.")
|
|
.font(.footnote)
|
|
}
|
|
}
|
|
.navigationTitle("RuView LiDAR")
|
|
.onAppear {
|
|
capture.onFrame = { frame in
|
|
guard streaming else { return }
|
|
Task {
|
|
do {
|
|
try await streamer.send(frame, maxFPS: 15, sampleStep: 2)
|
|
} catch {
|
|
await MainActor.run {
|
|
status = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDisappear {
|
|
capture.stop()
|
|
Task { await streamer.disconnect() }
|
|
}
|
|
}
|
|
}
|
|
|
|
private var stateText: String {
|
|
switch capture.state {
|
|
case .idle: return "Idle"
|
|
case .unsupported: return "No LiDAR"
|
|
case .running: return "Live"
|
|
case .failed(let message): return "Error: \(message)"
|
|
}
|
|
}
|
|
|
|
private var stateColor: Color {
|
|
switch capture.state {
|
|
case .running: return .green
|
|
case .failed, .unsupported: return .red
|
|
case .idle: return .secondary
|
|
}
|
|
}
|
|
}
|