feat: async C API and Swift radio state display
Split the dongle session into a fast probe (xone_open) and a background start (xone_start) that loads the firmware and initializes the radio on a worker thread, so the app UI never blocks. Add xone_state/xone_error for progress display, and select the firmware image per product ID when no path is given. The Swift app polls the state each second and shows idle/starting/ready/error plus the firmware build or error string. The session destructor joins the worker so quitting the app does not terminate on a joinable thread. Note in the CLI and transport that the recover/re-enumerate path should not be used yet: a crashed SIE drops off the bus, while the MCU watchdog recovers it after ~90s of quiet. Co-Authored-By: qwen3.8-27b@q2_k_xl: async C API, Swift state display, and exit-time worker join
This commit is contained in:
+28
-2
@@ -27,6 +27,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
struct ContentView: View {
|
||||
@State private var donglePresent = false
|
||||
@State private var session: OpaquePointer? = nil
|
||||
// Radio state polled from the background worker each tick; a change here
|
||||
// is what re-renders the debug section.
|
||||
@State private var radioState = XONE_STATE_IDLE
|
||||
|
||||
private let timer = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
@@ -69,10 +72,18 @@ struct ContentView: View {
|
||||
.onReceive(timer) { _ in
|
||||
let present = xone_dongle_present()
|
||||
if present && session == nil {
|
||||
session = xone_open("firmware/xow_dongle.bin")
|
||||
// Probe is fast; firmware load + radio init run on a
|
||||
// background thread so the UI never blocks.
|
||||
if let s = xone_open() {
|
||||
session = s
|
||||
xone_start(s, nil)
|
||||
radioState = Int(xone_state(s))
|
||||
}
|
||||
} else if !present, let s = session {
|
||||
xone_close(s)
|
||||
session = nil
|
||||
} else if let s = session {
|
||||
radioState = Int(xone_state(s))
|
||||
}
|
||||
donglePresent = present
|
||||
}
|
||||
@@ -85,7 +96,12 @@ struct ContentView: View {
|
||||
debugRow("PID", String(format: "0x%04X", xone_pid(session)))
|
||||
debugRow("Chip ID", String(format: "0x%04X", xone_chip_id(session)))
|
||||
debugRow("MAC", String(cString: xone_mac_address(session)))
|
||||
debugRow("Firmware", firmwareBuildText(session))
|
||||
debugRow("State", stateText(radioState))
|
||||
if radioState == XONE_STATE_READY {
|
||||
debugRow("Firmware", firmwareBuildText(session))
|
||||
} else if radioState == XONE_STATE_ERROR {
|
||||
debugRow("Error", String(cString: xone_error(session)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +115,16 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func stateText(_ state: Int) -> String {
|
||||
switch state {
|
||||
case XONE_STATE_IDLE: return "idle"
|
||||
case XONE_STATE_STARTING: return "starting..."
|
||||
case XONE_STATE_READY: return "ready"
|
||||
case XONE_STATE_ERROR: return "error"
|
||||
default: return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private func firmwareBuildText(_ session: OpaquePointer) -> String {
|
||||
let build = String(cString: xone_firmware_build(session))
|
||||
return build.isEmpty ? "not loaded" : build
|
||||
|
||||
Reference in New Issue
Block a user