@@ -7,8 +7,12 @@ import com.evsuite.tasker.engine.RuleEngine
77import com.evsuite.tasker.store.HistoryStore
88import com.evsuite.tasker.store.RuleStore
99import com.evsuite.tasker.model.EngineRun
10+ import com.evsuite.tasker.model.Rule
1011import com.evsuite.tasker.util.BtDevices
1112import com.evsuite.tasker.util.CarLocation
13+ import com.evsuite.tasker.util.WallClock
14+ import android.os.SystemClock
15+ import kotlin.concurrent.thread
1216
1317/* *
1418 * Bluetooth devices currently connected to the vehicle.
@@ -115,19 +119,29 @@ object RuleCycle {
115119 const val MANUAL = " MANUAL"
116120 const val PHYSICAL_BUTTON = " PHYSICAL_BUTTON"
117121
122+ /* *
123+ * How long rules that read the clock wait for it after boot. The head unit corrects its
124+ * RTC default from the network or GPS; past this, the rules run anyway and the history
125+ * shows them not evaluable for `CLOCK_UNSET` rather than leaving no trace.
126+ */
127+ private const val CLOCK_WAIT_MS = 5 * 60_000L
128+ private const val CLOCK_POLL_MS = 2_000L
129+
118130 // A P cycle can still be closing glass when ignition-off arrives. Keep whole cycles,
119131 // not just individual writes, atomic against every other trigger.
120132 @Synchronized
121133 fun run (
122134 context : Context ,
123135 trigger : String ,
124136 eventReadings : Map <String , Any > = emptyMap(),
125- ruleId : String? = null
137+ ruleId : String? = null,
138+ /* * The rules held back by [waitForClock], run once it is over. */
139+ clockWaited : Set <String >? = null
126140 ) {
127141 val all = RuleStore (context).getAll()
128142 // Rules wired to a different event are not "skipped", they were never addressed: the
129143 // history would otherwise fill with a line per rule per ignition saying nothing.
130- val rules = when (trigger) {
144+ var rules = when (trigger) {
131145 MANUAL -> all.filter { it.id == ruleId }
132146 PHYSICAL_BUTTON -> all.filter { it.hasPhysicalButtonCondition }
133147 // A position event belongs to the one rule whose place it is: its neighbours are
@@ -137,10 +151,20 @@ object RuleCycle {
137151 (ruleId == null || it.id == ruleId)
138152 }
139153 }
154+ if (clockWaited != null ) rules = rules.filter { it.id in clockWaited }
140155 if (rules.isEmpty()) {
141156 AppLogger .i(TAG , " no rules for $trigger (${all.size} total) — skipped" )
142157 return
143158 }
159+ // A manual test answers now, clock or not: the user is looking at the result.
160+ if (trigger != MANUAL && clockWaited == null && ! WallClock .isSet()) {
161+ val (needClock, now) = rules.partition(::readsClock)
162+ if (needClock.isNotEmpty()) {
163+ waitForClock(context.applicationContext, trigger, eventReadings, needClock.map { it.id }.toSet())
164+ }
165+ rules = now
166+ if (rules.isEmpty()) return
167+ }
144168
145169 val startedAt = System .currentTimeMillis()
146170 val profileBridge = ProfileBridge (context).takeIf { it.connect() }
@@ -180,4 +204,40 @@ object RuleCycle {
180204 profileBridge?.disconnect()
181205 }
182206 }
207+
208+ /* * Whether any case of [rule], "else if" ones included, asks the time, weekday or date. */
209+ internal fun readsClock (rule : Rule ): Boolean =
210+ rule.branches.any { branch -> branch.conditions.any { it.type in WallClock .CONDITIONS } }
211+
212+ /* *
213+ * Runs [ruleIds] again once the head unit's clock is set.
214+ *
215+ * At boot the clock reads 2019-01-01 until the network or GPS corrects it, and the
216+ * ignition cycle runs before that. Rules that read the time, the weekday or the date
217+ * wait for it; the others ran already and are not held up. Abandoned if the ignition
218+ * changes meanwhile: a start-of-drive rule must not fire after the drive is over. Waits
219+ * on elapsed time, because the wall clock is precisely what jumps.
220+ */
221+ private fun waitForClock (
222+ context : Context ,
223+ trigger : String ,
224+ eventReadings : Map <String , Any >,
225+ ruleIds : Set <String >
226+ ) {
227+ val ignition = EVHardware .getCurrentIgnitionState()
228+ AppLogger .i(TAG , " clock not set — ${ruleIds.size} $trigger rule(s) wait for it" )
229+ thread(name = " mg4-tasker-clock-wait" ) {
230+ val start = SystemClock .elapsedRealtime()
231+ while (! WallClock .isSet() && SystemClock .elapsedRealtime() - start < CLOCK_WAIT_MS ) {
232+ Thread .sleep(CLOCK_POLL_MS )
233+ }
234+ val waitedS = (SystemClock .elapsedRealtime() - start) / 1000
235+ if (EVHardware .getCurrentIgnitionState() != ignition) {
236+ AppLogger .i(TAG , " ignition changed during the ${waitedS} s clock wait — $trigger rules dropped" )
237+ return @thread
238+ }
239+ AppLogger .i(TAG , " clock ${if (WallClock .isSet()) " set" else " still not set" } after ${waitedS} s — running held $trigger rules" )
240+ run (context, trigger, eventReadings, clockWaited = ruleIds)
241+ }
242+ }
183243}
0 commit comments