Skip to content

Commit e25c310

Browse files
fix sleep data sync.
1 parent c442a5e commit e25c310

1 file changed

Lines changed: 69 additions & 24 deletions

File tree

pebble/src/commonMain/kotlin/coredevices/pebble/health/PlatformHealthSync.kt

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,30 @@ class PlatformHealthSync(
168168
val overlays = healthDao.getOverlayEntriesAfter(lastTimestamp, allTypes)
169169
if (overlays.isEmpty()) return
170170

171-
val healthRecords = mutableListOf<HealthRecord>()
172-
173171
val sleepOverlays = overlays.filter { it.type in sleepTypes }
174172
val exerciseOverlays = overlays.filter { it.type in exerciseTypes }
175173

176-
// Build sleep sessions: group adjacent sleep overlays (within 2h gap)
177-
healthRecords += buildSleepSessions(sleepOverlays)
174+
var allSucceeded = true
175+
176+
// Write sleep sessions separately so exercise failures don't block sleep
177+
val sleepRecords = buildSleepSessions(sleepOverlays)
178+
if (sleepRecords.isNotEmpty()) {
179+
logger.d { "Writing ${sleepRecords.size} sleep sessions to health platform" }
180+
val result = healthManager.writeData(sleepRecords)
181+
if (result.isSuccess) {
182+
logger.d { "Synced ${sleepRecords.size} sleep records" }
183+
} else {
184+
allSucceeded = false
185+
logger.e { "Failed to write sleep records: ${result.exceptionOrNull()}" }
186+
}
187+
}
178188

179-
// Build exercise records
189+
// Write exercise records separately
190+
val exerciseRecords = mutableListOf<HealthRecord>()
180191
for (overlay in exerciseOverlays) {
192+
if (overlay.duration <= 0) continue
181193
val startTime = Instant.fromEpochSeconds(overlay.startTime)
182194
val endTime = startTime + overlay.duration.seconds
183-
if (overlay.duration <= 0) continue
184195

185196
val overlayType = OverlayType.fromValue(overlay.type) ?: continue
186197
val exerciseType = when (overlayType) {
@@ -190,7 +201,7 @@ class PlatformHealthSync(
190201
else -> continue
191202
}
192203

193-
healthRecords += ExerciseSessionRecord(
204+
exerciseRecords += ExerciseSessionRecord(
194205
startTime = startTime,
195206
endTime = endTime,
196207
exerciseType = exerciseType,
@@ -204,51 +215,66 @@ class PlatformHealthSync(
204215
metadata = createMetadata(overlay.startTime, "exercise"),
205216
)
206217
}
207-
208-
if (healthRecords.isNotEmpty()) {
209-
val result = healthManager.writeData(healthRecords)
218+
if (exerciseRecords.isNotEmpty()) {
219+
val result = healthManager.writeData(exerciseRecords)
210220
if (result.isSuccess) {
211-
tracker.lastSyncedOverlayTimestamp = overlays.maxOf { it.startTime }
212-
logger.d { "Synced ${healthRecords.size} sleep/exercise records" }
221+
logger.d { "Synced ${exerciseRecords.size} exercise records" }
213222
} else {
214-
logger.e { "Failed to write sleep/exercise records: ${result.exceptionOrNull()}" }
223+
allSucceeded = false
224+
logger.e { "Failed to write exercise records: ${result.exceptionOrNull()}" }
215225
}
216-
} else {
226+
}
227+
228+
// Only advance tracker if all writes succeeded (or there was nothing to write)
229+
if (allSucceeded) {
217230
tracker.lastSyncedOverlayTimestamp = overlays.maxOf { it.startTime }
218231
}
219232
}
220233

221234
private fun buildSleepSessions(overlays: List<OverlayDataEntity>): List<SleepSessionRecord> {
222235
if (overlays.isEmpty()) return emptyList()
223236

224-
val sorted = overlays.sortedBy { it.startTime }
225-
val sessions = mutableListOf<SleepSessionRecord>()
226-
var currentGroup = mutableListOf(sorted.first())
237+
// Filter to only overlays with positive duration before grouping
238+
val valid = overlays.filter { it.duration > 0 }.sortedBy { it.startTime }
239+
if (valid.isEmpty()) return emptyList()
227240

228-
for (i in 1 until sorted.size) {
241+
val groups = mutableListOf<MutableList<OverlayDataEntity>>()
242+
var currentGroup = mutableListOf(valid.first())
243+
244+
for (i in 1 until valid.size) {
229245
val prev = currentGroup.last()
230246
val prevEnd = prev.startTime + prev.duration
231-
val curr = sorted[i]
247+
val curr = valid[i]
232248

233249
// Group overlays within 2 hours of each other into one session
234250
if (curr.startTime - prevEnd <= 2 * 3600) {
235251
currentGroup.add(curr)
236252
} else {
237-
sessions += createSleepSession(currentGroup)
253+
groups += currentGroup
238254
currentGroup = mutableListOf(curr)
239255
}
240256
}
241-
sessions += createSleepSession(currentGroup)
242-
243-
return sessions
257+
groups += currentGroup
258+
259+
return groups.mapNotNull { group ->
260+
try {
261+
createSleepSession(group)
262+
} catch (e: Exception) {
263+
logger.e(e) {
264+
"Failed to create sleep session from ${group.size} overlays: " +
265+
group.joinToString { "type=${it.type},start=${it.startTime},dur=${it.duration}" }
266+
}
267+
null
268+
}
269+
}
244270
}
245271

246272
private fun createSleepSession(overlays: List<OverlayDataEntity>): SleepSessionRecord {
247273
val sessionStart = Instant.fromEpochSeconds(overlays.minOf { it.startTime })
248274
val sessionEnd = Instant.fromEpochSeconds(overlays.maxOf { it.startTime + it.duration })
249275

276+
// Build non-overlapping stages sorted by start time
250277
val stages = overlays
251-
.filter { it.duration > 0 }
252278
.map { overlay ->
253279
val stageType = when (OverlayType.fromValue(overlay.type)) {
254280
OverlayType.DeepSleep, OverlayType.DeepNap -> SleepStageType.Deep
@@ -261,6 +287,25 @@ class PlatformHealthSync(
261287
)
262288
}
263289
.sortedBy { it.startTime }
290+
.fold(mutableListOf<SleepSessionRecord.Stage>()) { acc, stage ->
291+
val prev = acc.lastOrNull()
292+
if (prev != null && stage.startTime < prev.endTime) {
293+
// Overlapping stage — trim its start to previous end, or skip if fully contained
294+
if (stage.endTime > prev.endTime) {
295+
acc += SleepSessionRecord.Stage(
296+
startTime = prev.endTime,
297+
endTime = stage.endTime,
298+
type = stage.type,
299+
)
300+
}
301+
// else: fully contained, skip
302+
} else {
303+
acc += stage
304+
}
305+
acc
306+
}
307+
308+
logger.d { "Sleep session: ${stages.size} stages, start=$sessionStart, end=$sessionEnd" }
264309

265310
return SleepSessionRecord(
266311
startTime = sessionStart,

0 commit comments

Comments
 (0)