fix(scene): address automation review findings
This commit is contained in:
@@ -40,6 +40,8 @@ dependencies {
|
||||
kapt(libs.hilt.compiler)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.hilt.work)
|
||||
kapt(libs.androidx.hilt.compiler)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.material3)
|
||||
|
||||
+22
-6
@@ -1,14 +1,18 @@
|
||||
package com.flagship.abox.manager.automation.runtime
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import com.flagship.abox.manager.domain.ProviderProfileStore
|
||||
import com.flagship.abox.manager.domain.SessionCoordinator
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.delay
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
class AutomationBootstrapper(
|
||||
private val profileStore: ProviderProfileStore,
|
||||
@@ -16,20 +20,24 @@ class AutomationBootstrapper(
|
||||
private val scheduler: AutomationScheduler,
|
||||
private val engine: AutomationEngine,
|
||||
private val scope: CoroutineScope,
|
||||
) {
|
||||
) : DefaultLifecycleObserver {
|
||||
private var previousProfileId: ProviderProfileId? = null
|
||||
private val isForeground = MutableStateFlow(false)
|
||||
|
||||
fun start() {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
scope.launch {
|
||||
combine(
|
||||
profileStore.observeActiveProfile(),
|
||||
sessionCoordinator.observeActiveSession(),
|
||||
) { active, session -> active to session }.collectLatest { (active, session) ->
|
||||
isForeground,
|
||||
) { active, session, foreground -> Triple(active, session, foreground) }
|
||||
.collectLatest { (active, session, foreground) ->
|
||||
previousProfileId?.takeIf { it != active?.id }?.let { scheduler.cancel(it) }
|
||||
previousProfileId = active?.id
|
||||
if (active != null && session?.profileId == active.id) {
|
||||
scheduler.reconcile(active.id)
|
||||
while (true) {
|
||||
while (foreground) {
|
||||
try {
|
||||
engine.evaluate(active.id)
|
||||
} catch (cancelled: CancellationException) {
|
||||
@@ -43,4 +51,12 @@ class AutomationBootstrapper(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
isForeground.value = true
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
isForeground.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +65,16 @@ class SceneTriggerEvaluator {
|
||||
private fun evaluateOne(trigger: SceneTrigger, snapshot: AutomationSnapshot, now: LocalDateTime): TriggerEvaluation? = when (trigger) {
|
||||
is SceneTrigger.Time -> {
|
||||
val time = parseTime(trigger.timeOfDay) ?: return TriggerEvaluation.Unavailable(DomainError.InvalidRequest(detail = "Invalid trigger time"))
|
||||
val dayMatches = trigger.daysOfWeek.isEmpty() || now.dayOfWeek.value in trigger.daysOfWeek
|
||||
val scheduled = LocalDateTime.of(now.toLocalDate(), time)
|
||||
val lateness = Duration.between(scheduled, now).toMinutes()
|
||||
if (dayMatches && lateness in 0..20) {
|
||||
TriggerEvaluation.Matched("${now.toLocalDate()}:${trigger.timeOfDay}:${trigger.id.value}")
|
||||
} else null
|
||||
val scheduled = listOf(now.toLocalDate(), now.toLocalDate().minusDays(1))
|
||||
.map { date -> LocalDateTime.of(date, time) }
|
||||
.firstOrNull { candidate ->
|
||||
val dayMatches = trigger.daysOfWeek.isEmpty() || candidate.dayOfWeek.value in trigger.daysOfWeek
|
||||
val lateness = Duration.between(candidate, now).toMinutes()
|
||||
dayMatches && lateness in 0..TIME_TRIGGER_GRACE_MINUTES
|
||||
}
|
||||
scheduled?.let { occurrence ->
|
||||
TriggerEvaluation.Matched("${occurrence.toLocalDate()}:${trigger.timeOfDay}:${trigger.id.value}")
|
||||
}
|
||||
}
|
||||
is SceneTrigger.Temperature -> threshold(snapshot.readings[trigger.deviceName], trigger.operator, trigger.thresholdCelsius) {
|
||||
it.temperature
|
||||
@@ -134,6 +138,7 @@ class SceneTriggerEvaluator {
|
||||
private companion object {
|
||||
const val TEMPERATURE_HYSTERESIS = 0.5
|
||||
const val HUMIDITY_HYSTERESIS = 2.0
|
||||
const val TIME_TRIGGER_GRACE_MINUTES = 20L
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-
|
||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
||||
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltNavigationCompose" }
|
||||
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltNavigationCompose" }
|
||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
|
||||
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
|
||||
androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" }
|
||||
androidx-compose-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "adaptiveNavigationSuite" }
|
||||
androidx-window = { module = "androidx.window:window", version.ref = "window" }
|
||||
|
||||
Reference in New Issue
Block a user