fix: switch WebSocket to Tailscale IP, add configurable Orin URL (Issue #709)
- Default URL updated from ws://192.168.86.158:9090 (LAN) to ws://100.64.0.2:9090 (Tailscale) - URL persisted in UserDefaults under key "orinURL" — survives app restarts - WebSocketClient.url is now mutable so it can be updated without recreation - SensorManager.updateURL(_:) applies a new URL when not streaming - ContentView: editable text field for Orin address with Apply button, disabled while streaming - Connection banner shows the active URL instead of hardcoded string Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1b2674c761
commit
a47ed7d10f
@ -2,11 +2,15 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@EnvironmentObject var sensor: SensorManager
|
@EnvironmentObject var sensor: SensorManager
|
||||||
|
@AppStorage("orinURL") private var orinURL: String = SensorManager.defaultOrinURL
|
||||||
|
@State private var editingURL: String = ""
|
||||||
|
@FocusState private var urlFieldFocused: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
connectionBanner
|
connectionBanner
|
||||||
|
orinURLField
|
||||||
Divider()
|
Divider()
|
||||||
sensorRatesGrid
|
sensorRatesGrid
|
||||||
Divider()
|
Divider()
|
||||||
@ -20,11 +24,41 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.navigationTitle("Sul-Tee")
|
.navigationTitle("Sul-Tee")
|
||||||
|
.onAppear { editingURL = orinURL }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Subviews
|
// MARK: - Subviews
|
||||||
|
|
||||||
|
private var orinURLField: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("Orin WebSocket URL")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
HStack {
|
||||||
|
TextField("ws://host:port", text: $editingURL)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.keyboardType(.URL)
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
.textInputAutocapitalization(.never)
|
||||||
|
.focused($urlFieldFocused)
|
||||||
|
.disabled(sensor.isStreaming)
|
||||||
|
.onSubmit { applyURL() }
|
||||||
|
if !sensor.isStreaming {
|
||||||
|
Button("Apply") { applyURL() }
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyURL() {
|
||||||
|
urlFieldFocused = false
|
||||||
|
orinURL = editingURL
|
||||||
|
sensor.updateURL(editingURL)
|
||||||
|
}
|
||||||
|
|
||||||
private var connectionBanner: some View {
|
private var connectionBanner: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Circle()
|
Circle()
|
||||||
@ -102,7 +136,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
private var wsLabel: String {
|
private var wsLabel: String {
|
||||||
switch sensor.wsState {
|
switch sensor.wsState {
|
||||||
case .connected: return "Connected — ws://192.168.86.158:9090"
|
case .connected: return "Connected — \(orinURL)"
|
||||||
case .connecting: return "Connecting…"
|
case .connecting: return "Connecting…"
|
||||||
case .disconnected: return "Disconnected"
|
case .disconnected: return "Disconnected"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,16 @@ final class SensorManager: NSObject, ObservableObject {
|
|||||||
|
|
||||||
// MARK: - Dependencies
|
// MARK: - Dependencies
|
||||||
|
|
||||||
private let ws = WebSocketClient(url: URL(string: "ws://192.168.86.158:9090")!)
|
static let defaultOrinURL = "ws://100.64.0.2:9090"
|
||||||
|
private static let orinURLKey = "orinURL"
|
||||||
|
|
||||||
|
private(set) var ws: WebSocketClient
|
||||||
|
|
||||||
|
/// Current Orin WebSocket URL string (persisted in UserDefaults).
|
||||||
|
var orinURLString: String {
|
||||||
|
get { UserDefaults.standard.string(forKey: Self.orinURLKey) ?? Self.defaultOrinURL }
|
||||||
|
set { UserDefaults.standard.set(newValue, forKey: Self.orinURLKey) }
|
||||||
|
}
|
||||||
private let locationManager = CLLocationManager()
|
private let locationManager = CLLocationManager()
|
||||||
private let motionManager = CMMotionManager()
|
private let motionManager = CMMotionManager()
|
||||||
private let altimeter = CMAltimeter()
|
private let altimeter = CMAltimeter()
|
||||||
@ -35,6 +44,8 @@ final class SensorManager: NSObject, ObservableObject {
|
|||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
let urlString = UserDefaults.standard.string(forKey: Self.orinURLKey) ?? Self.defaultOrinURL
|
||||||
|
self.ws = WebSocketClient(url: URL(string: urlString) ?? URL(string: Self.defaultOrinURL)!)
|
||||||
super.init()
|
super.init()
|
||||||
locationManager.delegate = self
|
locationManager.delegate = self
|
||||||
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
|
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
|
||||||
@ -69,6 +80,16 @@ final class SensorManager: NSObject, ObservableObject {
|
|||||||
rateTimer = nil
|
rateTimer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call when the user edits the Orin URL. Persists the value and updates
|
||||||
|
/// the client URL; takes effect on the next connect().
|
||||||
|
func updateURL(_ urlString: String) {
|
||||||
|
guard !isStreaming else { return }
|
||||||
|
orinURLString = urlString
|
||||||
|
if let url = URL(string: urlString), url.scheme?.hasPrefix("ws") == true {
|
||||||
|
ws.url = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Sensor start / stop
|
// MARK: - Sensor start / stop
|
||||||
|
|
||||||
private func requestPermissionsAndStartSensors() {
|
private func requestPermissionsAndStartSensors() {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ final class WebSocketClient: NSObject, ObservableObject {
|
|||||||
|
|
||||||
@Published var state: ConnectionState = .disconnected
|
@Published var state: ConnectionState = .disconnected
|
||||||
|
|
||||||
private let url: URL
|
var url: URL
|
||||||
private var session: URLSession!
|
private var session: URLSession!
|
||||||
private var task: URLSessionWebSocketTask?
|
private var task: URLSessionWebSocketTask?
|
||||||
private var shouldRun = false
|
private var shouldRun = false
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user