homebridge-tryfi 1.1.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.
@@ -0,0 +1,118 @@
1
+ import { PlatformConfig } from 'homebridge';
2
+ /**
3
+ * TryFi Platform Configuration
4
+ */
5
+ export interface TryFiPlatformConfig extends PlatformConfig {
6
+ username: string;
7
+ password: string;
8
+ pollingInterval?: number;
9
+ escapeAlertType?: 'leak' | 'motion';
10
+ ignoredPets?: string[];
11
+ }
12
+ /**
13
+ * TryFi Session Information
14
+ */
15
+ export interface TryFiSession {
16
+ userId: string;
17
+ sessionId: string;
18
+ }
19
+ /**
20
+ * TryFi Pet Data (processed from API response)
21
+ */
22
+ export interface TryFiPet {
23
+ petId: string;
24
+ name: string;
25
+ breed: string;
26
+ moduleId: string;
27
+ batteryPercent: number;
28
+ isCharging: boolean;
29
+ ledEnabled: boolean;
30
+ mode: string;
31
+ connectedToUser: string | null;
32
+ latitude: number;
33
+ longitude: number;
34
+ areaName: string | null;
35
+ placeName: string | null;
36
+ placeAddress: string | null;
37
+ }
38
+ /**
39
+ * GraphQL Response Wrapper
40
+ */
41
+ export interface GraphQLResponse<T> {
42
+ data?: T;
43
+ errors?: Array<{
44
+ message: string;
45
+ locations?: Array<{
46
+ line: number;
47
+ column: number;
48
+ }>;
49
+ path?: string[];
50
+ }>;
51
+ }
52
+ /**
53
+ * CurrentUser Query Response - matches pytryfi structure
54
+ */
55
+ export interface CurrentUserResponse {
56
+ currentUser: {
57
+ __typename: string;
58
+ id: string;
59
+ email: string;
60
+ firstName: string;
61
+ lastName: string;
62
+ userHouseholds: Array<{
63
+ __typename: string;
64
+ household: {
65
+ __typename: string;
66
+ pets: Array<{
67
+ __typename: string;
68
+ id: string;
69
+ name: string;
70
+ homeCityState?: string;
71
+ gender?: string;
72
+ breed?: {
73
+ __typename: string;
74
+ id: string;
75
+ name: string;
76
+ };
77
+ device?: {
78
+ __typename: string;
79
+ id: string;
80
+ moduleId: string;
81
+ info: any;
82
+ operationParams?: {
83
+ __typename: string;
84
+ mode: string;
85
+ ledEnabled: boolean;
86
+ ledOffAt?: string;
87
+ };
88
+ lastConnectionState?: {
89
+ __typename: string;
90
+ date: string;
91
+ } | {
92
+ __typename: 'ConnectedToUser';
93
+ date: string;
94
+ user: {
95
+ __typename: string;
96
+ id: string;
97
+ firstName: string;
98
+ lastName: string;
99
+ };
100
+ } | {
101
+ __typename: 'ConnectedToBase';
102
+ date: string;
103
+ chargingBase: {
104
+ __typename: string;
105
+ id: string;
106
+ };
107
+ } | {
108
+ __typename: 'ConnectedToCellular';
109
+ date: string;
110
+ signalStrengthPercent: number;
111
+ };
112
+ };
113
+ }>;
114
+ };
115
+ }>;
116
+ };
117
+ }
118
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "homebridge-tryfi",
3
+ "version": "1.1.0",
4
+ "description": "Homebridge plugin for TryFi Dog GPS Collars",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "watch": "tsc -w",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "homebridge-plugin",
13
+ "tryfi",
14
+ "dog",
15
+ "gps",
16
+ "collar",
17
+ "pet tracker"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18.0.0",
21
+ "homebridge": ">=1.6.0"
22
+ },
23
+ "author": "Dave Linger",
24
+ "license": "Apache-2.0",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/DaveLinger/homebridge-tryfi.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/DaveLinger/homebridge-tryfi/issues"
31
+ },
32
+ "dependencies": {
33
+ "axios": "^1.6.0",
34
+ "axios-cookiejar-support": "^6.0.5",
35
+ "tough-cookie": "^6.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.0.0",
39
+ "homebridge": "^1.6.0",
40
+ "typescript": "^5.0.0"
41
+ }
42
+ }
@@ -0,0 +1,172 @@
1
+ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
2
+ import { TryFiPlatform } from './platform';
3
+ import { TryFiPet } from './types';
4
+
5
+ /**
6
+ * TryFi Collar Accessory
7
+ * Represents a single dog collar with multiple HomeKit services
8
+ */
9
+ export class TryFiCollarAccessory {
10
+ private escapeAlertService: Service;
11
+ private batteryService: Service;
12
+ private lightbulbService: Service;
13
+ private lostDogSwitchService: Service;
14
+
15
+ constructor(
16
+ private readonly platform: TryFiPlatform,
17
+ private readonly accessory: PlatformAccessory,
18
+ private pet: TryFiPet,
19
+ ) {
20
+ // Set accessory information
21
+ this.accessory.getService(this.platform.Service.AccessoryInformation)!
22
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'TryFi')
23
+ .setCharacteristic(this.platform.Characteristic.Model, 'GPS Dog Collar')
24
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, pet.moduleId)
25
+ .setCharacteristic(this.platform.Characteristic.FirmwareRevision, '1.0.0');
26
+
27
+ // Get or create services
28
+ const escapeAlertType = this.platform.config.escapeAlertType || 'leak';
29
+
30
+ if (escapeAlertType === 'leak') {
31
+ this.escapeAlertService = this.accessory.getService(this.platform.Service.LeakSensor) ||
32
+ this.accessory.addService(this.platform.Service.LeakSensor);
33
+ this.escapeAlertService.setCharacteristic(this.platform.Characteristic.Name, `${pet.name} Escape Alert`);
34
+ } else {
35
+ this.escapeAlertService = this.accessory.getService(this.platform.Service.MotionSensor) ||
36
+ this.accessory.addService(this.platform.Service.MotionSensor);
37
+ this.escapeAlertService.setCharacteristic(this.platform.Characteristic.Name, `${pet.name} Escape Alert`);
38
+ }
39
+
40
+ this.batteryService = this.accessory.getService(this.platform.Service.Battery) ||
41
+ this.accessory.addService(this.platform.Service.Battery);
42
+ this.batteryService.setCharacteristic(this.platform.Characteristic.Name, `${pet.name} Battery`);
43
+
44
+ this.lightbulbService = this.accessory.getService(this.platform.Service.Lightbulb) ||
45
+ this.accessory.addService(this.platform.Service.Lightbulb);
46
+ this.lightbulbService.setCharacteristic(this.platform.Characteristic.Name, `${pet.name} LED Light`);
47
+
48
+ this.lostDogSwitchService = this.accessory.getService(this.platform.Service.Switch) ||
49
+ this.accessory.addService(this.platform.Service.Switch);
50
+ this.lostDogSwitchService.setCharacteristic(this.platform.Characteristic.Name, `${pet.name} Lost Dog Mode`);
51
+
52
+ // Set up characteristic handlers
53
+ this.setupCharacteristics();
54
+
55
+ // Initial update
56
+ this.updateCharacteristics();
57
+ }
58
+
59
+ private setupCharacteristics() {
60
+ // LED Light handlers
61
+ this.lightbulbService.getCharacteristic(this.platform.Characteristic.On)
62
+ .onGet(this.handleLightGet.bind(this))
63
+ .onSet(this.handleLightSet.bind(this));
64
+
65
+ // Lost Dog Mode handlers
66
+ this.lostDogSwitchService.getCharacteristic(this.platform.Characteristic.On)
67
+ .onGet(this.handleLostModeGet.bind(this))
68
+ .onSet(this.handleLostModeSet.bind(this));
69
+ }
70
+
71
+ /**
72
+ * Update all characteristics from latest pet data
73
+ */
74
+ updateCharacteristics() {
75
+ const escapeAlertType = this.platform.config.escapeAlertType || 'leak';
76
+
77
+ // Escape Alert: Triggered when NOT in safe zone AND NOT with owner
78
+ const isEscaped = (this.pet.placeName === null) && (this.pet.connectedToUser === null);
79
+
80
+ if (escapeAlertType === 'leak') {
81
+ this.escapeAlertService.updateCharacteristic(
82
+ this.platform.Characteristic.LeakDetected,
83
+ isEscaped
84
+ ? this.platform.Characteristic.LeakDetected.LEAK_DETECTED
85
+ : this.platform.Characteristic.LeakDetected.LEAK_NOT_DETECTED,
86
+ );
87
+ } else {
88
+ this.escapeAlertService.updateCharacteristic(
89
+ this.platform.Characteristic.MotionDetected,
90
+ isEscaped,
91
+ );
92
+ }
93
+
94
+ // Battery Service
95
+ this.batteryService.updateCharacteristic(
96
+ this.platform.Characteristic.BatteryLevel,
97
+ this.pet.batteryPercent,
98
+ );
99
+
100
+ this.batteryService.updateCharacteristic(
101
+ this.platform.Characteristic.ChargingState,
102
+ this.pet.isCharging
103
+ ? this.platform.Characteristic.ChargingState.CHARGING
104
+ : this.platform.Characteristic.ChargingState.NOT_CHARGING,
105
+ );
106
+
107
+ this.batteryService.updateCharacteristic(
108
+ this.platform.Characteristic.StatusLowBattery,
109
+ this.pet.batteryPercent < 20
110
+ ? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
111
+ : this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL,
112
+ );
113
+
114
+ // LED Light
115
+ this.lightbulbService.updateCharacteristic(
116
+ this.platform.Characteristic.On,
117
+ this.pet.ledEnabled,
118
+ );
119
+
120
+ // Lost Dog Mode
121
+ this.lostDogSwitchService.updateCharacteristic(
122
+ this.platform.Characteristic.On,
123
+ this.pet.mode === 'LOST_DOG',
124
+ );
125
+
126
+ this.platform.log.debug(`Updated ${this.pet.name}: Battery ${this.pet.batteryPercent}%, ` +
127
+ `LED ${this.pet.ledEnabled ? 'On' : 'Off'}, Mode ${this.pet.mode}, ` +
128
+ `Escaped: ${isEscaped}, Place: ${this.pet.placeName}, With: ${this.pet.connectedToUser}`);
129
+ }
130
+
131
+ /**
132
+ * Update pet data and refresh characteristics
133
+ */
134
+ updatePetData(pet: TryFiPet) {
135
+ this.pet = pet;
136
+ this.updateCharacteristics();
137
+ }
138
+
139
+ // LED Light Handlers
140
+ async handleLightGet(): Promise<CharacteristicValue> {
141
+ return this.pet.ledEnabled;
142
+ }
143
+
144
+ async handleLightSet(value: CharacteristicValue) {
145
+ const ledEnabled = value as boolean;
146
+ try {
147
+ await this.platform.api.setLedState(this.pet.moduleId, ledEnabled);
148
+ this.pet.ledEnabled = ledEnabled;
149
+ this.platform.log.info(`Set ${this.pet.name} LED to ${ledEnabled ? 'On' : 'Off'}`);
150
+ } catch (error) {
151
+ this.platform.log.error(`Failed to set LED for ${this.pet.name}:`, error);
152
+ throw new this.platform.homebridgeApi.hap.HapStatusError(this.platform.homebridgeApi.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
153
+ }
154
+ }
155
+
156
+ // Lost Dog Mode Handlers
157
+ async handleLostModeGet(): Promise<CharacteristicValue> {
158
+ return this.pet.mode === 'LOST_DOG';
159
+ }
160
+
161
+ async handleLostModeSet(value: CharacteristicValue) {
162
+ const isLost = value as boolean;
163
+ try {
164
+ await this.platform.api.setLostDogMode(this.pet.moduleId, isLost);
165
+ this.pet.mode = isLost ? 'LOST_DOG' : 'NORMAL';
166
+ this.platform.log.info(`Set ${this.pet.name} Lost Dog Mode to ${isLost ? 'On' : 'Off'}`);
167
+ } catch (error) {
168
+ this.platform.log.error(`Failed to set Lost Dog Mode for ${this.pet.name}:`, error);
169
+ throw new this.platform.homebridgeApi.hap.HapStatusError(this.platform.homebridgeApi.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
170
+ }
171
+ }
172
+ }