@@ -4,12 +4,12 @@ import fetchStatistics from "./fetch-statistics";
4
4
import fetchStates from "./fetch-states" ;
5
5
import {
6
6
TimestampRange ,
7
- History ,
8
7
isEntityIdAttrConfig ,
9
8
EntityConfig ,
10
9
isEntityIdStateConfig ,
11
10
isEntityIdStatisticsConfig ,
12
11
HistoryInRange ,
12
+ EntityState ,
13
13
} from "../types" ;
14
14
15
15
export function mapValues < T , S > (
@@ -25,13 +25,14 @@ async function fetchSingleRange(
25
25
significant_changes_only : boolean ,
26
26
minimal_response : boolean
27
27
) : Promise < HistoryInRange > {
28
- const start = new Date ( startT ) ;
28
+ const start = new Date ( startT - 1 ) ;
29
+ endT = Math . min ( endT , Date . now ( ) ) ;
29
30
const end = new Date ( endT ) ;
30
- let historyInRange : HistoryInRange ;
31
+ let history : EntityState [ ] ;
31
32
if ( isEntityIdStatisticsConfig ( entity ) ) {
32
- historyInRange = await fetchStatistics ( hass , entity , [ start , end ] ) ;
33
+ history = await fetchStatistics ( hass , entity , [ start , end ] ) ;
33
34
} else {
34
- historyInRange = await fetchStates (
35
+ history = await fetchStates (
35
36
hass ,
36
37
entity ,
37
38
[ start , end ] ,
@@ -40,25 +41,19 @@ async function fetchSingleRange(
40
41
) ;
41
42
}
42
43
43
- const { history, range } = historyInRange ;
44
44
/*
45
- home assistant will "invent" a datapoiont at startT with the previous known value, except if there is actually one at startT.
46
- To avoid these duplicates, the "fetched range" is capped to end at the last known point instead of endT .
47
- This ensures that the next fetch will start with a duplicate of the last known datapoint, which can then be removed.
48
- On top of that, in order to ensure that the last known point is extended to endT, I duplicate the last datapoint
49
- and set its date to endT .
45
+ home assistant will "invent" a datapoiont at startT with the previous
46
+ known value, except if there is actually one at startT .
47
+ To avoid these duplicates, the "fetched range" starts at startT-1,
48
+ but the first point is marked to be deleted (fake_boundary_datapoint).
49
+ Delettion occurs when merging the fetched range inside the cached history .
50
50
*/
51
+ let range : [ number , number ] = [ startT , endT ] ;
51
52
if ( history . length ) {
52
- const last = history [ history . length - 1 ] ;
53
- const dup = JSON . parse ( JSON . stringify ( last ) ) ;
54
- history [ 0 ] . duplicate_datapoint = true ;
55
- dup . duplicate_datapoint = true ;
56
- dup . last_updated = Math . min ( + end , Date . now ( ) ) ;
57
- history . push ( dup ) ;
53
+ history [ 0 ] . fake_boundary_datapoint = true ;
58
54
}
59
- Math . min ( + end , Date . now ( ) ) ;
60
55
return {
61
- range : [ range [ 0 ] , Math . min ( range [ 1 ] , Date . now ( ) ) ] ,
56
+ range,
62
57
history,
63
58
} ;
64
59
}
@@ -75,8 +70,22 @@ export function getEntityKey(entity: EntityConfig) {
75
70
}
76
71
export default class Cache {
77
72
ranges : Record < string , TimestampRange [ ] > = { } ;
78
- histories : Record < string , History > = { } ;
73
+ histories : Record < string , EntityState [ ] > = { } ;
79
74
busy = Promise . resolve ( ) ; // mutex
75
+
76
+ add ( entity : EntityConfig , states : EntityState [ ] , range : [ number , number ] ) {
77
+ const entityKey = getEntityKey ( entity ) ;
78
+ let h = ( this . histories [ entityKey ] ??= [ ] ) ;
79
+ h . push ( ...states ) ;
80
+ h . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
81
+ h = h . filter ( ( x , i ) => i == 0 || ! x . fake_boundary_datapoint ) ;
82
+ h = h . filter ( ( _ , i ) => h [ i - 1 ] ?. timestamp !== h [ i ] . timestamp ) ;
83
+ this . histories [ entityKey ] = h ;
84
+ this . ranges [ entityKey ] ??= [ ] ;
85
+ this . ranges [ entityKey ] . push ( range ) ;
86
+ this . ranges [ entityKey ] = compactRanges ( this . ranges [ entityKey ] ) ;
87
+ }
88
+
80
89
clearCache ( ) {
81
90
this . ranges = { } ;
82
91
this . histories = { } ;
@@ -112,47 +121,34 @@ export default class Cache {
112
121
minimal_response
113
122
) ;
114
123
if ( fetchedHistory === null ) continue ;
115
- let h = ( this . histories [ entityKey ] ??= [ ] ) ;
116
- h . push ( ...fetchedHistory . history ) ;
117
- h . sort ( ( a , b ) => a . last_updated - b . last_updated ) ;
118
- h = h . filter (
119
- ( x , i ) => i == 0 || i == h . length - 1 || ! x . duplicate_datapoint
120
- ) ;
121
- h = h . filter (
122
- ( _ , i ) => h [ i ] . last_updated !== h [ i + 1 ] ?. last_updated
123
- ) ;
124
- this . histories [ entityKey ] = h ;
125
- this . ranges [ entityKey ] . push ( fetchedHistory . range ) ;
126
- this . ranges [ entityKey ] = compactRanges ( this . ranges [ entityKey ] ) ;
124
+ this . add ( entity , fetchedHistory . history , fetchedHistory . range ) ;
127
125
}
128
126
} ) ;
129
127
130
128
await Promise . all ( promises ) ;
131
129
} ) ) ;
132
130
}
133
131
134
- private removeOutsideRange ( range : TimestampRange ) {
132
+ removeOutsideRange ( range : TimestampRange ) {
135
133
this . ranges = mapValues ( this . ranges , ( ranges ) =>
136
134
subtractRanges ( ranges , [
137
135
[ Number . NEGATIVE_INFINITY , range [ 0 ] - 1 ] ,
138
136
[ range [ 1 ] + 1 , Number . POSITIVE_INFINITY ] ,
139
137
] )
140
138
) ;
141
139
this . histories = mapValues ( this . histories , ( history ) => {
142
- let first : History [ 0 ] | undefined ;
143
- let last : History [ 0 ] | undefined ;
140
+ let first : EntityState | undefined ;
141
+ let last : EntityState | undefined ;
144
142
const newHistory = history . filter ( ( datum ) => {
145
- if ( datum . last_updated <= range [ 0 ] ) first = datum ;
146
- else if ( ! last && datum . last_updated >= range [ 1 ] ) last = datum ;
143
+ if ( datum . timestamp <= range [ 0 ] ) first = datum ;
144
+ else if ( ! last && datum . timestamp >= range [ 1 ] ) last = datum ;
147
145
else return true ;
148
146
return false ;
149
147
} ) ;
150
148
if ( first ) {
151
- first . last_updated = range [ 0 ] ;
152
149
newHistory . unshift ( first ) ;
153
150
}
154
151
if ( last ) {
155
- last . last_updated = range [ 1 ] ;
156
152
newHistory . push ( last ) ;
157
153
}
158
154
return newHistory ;
0 commit comments