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/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
  /**