homebridge-verisure 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +33 -2
  2. package/config.schema.json +30 -0
  3. package/dist/accessories/alarm.d.ts +28 -0
  4. package/dist/accessories/alarm.js +129 -0
  5. package/dist/accessories/alarm.js.map +1 -0
  6. package/dist/accessories/base.d.ts +39 -0
  7. package/dist/accessories/base.js +63 -0
  8. package/dist/accessories/base.js.map +1 -0
  9. package/dist/accessories/climateSensor.d.ts +14 -0
  10. package/dist/accessories/climateSensor.js +60 -0
  11. package/dist/accessories/climateSensor.js.map +1 -0
  12. package/dist/accessories/contactSensor.d.ts +14 -0
  13. package/dist/accessories/contactSensor.js +39 -0
  14. package/dist/accessories/contactSensor.js.map +1 -0
  15. package/dist/accessories/doorLock.d.ts +32 -0
  16. package/dist/accessories/doorLock.js +162 -0
  17. package/dist/accessories/doorLock.js.map +1 -0
  18. package/dist/accessories/smartPlug.d.ts +15 -0
  19. package/dist/accessories/smartPlug.js +54 -0
  20. package/dist/accessories/smartPlug.js.map +1 -0
  21. package/dist/bin/verisure.d.ts +2 -0
  22. package/dist/bin/verisure.js +56 -0
  23. package/dist/bin/verisure.js.map +1 -0
  24. package/dist/cache.d.ts +14 -0
  25. package/dist/cache.js +35 -0
  26. package/dist/cache.js.map +1 -0
  27. package/dist/deviceNames.d.ts +3 -0
  28. package/dist/deviceNames.js +16 -0
  29. package/dist/deviceNames.js.map +1 -0
  30. package/dist/i18n/index.d.ts +1 -0
  31. package/dist/i18n/index.js +18 -0
  32. package/dist/i18n/index.js.map +1 -0
  33. package/dist/i18n/nb_NO.json +22 -0
  34. package/dist/i18n/sv_SE.json +22 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.js +7 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/operations.d.ts +27 -0
  39. package/dist/operations.js +322 -0
  40. package/dist/operations.js.map +1 -0
  41. package/dist/overviewPoller.d.ts +53 -0
  42. package/dist/overviewPoller.js +169 -0
  43. package/dist/overviewPoller.js.map +1 -0
  44. package/dist/platform.d.ts +27 -0
  45. package/dist/platform.js +272 -0
  46. package/dist/platform.js.map +1 -0
  47. package/dist/settings.d.ts +21 -0
  48. package/dist/settings.js +25 -0
  49. package/dist/settings.js.map +1 -0
  50. package/dist/types.d.ts +117 -0
  51. package/dist/types.js +12 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +39 -16
  54. package/CHANGELOG.md +0 -180
  55. package/bin/verisure +0 -55
  56. package/index.js +0 -6
  57. package/lib/accessories/alarm.js +0 -111
  58. package/lib/accessories/climatesensor.js +0 -68
  59. package/lib/accessories/contactsensor.js +0 -45
  60. package/lib/accessories/doorlock.js +0 -216
  61. package/lib/accessories/index.js +0 -13
  62. package/lib/accessories/smartplug.js +0 -60
  63. package/lib/accessories/verisure.js +0 -62
  64. package/lib/i18n/index.js +0 -11
  65. package/lib/i18n/nb_NO.json +0 -22
  66. package/lib/i18n/sv_SE.json +0 -22
  67. package/lib/operations.js +0 -225
  68. package/lib/platform.js +0 -156
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doorLockUpdateConfigOperation = exports.doorLockConfigOperation = exports.pollLockStateOperation = exports.doorUnlockOperation = exports.doorLockOperation = exports.smartPlugStateOperation = exports.pollArmStateOperation = exports.disarmOperation = exports.armHomeOperation = exports.armAwayOperation = exports.doorlocksOperation = exports.smartplugsOperation = exports.doorWindowsOperation = exports.climatesOperation = exports.armStateOperation = exports.overviewOperation = void 0;
4
+ /**
5
+ * The combined query used on the common/fast path: one request covers every
6
+ * accessory type. Some installations (observed on certain PreSense/non-Yale
7
+ * setups) have Verisure's own GraphQL server reject this query outright with
8
+ * e.g. `Expected Iterable, but did not find one for field
9
+ * "Installation.doorWindows"` when a sub-resolver returns `null` instead of
10
+ * `[]` - see issues #188, #189. `OverviewPoller` catches that and falls back
11
+ * to the individual per-domain queries below, so a broken field for one
12
+ * installation doesn't take down every accessory type on it.
13
+ */
14
+ exports.overviewOperation = {
15
+ operationName: 'Overview',
16
+ query: `query Overview($giid: String!) {
17
+ installation(giid: $giid) {
18
+ alias
19
+ locale
20
+ climates {
21
+ device {
22
+ deviceLabel
23
+ area
24
+ gui {
25
+ label
26
+ __typename
27
+ }
28
+ __typename
29
+ }
30
+ humidityEnabled
31
+ humidityTimestamp
32
+ humidityValue
33
+ temperatureTimestamp
34
+ temperatureValue
35
+ thresholds {
36
+ aboveMaxAlert
37
+ belowMinAlert
38
+ sensorType
39
+ __typename
40
+ }
41
+ __typename
42
+ }
43
+ armState {
44
+ type
45
+ statusType
46
+ date
47
+ name
48
+ changedVia
49
+ __typename
50
+ }
51
+ doorWindows {
52
+ device {
53
+ deviceLabel
54
+ area
55
+ gui {
56
+ support
57
+ label
58
+ __typename
59
+ }
60
+ __typename
61
+ }
62
+ type
63
+ state
64
+ wired
65
+ reportTime
66
+ __typename
67
+ }
68
+ smartplugs {
69
+ device {
70
+ deviceLabel
71
+ area
72
+ gui {
73
+ support
74
+ label
75
+ __typename
76
+ }
77
+ __typename
78
+ }
79
+ currentState
80
+ icon
81
+ isHazardous
82
+ __typename
83
+ }
84
+ doorlocks {
85
+ device {
86
+ area
87
+ deviceLabel
88
+ __typename
89
+ }
90
+ currentLockState
91
+ __typename
92
+ }
93
+ __typename
94
+ }
95
+ }`,
96
+ };
97
+ exports.armStateOperation = {
98
+ operationName: 'ArmState',
99
+ query: `query ArmState($giid: String!) {
100
+ installation(giid: $giid) {
101
+ armState {
102
+ type
103
+ statusType
104
+ date
105
+ name
106
+ changedVia
107
+ __typename
108
+ }
109
+ __typename
110
+ }
111
+ }`,
112
+ };
113
+ exports.climatesOperation = {
114
+ operationName: 'Climate',
115
+ query: `query Climate($giid: String!) {
116
+ installation(giid: $giid) {
117
+ climates {
118
+ device {
119
+ deviceLabel
120
+ area
121
+ gui {
122
+ label
123
+ __typename
124
+ }
125
+ __typename
126
+ }
127
+ humidityEnabled
128
+ humidityTimestamp
129
+ humidityValue
130
+ temperatureTimestamp
131
+ temperatureValue
132
+ __typename
133
+ }
134
+ __typename
135
+ }
136
+ }`,
137
+ };
138
+ exports.doorWindowsOperation = {
139
+ operationName: 'DoorWindow',
140
+ query: `query DoorWindow($giid: String!) {
141
+ installation(giid: $giid) {
142
+ doorWindows {
143
+ device {
144
+ deviceLabel
145
+ area
146
+ gui {
147
+ support
148
+ label
149
+ __typename
150
+ }
151
+ __typename
152
+ }
153
+ type
154
+ state
155
+ wired
156
+ reportTime
157
+ __typename
158
+ }
159
+ __typename
160
+ }
161
+ }`,
162
+ };
163
+ exports.smartplugsOperation = {
164
+ operationName: 'SmartPlug',
165
+ query: `query SmartPlug($giid: String!) {
166
+ installation(giid: $giid) {
167
+ smartplugs {
168
+ device {
169
+ deviceLabel
170
+ area
171
+ gui {
172
+ support
173
+ label
174
+ __typename
175
+ }
176
+ __typename
177
+ }
178
+ currentState
179
+ icon
180
+ isHazardous
181
+ __typename
182
+ }
183
+ __typename
184
+ }
185
+ }`,
186
+ };
187
+ exports.doorlocksOperation = {
188
+ operationName: 'SmartLock',
189
+ query: `query SmartLock($giid: String!) {
190
+ installation(giid: $giid) {
191
+ doorlocks {
192
+ device {
193
+ area
194
+ deviceLabel
195
+ __typename
196
+ }
197
+ currentLockState
198
+ __typename
199
+ }
200
+ __typename
201
+ }
202
+ }`,
203
+ };
204
+ const armAwayOperation = (code, forceArm) => ({
205
+ operationName: 'armAway',
206
+ variables: { code, forceArm },
207
+ query: `mutation armAway($giid: String!, $code: String!, $forceArm: Boolean) {
208
+ transactionId: armStateArmAway(giid: $giid, code: $code, forceArm: $forceArm)
209
+ }`,
210
+ });
211
+ exports.armAwayOperation = armAwayOperation;
212
+ const armHomeOperation = (code, forceArm) => ({
213
+ operationName: 'armHome',
214
+ variables: { code, forceArm },
215
+ query: `mutation armHome($giid: String!, $code: String!, $forceArm: Boolean) {
216
+ transactionId: armStateArmHome(giid: $giid, code: $code, forceArm: $forceArm)
217
+ }`,
218
+ });
219
+ exports.armHomeOperation = armHomeOperation;
220
+ const disarmOperation = (code) => ({
221
+ operationName: 'disarm',
222
+ variables: { code },
223
+ query: `mutation disarm($giid: String!, $code: String!) {
224
+ transactionId: armStateDisarm(giid: $giid, code: $code)
225
+ }`,
226
+ });
227
+ exports.disarmOperation = disarmOperation;
228
+ const pollArmStateOperation = (transactionId, futureState) => ({
229
+ operationName: 'pollArmState',
230
+ variables: { transactionId, futureState },
231
+ query: `query pollArmState($giid: String!, $transactionId: String, $futureState: ArmStateStatusTypes!) {
232
+ installation(giid: $giid) {
233
+ pollResult: armStateChangePollResult(transactionId: $transactionId, futureState: $futureState) {
234
+ result
235
+ createTime
236
+ __typename
237
+ }
238
+ __typename
239
+ }
240
+ }`,
241
+ });
242
+ exports.pollArmStateOperation = pollArmStateOperation;
243
+ const smartPlugStateOperation = (deviceLabel, state) => ({
244
+ operationName: 'smartPlugState',
245
+ variables: { deviceLabel, state },
246
+ query: `mutation smartPlugState($giid: String!, $deviceLabel: String!, $state: Boolean!) {
247
+ SmartPlugSetState(giid: $giid, input: [{deviceLabel: $deviceLabel, state: $state}])
248
+ }`,
249
+ });
250
+ exports.smartPlugStateOperation = smartPlugStateOperation;
251
+ const doorLockOperation = (deviceLabel, code) => ({
252
+ operationName: 'DoorLock',
253
+ variables: { deviceLabel, input: { code } },
254
+ query: `mutation DoorLock($giid: String!, $deviceLabel: String!, $input: LockDoorInput!) {
255
+ transactionId: DoorLock(giid: $giid, deviceLabel: $deviceLabel, input: $input)
256
+ }`,
257
+ });
258
+ exports.doorLockOperation = doorLockOperation;
259
+ const doorUnlockOperation = (deviceLabel, code) => ({
260
+ operationName: 'DoorUnlock',
261
+ variables: { deviceLabel, input: { code } },
262
+ query: `mutation DoorUnlock($giid: String!, $deviceLabel: String!, $input: LockDoorInput!) {
263
+ transactionId: DoorUnlock(giid: $giid, deviceLabel: $deviceLabel, input: $input)
264
+ }`,
265
+ });
266
+ exports.doorUnlockOperation = doorUnlockOperation;
267
+ const pollLockStateOperation = (transactionId, deviceLabel, futureState) => ({
268
+ operationName: 'pollLockState',
269
+ variables: { transactionId, deviceLabel, futureState },
270
+ query: `query pollLockState($giid: String!, $transactionId: String, $deviceLabel: String!, $futureState: DoorLockState!) {
271
+ installation(giid: $giid) {
272
+ pollResult: doorLockStateChangePollResult(transactionId: $transactionId, deviceLabel: $deviceLabel, futureState: $futureState) {
273
+ result
274
+ createTime
275
+ __typename
276
+ }
277
+ __typename
278
+ }
279
+ }`,
280
+ });
281
+ exports.pollLockStateOperation = pollLockStateOperation;
282
+ const doorLockConfigOperation = (deviceLabel) => ({
283
+ operationName: 'DoorLockConfiguration',
284
+ variables: { deviceLabel },
285
+ query: `query DoorLockConfiguration($giid: String!, $deviceLabel: String!) {
286
+ installation(giid: $giid) {
287
+ smartLocks(filter: {deviceLabels: [$deviceLabel]}) {
288
+ device {
289
+ area
290
+ deviceLabel
291
+ __typename
292
+ }
293
+ configuration {
294
+ ... on YaleLockConfiguration {
295
+ autoLockEnabled
296
+ voiceLevel
297
+ volume
298
+ __typename
299
+ }
300
+ ... on DanaLockConfiguration {
301
+ holdBackLatchDuration
302
+ twistAssistEnabled
303
+ __typename
304
+ }
305
+ __typename
306
+ }
307
+ __typename
308
+ }
309
+ __typename
310
+ }
311
+ }`,
312
+ });
313
+ exports.doorLockConfigOperation = doorLockConfigOperation;
314
+ const doorLockUpdateConfigOperation = (deviceLabel, input) => ({
315
+ operationName: 'DoorLockUpdateConfig',
316
+ variables: { deviceLabel, input },
317
+ query: `mutation DoorLockUpdateConfig($giid: String!, $deviceLabel: String!, $input: DoorLockUpdateConfigInput!) {
318
+ DoorLockUpdateConfig(giid: $giid, deviceLabel: $deviceLabel, input: $input)
319
+ }`,
320
+ });
321
+ exports.doorLockUpdateConfigOperation = doorLockUpdateConfigOperation;
322
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { Logger } from 'homebridge';
2
+ import type { VerisureInstallation } from 'verisure';
3
+ import type { Overview } from './types';
4
+ export type OverviewSubscriber = (overview: Overview) => void;
5
+ /**
6
+ * Fetches and caches a single installation's overview, and drives the
7
+ * scheduled poll tick that pushes fresh values to every accessory on that
8
+ * installation.
9
+ *
10
+ * Centralizing this - instead of every accessory polling the API on its own
11
+ * timer - is what keeps an installation with many accessories from making
12
+ * many times the necessary number of requests per poll interval (a
13
+ * significant contributor to the account rate-limit lockouts reported in
14
+ * issues #173, #181, #168).
15
+ */
16
+ export declare class OverviewPoller {
17
+ private readonly installation;
18
+ private readonly log;
19
+ private readonly pollIntervalMs;
20
+ private cache?;
21
+ private pending?;
22
+ private splitMode;
23
+ private timer?;
24
+ private rateLimitedUntil;
25
+ private readonly subscribers;
26
+ constructor(installation: VerisureInstallation, log: Logger, pollIntervalMs: number);
27
+ subscribe(subscriber: OverviewSubscriber): void;
28
+ unsubscribe(subscriber: OverviewSubscriber): void;
29
+ /** Starts the recurring poll tick. Callers that already have a fresh
30
+ * overview (from discovery) should push it to accessories themselves
31
+ * first - this deliberately doesn't fetch immediately, to avoid a
32
+ * redundant back-to-back request on startup. */
33
+ start(): void;
34
+ stop(): void;
35
+ /** Used by accessories for on-demand ("get") reads. Reuses the cached
36
+ * overview when it's fresh enough instead of always fetching. */
37
+ getOverview(): Promise<Overview>;
38
+ /** Discards the cached overview. Call this right after a mutation (arm,
39
+ * lock, plug toggle) succeeds: HomeKit controllers commonly re-read a
40
+ * characteristic right after writing it to confirm the change actually
41
+ * took, and without this a stale cached overview from just before the
42
+ * mutation would answer that read - undoing the fresh value the mutation
43
+ * just pushed, and leaving e.g. the Home app's "Disarming..." spinner
44
+ * stuck since current would appear to disagree with target again. */
45
+ invalidate(): void;
46
+ private tick;
47
+ private fetch;
48
+ private fetchFresh;
49
+ private fetchCombined;
50
+ private fetchSplit;
51
+ private querySingle;
52
+ private unwrap;
53
+ }
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OverviewPoller = void 0;
4
+ const operations_1 = require("./operations");
5
+ const settings_1 = require("./settings");
6
+ const types_1 = require("./types");
7
+ /**
8
+ * Fetches and caches a single installation's overview, and drives the
9
+ * scheduled poll tick that pushes fresh values to every accessory on that
10
+ * installation.
11
+ *
12
+ * Centralizing this - instead of every accessory polling the API on its own
13
+ * timer - is what keeps an installation with many accessories from making
14
+ * many times the necessary number of requests per poll interval (a
15
+ * significant contributor to the account rate-limit lockouts reported in
16
+ * issues #173, #181, #168).
17
+ */
18
+ class OverviewPoller {
19
+ installation;
20
+ log;
21
+ pollIntervalMs;
22
+ cache;
23
+ pending;
24
+ splitMode = false;
25
+ timer;
26
+ rateLimitedUntil = 0;
27
+ subscribers = new Set();
28
+ constructor(installation, log, pollIntervalMs) {
29
+ this.installation = installation;
30
+ this.log = log;
31
+ this.pollIntervalMs = pollIntervalMs;
32
+ }
33
+ subscribe(subscriber) {
34
+ this.subscribers.add(subscriber);
35
+ }
36
+ unsubscribe(subscriber) {
37
+ this.subscribers.delete(subscriber);
38
+ }
39
+ /** Starts the recurring poll tick. Callers that already have a fresh
40
+ * overview (from discovery) should push it to accessories themselves
41
+ * first - this deliberately doesn't fetch immediately, to avoid a
42
+ * redundant back-to-back request on startup. */
43
+ start() {
44
+ if (this.timer) {
45
+ return;
46
+ }
47
+ this.timer = setInterval(() => {
48
+ this.tick();
49
+ }, this.pollIntervalMs);
50
+ }
51
+ stop() {
52
+ if (this.timer) {
53
+ clearInterval(this.timer);
54
+ this.timer = undefined;
55
+ }
56
+ }
57
+ /** Used by accessories for on-demand ("get") reads. Reuses the cached
58
+ * overview when it's fresh enough instead of always fetching. */
59
+ async getOverview() {
60
+ const now = Date.now();
61
+ if (this.cache && now - this.cache.time < settings_1.OVERVIEW_CACHE_TTL_MS) {
62
+ return this.cache.value;
63
+ }
64
+ return this.fetch();
65
+ }
66
+ /** Discards the cached overview. Call this right after a mutation (arm,
67
+ * lock, plug toggle) succeeds: HomeKit controllers commonly re-read a
68
+ * characteristic right after writing it to confirm the change actually
69
+ * took, and without this a stale cached overview from just before the
70
+ * mutation would answer that read - undoing the fresh value the mutation
71
+ * just pushed, and leaving e.g. the Home app's "Disarming..." spinner
72
+ * stuck since current would appear to disagree with target again. */
73
+ invalidate() {
74
+ this.cache = undefined;
75
+ }
76
+ async tick() {
77
+ if (Date.now() < this.rateLimitedUntil) {
78
+ return;
79
+ }
80
+ try {
81
+ const overview = await this.fetch();
82
+ this.subscribers.forEach((subscriber) => {
83
+ try {
84
+ subscriber(overview);
85
+ }
86
+ catch (error) {
87
+ // A single accessory choking on this overview (e.g. an unexpected
88
+ // enum value) must not stop every other accessory on this
89
+ // installation from being updated.
90
+ this.log.debug(`${this.installation.config.alias}: A subscriber failed to process the latest overview: ${error.message}`);
91
+ }
92
+ });
93
+ }
94
+ catch (error) {
95
+ if ((0, types_1.isRateLimited)(error)) {
96
+ this.rateLimitedUntil = Date.now() + settings_1.RATE_LIMIT_BACKOFF_MS;
97
+ this.log.warn(`${this.installation.config.alias}: Verisure is rate-limiting this account. Pausing polling for ${Math.round(settings_1.RATE_LIMIT_BACKOFF_MS / 60000)} minutes.`);
98
+ }
99
+ else if ((0, types_1.isUnauthorized)(error)) {
100
+ this.log.error(`${this.installation.config.alias}: Verisure rejected our credentials (401). If you use "cookies" for MFA, they've likely expired - run "npx homebridge-verisure@latest" to get fresh ones.`);
101
+ }
102
+ else {
103
+ this.log.warn(`${this.installation.config.alias}: Failed to poll for updates: ${error.message}`);
104
+ }
105
+ // Deliberately don't notify subscribers: accessories keep reporting
106
+ // their last known state instead of appearing to go missing (#137).
107
+ }
108
+ }
109
+ fetch() {
110
+ if (!this.pending) {
111
+ this.pending = this.fetchFresh().finally(() => {
112
+ this.pending = undefined;
113
+ });
114
+ }
115
+ return this.pending;
116
+ }
117
+ async fetchFresh() {
118
+ const overview = this.splitMode ? await this.fetchSplit() : await this.fetchCombined();
119
+ this.cache = { value: overview, time: Date.now() };
120
+ return overview;
121
+ }
122
+ async fetchCombined() {
123
+ try {
124
+ const { installation } = await this.installation.client(operations_1.overviewOperation);
125
+ return installation;
126
+ }
127
+ catch (error) {
128
+ if (!(0, types_1.isGraphqlException)(error)) {
129
+ throw error;
130
+ }
131
+ this.log.warn(`${this.installation.config.alias}: The combined overview query failed (${error.message}). Falling back to fetching each device type separately for this installation from now on.`);
132
+ this.splitMode = true;
133
+ return this.fetchSplit();
134
+ }
135
+ }
136
+ async fetchSplit() {
137
+ const [armState, climates, doorWindows, smartplugs, doorlocks] = await Promise.allSettled([
138
+ this.querySingle(operations_1.armStateOperation),
139
+ this.querySingle(operations_1.climatesOperation),
140
+ this.querySingle(operations_1.doorWindowsOperation),
141
+ this.querySingle(operations_1.smartplugsOperation),
142
+ this.querySingle(operations_1.doorlocksOperation),
143
+ ]);
144
+ return {
145
+ armState: this.unwrap(armState, 'armState', 'installation.armState'),
146
+ climates: this.unwrap(climates, 'climates', 'installation.climates'),
147
+ doorWindows: this.unwrap(doorWindows, 'doorWindows', 'installation.doorWindows'),
148
+ smartplugs: this.unwrap(smartplugs, 'smartplugs', 'installation.smartplugs'),
149
+ doorlocks: this.unwrap(doorlocks, 'doorlocks', 'installation.doorlocks'),
150
+ };
151
+ }
152
+ querySingle(operation) {
153
+ return this.installation.client(operation);
154
+ }
155
+ unwrap(result, field, path) {
156
+ if (result.status === 'rejected') {
157
+ this.log.warn(`${this.installation.config.alias}: Could not fetch "${field}" for this installation: ${result.reason?.message ?? result.reason}. Treating it as unavailable for this poll.`);
158
+ return undefined;
159
+ }
160
+ const segments = path.split('.');
161
+ let value = result.value;
162
+ for (const segment of segments) {
163
+ value = value && typeof value === 'object' ? value[segment] : undefined;
164
+ }
165
+ return (value ?? undefined);
166
+ }
167
+ }
168
+ exports.OverviewPoller = OverviewPoller;
169
+ //# sourceMappingURL=overviewPoller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overviewPoller.js","sourceRoot":"","sources":["../src/overviewPoller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory } from 'homebridge';
2
+ import { VerisureInstallation } from 'verisure';
3
+ import { OverviewPoller } from './overviewPoller';
4
+ import type { VerisurePlatformConfig } from './types';
5
+ export declare class VerisurePlatform implements DynamicPlatformPlugin {
6
+ readonly log: Logger;
7
+ readonly api: API;
8
+ readonly config: VerisurePlatformConfig;
9
+ private readonly verisure;
10
+ private readonly accessories;
11
+ private readonly handlers;
12
+ private readonly pollers;
13
+ constructor(log: Logger, config: VerisurePlatformConfig, api: API);
14
+ /** Called by Homebridge once at startup for every accessory it restored
15
+ * from its cache, before `didFinishLaunching`/discovery runs. */
16
+ configureAccessory(accessory: PlatformAccessory): void;
17
+ /** Shared per-installation overview fetcher/poller, used by both this
18
+ * class and every accessory handler. */
19
+ poller(installation: VerisureInstallation): OverviewPoller;
20
+ private ensureAuthenticated;
21
+ private discoverDevices;
22
+ private retireUnusedPollers;
23
+ private discoverInstallation;
24
+ private instantiateHandler;
25
+ private isExcluded;
26
+ private buildDeviceList;
27
+ }