feat(watchos): mode Test (FC simulée) — valide le flux sans HealthKit

Bouton "Test (FC simulée)" qui envoie des samples synthétiques (FC ~110-150,
calories, distance, durée) via WCSession toutes les 2s, sans démarrer de séance
HealthKit. Permet de valider watch -> iPhone -> /live indépendamment de
l'autorisation Santé (bloquée par un sous-système Santé coincé sur la Watch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain
2026-05-25 20:21:24 +02:00
parent fef7781db7
commit 3ce8ca9e28
2 changed files with 51 additions and 2 deletions

View File

@@ -45,10 +45,18 @@ struct ActivityPickerView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
}
Button {
workout.startSimulation()
} label: {
Label("Test (FC simulée)", systemImage: "waveform.path.ecg")
.frame(maxWidth: .infinity, alignment: .leading)
}
.tint(.gray)
if let message = workout.statusMessage {
Text(message)
.font(.footnote)
.foregroundStyle(.red)
.foregroundStyle(.secondary)
}
}
.padding(.horizontal, 4)

View File

@@ -24,6 +24,7 @@ final class WorkoutManager: NSObject, ObservableObject {
private var lastSentAt = Date.distantPast
private let sendInterval: TimeInterval = 2
private var simTask: Task<Void, Never>?
private override init() { super.init() }
@@ -86,7 +87,47 @@ final class WorkoutManager: NSObject, ObservableObject {
func pause() { session?.pause() }
func resume() { session?.resume() }
func end() { session?.end() }
func end() {
if let simTask {
simTask.cancel()
self.simTask = nil
isRunning = false
statusMessage = nil
} else {
session?.end()
}
}
/// Mode test : FC simulée, sans HealthKit, pour valider le flux watch -> iPhone -> /live.
func startSimulation() {
guard !isRunning else { return }
isRunning = true
statusMessage = "Mode test — FC simulée"
let startedAt = Date()
simTask = Task { [weak self] in
while !Task.isCancelled {
guard let self else { return }
let elapsed = Date().timeIntervalSince(startedAt)
let hr = 110 + 40 * abs(sin(elapsed / 30))
let kcal = elapsed / 6
let dist = elapsed * 2.6
self.heartRate = hr
self.activeEnergyKcal = kcal
self.distanceMeters = dist
self.elapsedSec = elapsed
ConnectivityManager.shared.sendSample([
"heartRate": hr,
"activeEnergyKcal": kcal,
"distanceMeters": dist,
"elapsedSec": elapsed,
"ts": Date().timeIntervalSince1970,
"sim": true
])
try? await Task.sleep(nanoseconds: 2_000_000_000)
}
}
}
/// Appelé sur le main depuis le callback de collecte (hors-main).
private func ingest(hr: Double?, kcal: Double?, dist: Double?, elapsed: TimeInterval) {