@@ -4,8 +4,11 @@ import { listenMediaQuery } from "../../../common/dom/media_query";
4
4
import { isValidEntityId } from "../../../common/entity/valid_entity_id" ;
5
5
import { UNKNOWN } from "../../../data/entity" ;
6
6
import type { HomeAssistant } from "../../../types" ;
7
+ import { createDurationData } from "../../../common/datetime/create_duration_data" ;
8
+ import type { HaDurationData } from "../../../components/ha-duration-input" ;
7
9
8
10
export type Condition =
11
+ | LastUpdatedStateCondition
9
12
| NumericStateCondition
10
13
| StateCondition
11
14
| ScreenCondition
@@ -24,6 +27,13 @@ interface BaseCondition {
24
27
condition : string ;
25
28
}
26
29
30
+ export interface LastUpdatedStateCondition extends BaseCondition {
31
+ condition : "last_updated_state" ;
32
+ entity ?: string ;
33
+ within ?: HaDurationData ;
34
+ after ?: HaDurationData ;
35
+ }
36
+
27
37
export interface NumericStateCondition extends BaseCondition {
28
38
condition : "numeric_state" ;
29
39
entity ?: string ;
@@ -132,6 +142,62 @@ function checkStateNumericCondition(
132
142
) ;
133
143
}
134
144
145
+ function checkLastUpdatedStateCondition (
146
+ condition : LastUpdatedStateCondition ,
147
+ hass : HomeAssistant
148
+ ) {
149
+ const state_last_changed = (
150
+ condition . entity ? hass . states [ condition . entity ] : undefined
151
+ ) ?. last_changed ;
152
+ const within = condition . within ;
153
+ const after = condition . after ;
154
+
155
+ function HaDurationData_to_milliseconds (
156
+ duration : HaDurationData | undefined
157
+ ) {
158
+ // This function should not be here, and surely something like this already exists?
159
+ // If so, I can't find it :'(
160
+ if ( duration ) {
161
+ const days = duration . days || 0 ;
162
+ let hours = duration . hours || 0 ;
163
+ let minutes = duration . minutes || 0 ;
164
+ let seconds = duration . seconds || 0 ;
165
+ let milliseconds = duration . milliseconds || 0 ;
166
+
167
+ hours += days * 24 ;
168
+ minutes += hours * 60 ;
169
+ seconds += minutes * 60 ;
170
+ milliseconds += seconds * 1000 ;
171
+
172
+ return milliseconds ;
173
+ }
174
+ return 0 ; // this also is probably not good
175
+ }
176
+
177
+ const withinDuration = HaDurationData_to_milliseconds (
178
+ createDurationData ( within )
179
+ ) ;
180
+ const afterDuration = HaDurationData_to_milliseconds (
181
+ createDurationData ( after )
182
+ ) ;
183
+
184
+ const numericLastUpdatedState = new Date ( state_last_changed ) . getTime ( ) ;
185
+ const numericWithin = numericLastUpdatedState + withinDuration ;
186
+ const numericAfter = numericLastUpdatedState + afterDuration ;
187
+
188
+ if ( isNaN ( numericLastUpdatedState ) ) {
189
+ return false ;
190
+ }
191
+
192
+ const now = new Date ( ) . getTime ( ) ;
193
+ return (
194
+ ( condition . within == null ||
195
+ isNaN ( numericWithin ) ||
196
+ now <= numericWithin ) &&
197
+ ( condition . after == null || isNaN ( numericAfter ) || now > numericAfter )
198
+ ) ;
199
+ }
200
+
135
201
function checkScreenCondition ( condition : ScreenCondition , _ : HomeAssistant ) {
136
202
return condition . media_query
137
203
? matchMedia ( condition . media_query ) . matches
@@ -173,6 +239,8 @@ export function checkConditionsMet(
173
239
return checkUserCondition ( c , hass ) ;
174
240
case "numeric_state" :
175
241
return checkStateNumericCondition ( c , hass ) ;
242
+ case "last_updated_state" :
243
+ return checkLastUpdatedStateCondition ( c , hass ) ;
176
244
case "and" :
177
245
return checkAndCondition ( c , hass ) ;
178
246
case "or" :
@@ -206,6 +274,22 @@ export function extractConditionEntityIds(
206
274
) {
207
275
entityIds . add ( condition . below ) ;
208
276
}
277
+ } else if ( condition . condition === "last_updated_state" ) {
278
+ if ( condition . entity ) {
279
+ entityIds . add ( condition . entity ) ;
280
+ }
281
+ if (
282
+ typeof condition . within === "string" &&
283
+ isValidEntityId ( condition . within )
284
+ ) {
285
+ entityIds . add ( condition . within ) ;
286
+ }
287
+ if (
288
+ typeof condition . after === "string" &&
289
+ isValidEntityId ( condition . after )
290
+ ) {
291
+ entityIds . add ( condition . after ) ;
292
+ }
209
293
} else if ( condition . condition === "state" ) {
210
294
if ( condition . entity ) {
211
295
entityIds . add ( condition . entity ) ;
@@ -257,6 +341,14 @@ function validateNumericStateCondition(condition: NumericStateCondition) {
257
341
( condition . above != null || condition . below != null )
258
342
) ;
259
343
}
344
+ function validateLastUpdatedStateCondition (
345
+ condition : LastUpdatedStateCondition
346
+ ) {
347
+ return (
348
+ condition . entity != null &&
349
+ ( condition . within != null || condition . after != null )
350
+ ) ;
351
+ }
260
352
/**
261
353
* Validate the conditions config for the UI
262
354
* @param conditions conditions to apply
@@ -274,6 +366,8 @@ export function validateConditionalConfig(
274
366
return validateUserCondition ( c ) ;
275
367
case "numeric_state" :
276
368
return validateNumericStateCondition ( c ) ;
369
+ case "last_updated_state" :
370
+ return validateLastUpdatedStateCondition ( c ) ;
277
371
case "and" :
278
372
return validateAndCondition ( c ) ;
279
373
case "or" :
@@ -307,7 +401,8 @@ export function addEntityToCondition(
307
401
308
402
if (
309
403
condition . condition === "state" ||
310
- condition . condition === "numeric_state"
404
+ condition . condition === "numeric_state" ||
405
+ condition . condition === "last_updated_state"
311
406
) {
312
407
return {
313
408
entity : entityId ,
0 commit comments