Improve auto-probe UX: auto-connect after finding device

- Auto-probe now automatically connects after finding a VU meter
- Add connection status indicator (green/red dot) on each dial
- Add animation to dial value changes
- Show minimum arc value for better visibility at low values
- Display error messages in hardware panel
- Show "No USB serial devices" message when none found
- Improved status display with port name in green when connected
This commit is contained in:
Claude
2025-12-14 15:29:45 +00:00
parent 0ecf2c7940
commit 5e0cc74aaf
2 changed files with 50 additions and 12 deletions
+42 -8
View File
@@ -91,7 +91,7 @@ struct HardwarePanelView: View {
.disabled(serialManager.isProbing) .disabled(serialManager.isProbing)
} }
// Stats / Device info // Stats / Device info / Errors
if serialManager.isConnected { if serialManager.isConnected {
HStack { HStack {
Text("TX: \(formatBytes(serialManager.bytesSent))") Text("TX: \(formatBytes(serialManager.bytesSent))")
@@ -102,7 +102,20 @@ struct HardwarePanelView: View {
Text(serialManager.selectedPortPath.components(separatedBy: "/").last ?? "") Text(serialManager.selectedPortPath.components(separatedBy: "/").last ?? "")
.font(.system(size: 9, design: .monospaced)) .font(.system(size: 9, design: .monospaced))
.foregroundColor(.gray) .foregroundColor(.green)
}
} else if let error = serialManager.lastError {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.red)
.font(.system(size: 10))
Text(error)
.font(.system(size: 9, design: .monospaced))
.foregroundColor(.red)
.lineLimit(2)
Spacer()
} }
} else if let detected = serialManager.detectedDevice { } else if let detected = serialManager.detectedDevice {
HStack { HStack {
@@ -122,6 +135,18 @@ struct HardwarePanelView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
} }
} }
} else if serialManager.availablePorts.isEmpty {
HStack {
Image(systemName: "usb")
.foregroundColor(.orange)
.font(.system(size: 10))
Text("No USB serial devices detected")
.font(.system(size: 9, design: .monospaced))
.foregroundColor(.orange)
Spacer()
}
} }
} }
.padding() .padding()
@@ -183,29 +208,35 @@ struct DialIndicatorView: View {
var body: some View { var body: some View {
VStack(spacing: 4) { VStack(spacing: 4) {
// Dial number // Dial number with connection indicator
HStack(spacing: 2) {
Circle()
.fill(isConnected ? Color.green : Color.red)
.frame(width: 5, height: 5)
Text("D\(dialNumber)") Text("D\(dialNumber)")
.font(.system(size: 10, weight: .bold, design: .monospaced)) .font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundColor(.white.opacity(0.7)) .foregroundColor(.white.opacity(0.7))
}
// Value arc // Value arc
ZStack { ZStack {
// Background arc // Background arc
Circle() Circle()
.trim(from: 0.25, to: 0.75) .trim(from: 0.25, to: 0.75)
.stroke(Color.gray.opacity(0.2), lineWidth: 4) .stroke(Color.gray.opacity(0.3), lineWidth: 4)
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.rotationEffect(.degrees(180)) .rotationEffect(.degrees(180))
// Value arc // Value arc - always show with minimum visibility
Circle() Circle()
.trim(from: 0.25, to: 0.25 + (Double(value) / 255.0) * 0.5) .trim(from: 0.25, to: 0.25 + max(0.02, (Double(value) / 255.0) * 0.5))
.stroke( .stroke(
isConnected ? dialColor(for: value) : Color.gray, dialColor(for: value, connected: isConnected),
style: StrokeStyle(lineWidth: 4, lineCap: .round) style: StrokeStyle(lineWidth: 4, lineCap: .round)
) )
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.rotationEffect(.degrees(180)) .rotationEffect(.degrees(180))
.animation(.easeOut(duration: 0.1), value: value)
// Value text // Value text
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -222,7 +253,10 @@ struct DialIndicatorView: View {
} }
} }
private func dialColor(for value: Int) -> Color { private func dialColor(for value: Int, connected: Bool) -> Color {
if !connected {
return Color.gray.opacity(0.5)
}
let ratio = Double(value) / 255.0 let ratio = Double(value) / 255.0
if ratio > 0.9 { return .red } if ratio > 0.9 { return .red }
if ratio > 0.75 { return .orange } if ratio > 0.75 { return .orange }
@@ -424,13 +424,17 @@ class SerialManager: ObservableObject {
self.selectedProtocol = best.protocol_ self.selectedProtocol = best.protocol_
self.baudRate = best.baudRate self.baudRate = best.baudRate
self.probeStatus = "Found: \(best.port.name)" self.probeStatus = "Found: \(best.port.name)"
// Auto-connect after successful probe
self.connect()
} else if let firstWorking = workingPorts.first { } else if let firstWorking = workingPorts.first {
// No response but port works - use it anyway // No response but port works - use it anyway
self.detectedDevice = firstWorking.port self.detectedDevice = firstWorking.port
self.selectedPortPath = firstWorking.port.path self.selectedPortPath = firstWorking.port.path
self.selectedProtocol = .vuServer self.selectedProtocol = .vuServer
self.baudRate = firstWorking.baudRate self.baudRate = firstWorking.baudRate
self.probeStatus = "Using: \(firstWorking.port.name) (no response)" self.probeStatus = "Using: \(firstWorking.port.name)"
// Auto-connect
self.connect()
} else { } else {
self.probeStatus = "No serial devices found" self.probeStatus = "No serial devices found"
} }