Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions Apps/MetaWear/MetaWear/Features/Settings/DeviceSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ struct DeviceSettingsView: View {
@State private var activeLoggerCount: Int?
@State private var isClearing = false
@State private var clearLogError: AppError?
@State private var showClearMacrosConfirm = false
@State private var showClearEventsConfirm = false
@State private var showRestartConfirm = false
@State private var maintenanceError: AppError?

var body: some View {
Form {
Expand Down Expand Up @@ -69,6 +73,25 @@ struct DeviceSettingsView: View {
Text("Stops any active logging, drops every entry from flash, and removes all logger subscriptions. Local pending sessions for this device are also deleted (their backing data on the board is gone).")
}

Section {
Button("Reset LED", systemImage: "lightbulb.slash") {
Task { await resetLED() }
}
Button("Clear Macros", systemImage: "memorychip", role: .destructive) {
showClearMacrosConfirm = true
}
Button("Clear Events & Timers", systemImage: "calendar.badge.minus", role: .destructive) {
showClearEventsConfirm = true
}
Button("Restart Board", systemImage: "arrow.counterclockwise") {
showRestartConfirm = true
}
} header: {
Text("Maintenance")
} footer: {
Text("Reset LED stops and clears the light immediately — if it relights after a disconnect, an on-board event is re-arming it; use Clear Events & Timers. Restart reboots the board without erasing logs or macros.")
}

Section {
Button("Factory Reset", systemImage: "exclamationmark.triangle", role: .destructive) {
showFactoryResetConfirm = true
Expand Down Expand Up @@ -120,6 +143,38 @@ struct DeviceSettingsView: View {
Text("Any logger subscriptions and pending entries will be removed from the board.")
}
}
.alert("Clear all macros on this board?",
isPresented: $showClearMacrosConfirm) {
Button("Clear Macros", role: .destructive) {
Task { await clearMacros() }
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Removes every on-boot macro — including the identity broadcast this app configures. The app re-applies its broadcast automatically on the next connect.")
}
.alert("Clear all events and timers?",
isPresented: $showClearEventsConfirm) {
Button("Clear Events & Timers", role: .destructive) {
Task { await clearEventsAndTimers() }
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Removes every on-board event binding and timer — from ALL apps, including this one's recording heartbeat. Use this if the LED keeps relighting after disconnects.")
}
.alert("Restart this MetaWear?",
isPresented: $showRestartConfirm) {
Button("Restart") {
Task { await restartBoard() }
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Reboots the board without erasing anything — logs and macros survive. The board will disconnect.")
}
.alert(item: $maintenanceError) { err in
Alert(title: Text("Maintenance failed"),
message: Text(err.message),
dismissButton: .default(Text("OK")))
}
.alert(item: $clearLogError) { err in
Alert(title: Text("Clear logs failed"),
message: Text(err.message),
Expand Down Expand Up @@ -170,6 +225,52 @@ struct DeviceSettingsView: View {
}
}

// MARK: - Maintenance actions

/// Stop playback and clear all configured patterns. If the LED comes
/// back after a disconnect, an on-board disconnect EVENT is re-arming
/// it — that's what Clear Events & Timers is for.
private func resetLED() async {
guard let device = appStore.activeDevice else { return }
do {
try await device.stopLED(clearPattern: true)
} catch {
maintenanceError = AppError(error: error)
}
}

private func clearMacros() async {
guard let device = appStore.activeDevice else { return }
do {
try await device.eraseAllMacros()
} catch {
maintenanceError = AppError(error: error)
}
}

private func clearEventsAndTimers() async {
guard let device = appStore.activeDevice else { return }
do {
try await device.removeAllEvents()
try await device.removeAllTimers()
} catch {
maintenanceError = AppError(error: error)
}
}

/// Soft reboot — the board disconnects itself; the same intentional-
/// disconnect handling as Factory Reset takes the user back to the
/// scan list.
private func restartBoard() async {
guard let device = appStore.activeDevice else { return }
do {
try await device.restart()
await appStore.disconnect()
} catch {
maintenanceError = AppError(error: error)
}
}

private func deleteLocalPendingRecords(for deviceID: UUID) {
let context = appStore.containers.local.mainContext
let descriptor = FetchDescriptor<LogSessionRecord>(
Expand Down
25 changes: 25 additions & 0 deletions Sources/MetaWear/MetaWearDevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,31 @@ public actor MetaWearDevice {
mwLog("[Device] factoryReset: done")
}

/// Reboot the board WITHOUT erasing anything. Flash-resident data —
/// log entries, macros — survives; volatile state (timers, events,
/// sensor enables, a wedged NAND housekeeping pass) is cleared, which
/// is the point: this is the unwedge for a misbehaving board when a
/// factory reset would cost real data. The link drops intentionally;
/// reconnect after ~1 s brings the board back.
public func restart() async throws {
mwLog("[Device] restart: \(identifier)")
guard state != .disconnected else {
throw MWError.invalidState("Cannot restart a disconnected device")
}
// Intentional link drop — same suppression + reset pair (and the
// same MMS fw-1.5.0 ResetAfterGC-no-op fallback) as factoryReset.
await proto.clearDisconnectHandler()
try await send(MWDebug.ResetAfterGC())
try? await send(MWDebug.Reset())
await proto.stop()
state = .disconnected
activeStreamKeys.removeAll()
activeFusionConfig = nil
loggerRegistry.removeAll()
logReferenceDate = nil
mwLog("[Device] restart: done")
}

// MARK: - Streaming

/// Stream a sensor signal continuously.
Expand Down
Loading