homebridge-tryfi 1.1.1 → 1.2.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.
- package/CHANGELOG.md +60 -0
- package/README.md +308 -21
- package/config.schema.json +24 -0
- package/dist/accessory.d.ts +1 -0
- package/dist/accessory.d.ts.map +1 -1
- package/dist/accessory.js +21 -7
- package/dist/accessory.js.map +1 -1
- package/dist/api.d.ts +2 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +42 -6
- package/dist/api.js.map +1 -1
- package/dist/platform.d.ts +16 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +90 -0
- package/dist/platform.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/accessory.ts +28 -12
- package/src/api.ts +52 -7
- package/src/platform.ts +109 -1
- package/src/types.ts +2 -0
package/src/platform.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from 'homebridge';
|
|
10
10
|
import { TryFiAPI } from './api';
|
|
11
11
|
import { TryFiCollarAccessory } from './accessory';
|
|
12
|
-
import { TryFiPlatformConfig } from './types';
|
|
12
|
+
import { TryFiPlatformConfig, TryFiPet } from './types';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* TryFi Platform Plugin
|
|
@@ -25,6 +25,10 @@ export class TryFiPlatform implements DynamicPlatformPlugin {
|
|
|
25
25
|
public readonly tryfiApi: TryFiAPI;
|
|
26
26
|
public readonly api: TryFiAPI; // Alias for accessory use
|
|
27
27
|
private pollingInterval?: NodeJS.Timeout;
|
|
28
|
+
|
|
29
|
+
// Escape alert hysteresis tracking (in-memory only, resets on restart)
|
|
30
|
+
private escapeCounters: Map<string, number> = new Map();
|
|
31
|
+
private quickCheckTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
28
32
|
|
|
29
33
|
constructor(
|
|
30
34
|
public readonly log: Logger,
|
|
@@ -221,6 +225,9 @@ export class TryFiPlatform implements DynamicPlatformPlugin {
|
|
|
221
225
|
const accessory = this.collarAccessories.get(pet.petId);
|
|
222
226
|
if (accessory) {
|
|
223
227
|
accessory.updatePetData(pet);
|
|
228
|
+
|
|
229
|
+
// Handle escape alert hysteresis
|
|
230
|
+
this.handleEscapeDetection(pet);
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
|
|
@@ -250,6 +257,7 @@ export class TryFiPlatform implements DynamicPlatformPlugin {
|
|
|
250
257
|
const accessory = this.collarAccessories.get(pet.petId);
|
|
251
258
|
if (accessory) {
|
|
252
259
|
accessory.updatePetData(pet);
|
|
260
|
+
this.handleEscapeDetection(pet);
|
|
253
261
|
}
|
|
254
262
|
}
|
|
255
263
|
this.log.debug(`Updated ${pets.length} collar(s) after re-auth`);
|
|
@@ -268,6 +276,100 @@ export class TryFiPlatform implements DynamicPlatformPlugin {
|
|
|
268
276
|
}
|
|
269
277
|
}
|
|
270
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Handle escape detection with hysteresis (debouncing)
|
|
281
|
+
* Prevents false alarms from GPS drift by requiring multiple consecutive detections
|
|
282
|
+
*/
|
|
283
|
+
private handleEscapeDetection(pet: TryFiPet) {
|
|
284
|
+
// Check if pet is escaped (out of zone AND not with anyone)
|
|
285
|
+
const isEscaped = (pet.placeName === null && pet.connectedToUser === null);
|
|
286
|
+
|
|
287
|
+
const currentCount = this.escapeCounters.get(pet.petId) || 0;
|
|
288
|
+
const confirmationsRequired = this.config.escapeConfirmations || 2;
|
|
289
|
+
|
|
290
|
+
if (isEscaped) {
|
|
291
|
+
// Potential escape detected - increment counter
|
|
292
|
+
const newCount = currentCount + 1;
|
|
293
|
+
this.escapeCounters.set(pet.petId, newCount);
|
|
294
|
+
|
|
295
|
+
this.log.debug(
|
|
296
|
+
`${pet.name} out of zone (${newCount}/${confirmationsRequired} confirmations)`,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
if (newCount >= confirmationsRequired) {
|
|
300
|
+
// Threshold reached - escape confirmed
|
|
301
|
+
// Accessory will handle the actual alert state update
|
|
302
|
+
this.log.info(`${pet.name} escape confirmed after ${newCount} check(s)`);
|
|
303
|
+
} else {
|
|
304
|
+
// Not confirmed yet - schedule quick re-check
|
|
305
|
+
this.scheduleQuickCheck(pet.petId, pet.name);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
// Pet is safe (in zone OR with someone) - reset counter
|
|
309
|
+
const hadCount = currentCount > 0;
|
|
310
|
+
this.escapeCounters.set(pet.petId, 0);
|
|
311
|
+
this.cancelQuickCheck(pet.petId);
|
|
312
|
+
|
|
313
|
+
if (hadCount) {
|
|
314
|
+
this.log.debug(`${pet.name} back in safe state, reset escape counter`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Schedule a quick check for a specific pet
|
|
321
|
+
* Used during escape detection to re-verify faster than normal polling
|
|
322
|
+
*/
|
|
323
|
+
private scheduleQuickCheck(petId: string, petName: string) {
|
|
324
|
+
// Don't schedule if already scheduled
|
|
325
|
+
if (this.quickCheckTimeouts.has(petId)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const interval = (this.config.escapeCheckInterval || 30) * 1000;
|
|
330
|
+
|
|
331
|
+
this.log.debug(`Scheduling quick check for ${petName} in ${interval / 1000}s`);
|
|
332
|
+
|
|
333
|
+
const timeout = setTimeout(async () => {
|
|
334
|
+
this.log.debug(`Running quick check for ${petName}`);
|
|
335
|
+
|
|
336
|
+
// Remove from timeouts map
|
|
337
|
+
this.quickCheckTimeouts.delete(petId);
|
|
338
|
+
|
|
339
|
+
// Poll just this one pet
|
|
340
|
+
try {
|
|
341
|
+
const allPets = await this.tryfiApi.getPets();
|
|
342
|
+
const pet = allPets.find(p => p.petId === petId);
|
|
343
|
+
|
|
344
|
+
if (pet) {
|
|
345
|
+
const accessory = this.collarAccessories.get(petId);
|
|
346
|
+
if (accessory) {
|
|
347
|
+
accessory.updatePetData(pet);
|
|
348
|
+
|
|
349
|
+
// Handle escape detection (may schedule another quick check)
|
|
350
|
+
this.handleEscapeDetection(pet);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (error: any) {
|
|
354
|
+
this.log.warn(`Quick check failed for ${petName}:`, error.message || error);
|
|
355
|
+
// Counter will remain, next normal poll will try again
|
|
356
|
+
}
|
|
357
|
+
}, interval);
|
|
358
|
+
|
|
359
|
+
this.quickCheckTimeouts.set(petId, timeout);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Cancel any pending quick check for a pet
|
|
364
|
+
*/
|
|
365
|
+
private cancelQuickCheck(petId: string) {
|
|
366
|
+
const timeout = this.quickCheckTimeouts.get(petId);
|
|
367
|
+
if (timeout) {
|
|
368
|
+
clearTimeout(timeout);
|
|
369
|
+
this.quickCheckTimeouts.delete(petId);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
271
373
|
/**
|
|
272
374
|
* Stop polling when platform is shutting down
|
|
273
375
|
*/
|
|
@@ -275,5 +377,11 @@ export class TryFiPlatform implements DynamicPlatformPlugin {
|
|
|
275
377
|
if (this.pollingInterval) {
|
|
276
378
|
clearInterval(this.pollingInterval);
|
|
277
379
|
}
|
|
380
|
+
|
|
381
|
+
// Cancel all pending quick checks
|
|
382
|
+
for (const timeout of this.quickCheckTimeouts.values()) {
|
|
383
|
+
clearTimeout(timeout);
|
|
384
|
+
}
|
|
385
|
+
this.quickCheckTimeouts.clear();
|
|
278
386
|
}
|
|
279
387
|
}
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface TryFiPlatformConfig extends PlatformConfig {
|
|
|
9
9
|
pollingInterval?: number; // seconds, default 60
|
|
10
10
|
escapeAlertType?: 'leak' | 'motion'; // default 'leak'
|
|
11
11
|
ignoredPets?: string[]; // pet names to ignore (case-insensitive)
|
|
12
|
+
escapeConfirmations?: number; // consecutive out-of-zone readings required, default 2
|
|
13
|
+
escapeCheckInterval?: number; // seconds between quick checks, default 30
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|