@homebridge-plugins/homebridge-firstalert 0.0.1-beta.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 (121) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/FUNDING.yml +4 -0
  3. package/.github/ISSUE_TEMPLATE/bug-report.yml +97 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/.github/ISSUE_TEMPLATE/feature-request.yml +38 -0
  6. package/.github/ISSUE_TEMPLATE/support-request.yml +85 -0
  7. package/.github/ISSUE_TEMPLATE.md +52 -0
  8. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +27 -0
  9. package/.github/dependabot.yml +17 -0
  10. package/.github/labeler.yml +38 -0
  11. package/.github/release-drafter.yml +33 -0
  12. package/.github/workflows/beta-release.yml +55 -0
  13. package/.github/workflows/build.yml +18 -0
  14. package/.github/workflows/changerelease.yml +11 -0
  15. package/.github/workflows/labeler.yml +9 -0
  16. package/.github/workflows/release-drafter.yml +14 -0
  17. package/.github/workflows/release.yml +35 -0
  18. package/.github/workflows/stale.yml +12 -0
  19. package/CHANGELOG.md +10 -0
  20. package/LICENSE +14 -0
  21. package/README.md +67 -0
  22. package/SECURITY.md +19 -0
  23. package/branding/Homebridge_x_FirstAlert.svg +48 -0
  24. package/branding/icon.png +0 -0
  25. package/config.schema.json +58 -0
  26. package/dist/homebridge-ui/public/index.html +548 -0
  27. package/dist/src/api/resideoClient.d.ts +32 -0
  28. package/dist/src/api/resideoClient.d.ts.map +1 -0
  29. package/dist/src/api/resideoClient.js +76 -0
  30. package/dist/src/api/resideoClient.js.map +1 -0
  31. package/dist/src/devices/device.d.ts +40 -0
  32. package/dist/src/devices/device.d.ts.map +1 -0
  33. package/dist/src/devices/device.js +207 -0
  34. package/dist/src/devices/device.js.map +1 -0
  35. package/dist/src/devices/leaksensors.d.ts +34 -0
  36. package/dist/src/devices/leaksensors.d.ts.map +1 -0
  37. package/dist/src/devices/leaksensors.js +163 -0
  38. package/dist/src/devices/leaksensors.js.map +1 -0
  39. package/dist/src/devices/smoke.d.ts +35 -0
  40. package/dist/src/devices/smoke.d.ts.map +1 -0
  41. package/dist/src/devices/smoke.js +150 -0
  42. package/dist/src/devices/smoke.js.map +1 -0
  43. package/dist/src/devices/thermostats.d.ts +42 -0
  44. package/dist/src/devices/thermostats.d.ts.map +1 -0
  45. package/dist/src/devices/thermostats.js +192 -0
  46. package/dist/src/devices/thermostats.js.map +1 -0
  47. package/dist/src/devices/valve.d.ts +21 -0
  48. package/dist/src/devices/valve.d.ts.map +1 -0
  49. package/dist/src/devices/valve.js +131 -0
  50. package/dist/src/devices/valve.js.map +1 -0
  51. package/dist/src/homebridge-ui/server.d.ts +5 -0
  52. package/dist/src/homebridge-ui/server.d.ts.map +1 -0
  53. package/dist/src/homebridge-ui/server.js +95 -0
  54. package/dist/src/homebridge-ui/server.js.map +1 -0
  55. package/dist/src/index.d.ts +4 -0
  56. package/dist/src/index.d.ts.map +1 -0
  57. package/dist/src/index.js +7 -0
  58. package/dist/src/index.js.map +1 -0
  59. package/dist/src/platform.d.ts +18 -0
  60. package/dist/src/platform.d.ts.map +1 -0
  61. package/dist/src/platform.js +108 -0
  62. package/dist/src/platform.js.map +1 -0
  63. package/dist/src/settings.d.ts +341 -0
  64. package/dist/src/settings.d.ts.map +1 -0
  65. package/dist/src/settings.js +25 -0
  66. package/dist/src/settings.js.map +1 -0
  67. package/dist/src/utils.d.ts +21 -0
  68. package/dist/src/utils.d.ts.map +1 -0
  69. package/dist/src/utils.js +58 -0
  70. package/dist/src/utils.js.map +1 -0
  71. package/dist/test/index.test.d.ts +2 -0
  72. package/dist/test/index.test.d.ts.map +1 -0
  73. package/dist/test/index.test.js +14 -0
  74. package/dist/test/index.test.js.map +1 -0
  75. package/dist/test/platform.test.d.ts +2 -0
  76. package/dist/test/platform.test.d.ts.map +1 -0
  77. package/dist/test/platform.test.js +56 -0
  78. package/dist/test/platform.test.js.map +1 -0
  79. package/dist/test/settings.test.d.ts +2 -0
  80. package/dist/test/settings.test.d.ts.map +1 -0
  81. package/dist/test/settings.test.js +48 -0
  82. package/dist/test/settings.test.js.map +1 -0
  83. package/dist/test/utils.test.d.ts +2 -0
  84. package/dist/test/utils.test.d.ts.map +1 -0
  85. package/dist/test/utils.test.js +17 -0
  86. package/dist/test/utils.test.js.map +1 -0
  87. package/docs/.nojekyll +1 -0
  88. package/docs/assets/hierarchy.js +1 -0
  89. package/docs/assets/highlight.css +22 -0
  90. package/docs/assets/icons.js +18 -0
  91. package/docs/assets/icons.svg +1 -0
  92. package/docs/assets/main.js +60 -0
  93. package/docs/assets/navigation.js +1 -0
  94. package/docs/assets/search.js +1 -0
  95. package/docs/assets/style.css +1633 -0
  96. package/docs/hierarchy.html +1 -0
  97. package/docs/index.html +77 -0
  98. package/docs/modules.html +1 -0
  99. package/docs/variables/default.html +1 -0
  100. package/eslint.config.js +44 -0
  101. package/nodemon.json +10 -0
  102. package/package.json +106 -0
  103. package/scripts/free-dev-ports.mjs +105 -0
  104. package/src/api/resideoClient.ts +106 -0
  105. package/src/devices/device.ts +226 -0
  106. package/src/devices/leaksensors.ts +206 -0
  107. package/src/devices/smoke.ts +173 -0
  108. package/src/devices/thermostats.ts +243 -0
  109. package/src/devices/valve.ts +162 -0
  110. package/src/homebridge-ui/public/index.html +548 -0
  111. package/src/homebridge-ui/server.ts +102 -0
  112. package/src/index.ts +13 -0
  113. package/src/platform.ts +112 -0
  114. package/src/settings.ts +402 -0
  115. package/src/utils.ts +61 -0
  116. package/test/index.test.ts +18 -0
  117. package/test/platform.test.ts +65 -0
  118. package/test/settings.test.ts +56 -0
  119. package/test/utils.test.ts +20 -0
  120. package/tsconfig.json +27 -0
  121. package/typedoc.json +22 -0
@@ -0,0 +1,112 @@
1
+
2
+ import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig } from 'homebridge'
3
+ import { ResideoClient } from './api/resideoClient.js'
4
+ import { Thermostats } from './devices/thermostats.js'
5
+ import { LeakSensor } from './devices/leaksensors.js'
6
+ import { Valve } from './devices/valve.js'
7
+ import { deviceBase } from './devices/device.js'
8
+ import { SmokeSensor } from './devices/smoke.js'
9
+
10
+ export class ResideoPlatform implements DynamicPlatformPlugin {
11
+ private readonly log: Logger
12
+ private readonly config: PlatformConfig
13
+ private readonly api: API
14
+ private readonly pollInterval: number
15
+ public readonly client!: ResideoClient
16
+ private pollTimer?: any
17
+ private accessories: PlatformAccessory[] = []
18
+ private deviceInstances: Record<string, deviceBase> = {}
19
+
20
+ constructor(log: Logger, config: PlatformConfig, api: API) {
21
+ this.log = log
22
+ this.config = config
23
+ this.api = api
24
+ this.pollInterval = config.pollInterval || 60
25
+ if (!config.refreshToken) {
26
+ this.log.error('No refresh token configured. Please log in via the Homebridge UI.')
27
+ return
28
+ }
29
+ this.client = new ResideoClient(config.refreshToken)
30
+ api.on('didFinishLaunching', () => this.discoverDevices())
31
+ }
32
+
33
+ async discoverDevices() {
34
+ try {
35
+ const account = await this.client.getAccount()
36
+ for (const device of account.devices) {
37
+ // Find or create accessory
38
+ let accessory = this.accessories.find(a => a.context.deviceID === device.deviceId)
39
+ if (!accessory) {
40
+ accessory = new this.api.platformAccessory(device.name, this.api.hap.uuid.generate(device.deviceId))
41
+ accessory.context.deviceID = device.deviceId
42
+ accessory.context.deviceType = device.globalDeviceType
43
+ this.api.registerPlatformAccessories('@homebridge-plugins/homebridge-firstalert', 'FirstAlert', [accessory])
44
+ this.accessories.push(accessory)
45
+ this.log.info(`Registered new accessory: ${device.name} (${device.deviceId})`)
46
+ }
47
+ // Instantiate correct device class
48
+ let instance: deviceBase | undefined
49
+ switch (device.globalDeviceType) {
50
+ case 'thermostat':
51
+ instance = new Thermostats(this, accessory, device as any)
52
+ break
53
+ case 'leakSensor':
54
+ instance = new LeakSensor(this, accessory, device as any)
55
+ break
56
+ case 'valve':
57
+ instance = new Valve(this, accessory, device as any)
58
+ break
59
+ case 'smoke':
60
+ case 'smokeSensor':
61
+ case 'smokeDetector':
62
+ case 'carbonMonoxideSensor':
63
+ case 'carbonDioxideSensor':
64
+ instance = new SmokeSensor(this, accessory, device as any)
65
+ break
66
+ default:
67
+ this.log.warn(`Unknown device type: ${device.globalDeviceType} (${device.deviceId})`)
68
+ }
69
+ if (instance) {
70
+ this.deviceInstances[device.deviceId] = instance
71
+ }
72
+ this.log.info(`Discovered device: ${device.name} (${device.deviceId})`)
73
+ }
74
+ this.startPolling(account.devices.map(d => d.deviceId))
75
+ } catch (err) {
76
+ this.log.error('Failed to discover devices:', err)
77
+ }
78
+ }
79
+
80
+ startPolling(deviceIds: string[]) {
81
+ if (this.pollTimer) {
82
+ clearInterval(this.pollTimer)
83
+ }
84
+ this.pollTimer = setInterval(() => this.pollDevices(deviceIds), this.pollInterval * 1000)
85
+ this.pollDevices(deviceIds) // Initial poll
86
+ }
87
+
88
+ async pollDevices(deviceIds: string[]) {
89
+ for (const deviceId of deviceIds) {
90
+ const instance = this.deviceInstances[deviceId]
91
+ if (instance && typeof (instance as any).updateState === 'function') {
92
+ try {
93
+ await (instance as any).updateState()
94
+ } catch (err) {
95
+ this.log.warn(`Failed to update state for device ${deviceId}:`, err)
96
+ }
97
+ } else {
98
+ try {
99
+ const state = await this.client.getDeviceState(deviceId)
100
+ this.log.debug(`Polled device ${deviceId}:`, JSON.stringify(state))
101
+ } catch (err) {
102
+ this.log.warn(`Failed to poll device ${deviceId}:`, err)
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ configureAccessory(accessory: PlatformAccessory) {
109
+ // Required for restoring cached accessories
110
+ this.accessories.push(accessory)
111
+ }
112
+ }
@@ -0,0 +1,402 @@
1
+ /* Copyright(C) 2022-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
2
+ *
3
+ * settings.ts: homebridge-firstalert.
4
+ */
5
+ import type { PlatformConfig } from 'homebridge'
6
+
7
+ /**
8
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
9
+ */
10
+ export const PLATFORM_NAME = 'FirstAlert'
11
+
12
+ /**
13
+ * This must match the name of your plugin as defined the package.json
14
+ */
15
+ export const PLUGIN_NAME = '@homebridge-plugins/homebridge-firstalert'
16
+
17
+ /**
18
+ * This is the main url used to access FirstAlert API
19
+ */
20
+ export const AuthorizeURL = 'https://api.honeywell.com/oauth2/authorize?'
21
+
22
+ /**
23
+ * This is the main url used to access FirstAlert API
24
+ */
25
+ export const TokenURL = 'https://api.honeywell.com/oauth2/token'
26
+
27
+ /**
28
+ * This is the main url used to access FirstAlert API
29
+ */
30
+ export const LocationURL = 'https://api.honeywell.com/v2/locations'
31
+
32
+ /**
33
+ * This is the main url used to access FirstAlert API
34
+ */
35
+ export const DeviceURL = 'https://api.honeywell.com/v2/devices'
36
+
37
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
38
+
39
+ // Config
40
+ export interface ResideoPlatformConfig extends PlatformConfig {
41
+ credentials?: credentials
42
+ callbackUrl?: string
43
+ port?: string
44
+ }
45
+
46
+ export interface credentials {
47
+ accessToken?: string
48
+ refreshToken?: string
49
+ consumerKey?: string
50
+ consumerSecret?: string
51
+ }
52
+
53
+ export interface modes {
54
+ Off: number
55
+ Heat: number
56
+ Cool: number
57
+ Auto: number
58
+ }
59
+
60
+ export interface holdModes {
61
+ NoHold: number
62
+ TemporaryHold: number
63
+ PermanentHold: number
64
+ }
65
+
66
+ export interface payload {
67
+ mode?: string
68
+ heatSetpoint?: number
69
+ coolSetpoint?: number
70
+ thermostatSetpointStatus?: string
71
+ nextPeriodTime?: string
72
+ autoChangeoverActive?: boolean
73
+ thermostatSetpoint?: number
74
+ unit?: string
75
+ state?: string
76
+ }
77
+
78
+ export type locations = location[]
79
+
80
+ // Location
81
+ export interface location {
82
+ locationID: string | number // Updated to handle both string and number
83
+ name: string
84
+ devices: resideoDevice[]
85
+ }
86
+
87
+ export interface resideoDevice {
88
+ deviceID: string | number // Updated to handle both string and number
89
+ deviceClass: string
90
+ deviceModel: string
91
+ userDefinedDeviceName: string
92
+ firmware?: string
93
+ thermostatVersion?: string
94
+ external?: boolean
95
+ groups?: T9groups[]
96
+ inBuiltSensorState?: inBuiltSensorState
97
+ settings?: Settings
98
+ deviceType: string
99
+ name?: string
100
+ isAlive: boolean
101
+ priorityType?: string
102
+ units?: string
103
+ indoorTemperature?: number
104
+ allowedModes?: string[]
105
+ minHeatSetpoint?: number
106
+ maxHeatSetpoint?: number
107
+ minCoolSetpoint?: number
108
+ maxCoolSetpoint?: number
109
+ changeableValues?: ChangeableValues
110
+ operationStatus?: OperationStatus
111
+ indoorHumidity?: number
112
+ displayedOutdoorHumidity?: number
113
+ scheduleStatus?: string
114
+ allowedTimeIncrements?: number
115
+ isUpgrading?: boolean
116
+ isProvisioned?: boolean
117
+ macID?: string
118
+ dataSyncStatus?: string
119
+ outdoorTemperature?: number
120
+ deadband?: number
121
+ hasDualSetpointStatus?: boolean
122
+ parentDeviceId?: number
123
+ service: Service
124
+ deviceSettings: Record<string, unknown> // DeviceSettings
125
+ firmwareVersion?: string
126
+ vacationHold?: VacationHold
127
+ currentSchedulePeriod?: CurrentSchedulePeriod
128
+ scheduleCapabilities?: ScheduleCapabilities
129
+ scheduleType?: ScheduleType
130
+ changeSource?: ChangeSource
131
+ partnerInfo?: PartnerInfo
132
+ deviceRegistrationDate?: Date
133
+ indoorHumidityStatus?: string
134
+ waterPresent: boolean
135
+ currentSensorReadings: CurrentSensorReadings
136
+ batteryRemaining: number
137
+ isRegistered: boolean
138
+ hasDeviceCheckedIn: boolean
139
+ isDeviceOffline: boolean
140
+ deviceMac: string // Device MAC address
141
+ dataSyncInfo: dataSyncInfo
142
+ lastCheckin: Date // Last time data received from device
143
+ actuatorValve: actuatorValve // Values specific to the valve operation
144
+ daylightSavingsInfo: daylightSavingsInfo // Daylight savings time config info
145
+ maintenance: maintenance // Maintenance settings
146
+ }
147
+
148
+ export interface T9groups {
149
+ id: number
150
+ }
151
+
152
+ export interface inBuiltSensorState {
153
+ roomId: number
154
+ roomName: string
155
+ }
156
+
157
+ // Fan Settings
158
+ export interface Settings {
159
+ homeSetPoints?: HomeSetPoints
160
+ awaySetPoints?: AwaySetPoints
161
+ fan: Fan
162
+ temperatureMode?: TemperatureMode
163
+ specialMode?: SpecialMode
164
+ hardwareSettings?: HardwareSettings
165
+ devicePairingEnabled?: boolean
166
+ }
167
+
168
+ export interface ChangeableValues {
169
+ mode: string
170
+ autoChangeoverActive?: boolean
171
+ heatSetpoint: number
172
+ coolSetpoint: number
173
+ thermostatSetpointStatus?: string
174
+ nextPeriodTime?: string
175
+ endHeatSetpoint?: number
176
+ endCoolSetpoint?: number
177
+ heatCoolMode: string
178
+ emergencyHeatActive?: boolean
179
+ }
180
+
181
+ export interface OperationStatus {
182
+ mode: string
183
+ fanRequest?: boolean
184
+ circulationFanRequest?: boolean
185
+ }
186
+
187
+ export interface Service {
188
+ mode: string
189
+ }
190
+
191
+ export interface AwaySetPoints {
192
+ awayHeatSP: number
193
+ awayCoolSP: number
194
+ smartCoolSP: number
195
+ smartHeatSP: number
196
+ useAutoSmart: boolean
197
+ units: string
198
+ }
199
+
200
+ export interface HomeSetPoints {
201
+ homeHeatSP: number
202
+ homeCoolSP: number
203
+ units: string
204
+ }
205
+
206
+ export interface TemperatureMode {
207
+ feelsLike?: boolean
208
+ air: boolean
209
+ }
210
+
211
+ export interface SpecialMode {
212
+ autoChangeoverActive: boolean
213
+ emergencyHeatActive?: boolean
214
+ }
215
+
216
+ export interface Fan {
217
+ allowedModes: string[]
218
+ changeableValues: FanChangeableValues
219
+ fanRunning: boolean
220
+ allowedSpeeds?: AllowedSpeed[]
221
+ }
222
+
223
+ export interface FanChangeableValues {
224
+ mode: string
225
+ }
226
+
227
+ export interface AllowedSpeed {
228
+ item: string
229
+ value: Value
230
+ }
231
+
232
+ export interface Value {
233
+ speed?: number
234
+ mode: string
235
+ }
236
+
237
+ export interface VacationHold {
238
+ enabled: boolean
239
+ }
240
+
241
+ export interface CurrentSchedulePeriod {
242
+ day: string
243
+ period: string
244
+ }
245
+
246
+ export interface ScheduleCapabilities {
247
+ availableScheduleTypes: string[]
248
+ schedulableFan: boolean
249
+ }
250
+
251
+ export interface ScheduleType {
252
+ scheduleType: string
253
+ scheduleSubType: string
254
+ }
255
+
256
+ export interface ChangeSource {
257
+ by: string
258
+ name: string
259
+ }
260
+
261
+ export interface HardwareSettings {
262
+ brightness: number
263
+ maxBrightness: number
264
+ }
265
+
266
+ export interface PartnerInfo {
267
+ singleOrMultiODUConfiguration: number
268
+ parentDeviceModelId: number
269
+ parentDeviceBrandId: number
270
+ oduName: string
271
+ }
272
+
273
+ export interface DeviceSettings {
274
+ temp: Temp
275
+ humidity: Humidity
276
+ userDefinedName: string
277
+ buzzerMuted: boolean
278
+ checkinPeriod: number
279
+ currentSensorReadPeriod: number
280
+ }
281
+
282
+ export interface Humidity {
283
+ high: Record<string, unknown>
284
+ low: Record<string, unknown>
285
+ }
286
+
287
+ export interface CurrentSensorReadings {
288
+ temperature: number
289
+ humidity: number
290
+ }
291
+
292
+ export interface Temp {
293
+ high: Record<string, unknown>
294
+ low: Record<string, unknown>
295
+ }
296
+
297
+ // T9 Room Sensors
298
+ export interface sensorAccessory {
299
+ accessoryId: number
300
+ accessoryAttribute: accessoryAttribute
301
+ accessoryValue: accessoryValue
302
+ roomId: number
303
+ }
304
+
305
+ export interface accessoryAttribute {
306
+ type: string
307
+ connectionMethod: string
308
+ name: string
309
+ model: string
310
+ serialNumber: string
311
+ softwareRevision: string
312
+ hardwareRevision: string
313
+ }
314
+
315
+ export interface accessoryValue {
316
+ coolSetpoint: number
317
+ heatSetpoint: number
318
+ indoorHumidity: number
319
+ indoorTemperature: number
320
+ motionDet: boolean
321
+ occupancyDet: boolean
322
+ excludeTemp: boolean
323
+ excludeMotion: boolean
324
+ pressure: number
325
+ occupancyTimeout: number
326
+ status: string
327
+ batteryStatus: string
328
+ rssiAverage: number
329
+ occupancySensitivity: string
330
+ }
331
+
332
+ // T9 Room Priority
333
+ export interface roomPriorityStatus {
334
+ deviceId: string
335
+ status: string
336
+ currentPriority: CurrentPriority
337
+ }
338
+
339
+ export interface CurrentPriority {
340
+ priorityType: string
341
+ selectedRooms: Record<string, unknown>
342
+ rooms: PriorityRooms[]
343
+ }
344
+
345
+ export interface PriorityRooms {
346
+ rooms: PriorityRoom
347
+ }
348
+
349
+ export interface PriorityRoom {
350
+ id: number
351
+ roomName: string
352
+ roomAvgTemp: number
353
+ roomAvgHumidity: number
354
+ overallMotion: boolean
355
+ accessories: Accessory[]
356
+ }
357
+
358
+ export interface Accessory {
359
+ id: number
360
+ type: string
361
+ excludeTemp: boolean
362
+ excludeMotion: boolean
363
+ temperature: number
364
+ status: string
365
+ detectMotion: boolean
366
+ }
367
+
368
+ export interface dataSyncInfo {
369
+ state: string // 'NotStarted' | 'Initiated' | 'Completed' | 'Failed'
370
+ transactionId: string // Internal reference ID for the DataSync operation
371
+ }
372
+
373
+ export interface actuatorValve {
374
+ commandSource: string// 'app' | 'wldFreeze' | 'wldLeak' | 'manual' | 'buildInLeak' | 'maintenance';
375
+ runningTime: number // Operation time
376
+ valveStatus: string // 'unknown' | 'open' | 'close' | 'notOpen' | 'notClose' | 'opening' | 'closing' | 'antiScaleOpening' | 'antiScaleClosing';
377
+ motorCycles: number // Count of motor operations
378
+ motorCurrentAverage: number
379
+ motorCurrentMax: number
380
+ deviceTemperature: number // Current temperature of device in Fahrenheit units
381
+ lastAntiScaleTime: Date // Last time of anti - scale operation
382
+ leakStatus: string // 'ok' | 'leak' | 'na' | 'err'
383
+ timeValveChanged: Date // Time of last valve change
384
+ }
385
+
386
+ export interface daylightSavingsInfo {
387
+ isDaylightSaving: boolean // If device is currently using DST or not
388
+ nextOffsetChange: Date // Next scheduled DST changeover
389
+ }
390
+
391
+ export interface maintenance {
392
+ antiScaleSettings: string // Current anti - scale cycle: 'OncePerWeek' | 'OncePerTwoWeeks' | 'OncePerMonth' | 'OncePerTwoMonths' | 'Disabled'
393
+ antiScaleDOWSettings: string // 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday'
394
+ antiScaleDOMSettings: number // If monthly anti - scale is used, day of the month.
395
+ antiScaleTimeSettings: string // Time for anti - scale in 24 hrs format
396
+ }
397
+
398
+ export interface fanStatus {
399
+ changeableValues: {
400
+ mode: string
401
+ }
402
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,61 @@
1
+ /* Copyright(C) 2022-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
2
+ *
3
+ * util.ts: homebridge-firstalert platform class.
4
+ */
5
+
6
+ /**
7
+ * Converts the value to celsius if the temperature units are in Fahrenheit
8
+ */
9
+ export function toCelsius(value: number, unit: number): number {
10
+ if (unit === 0) {
11
+ return value
12
+ }
13
+
14
+ // celsius should be to the nearest 0.5 degree
15
+ return Math.round((5 / 9) * (value - 32) * 2) / 2
16
+ }
17
+
18
+ /**
19
+ * Converts the value to fahrenheit if the temperature units are in Fahrenheit
20
+ */
21
+ export function toFahrenheit(value: number, unit: number): number {
22
+ if (unit === 0) {
23
+ return value
24
+ }
25
+
26
+ return Math.round((value * 9) / 5 + 32)
27
+ }
28
+
29
+ // Map HomeKit Modes to FirstAlert Modes
30
+ export enum HomeKitModes {
31
+ Off = 0, // this.hap.Characteristic.TargetHeatingCoolingState.OFF
32
+ Heat = 1, // this.hap.Characteristic.TargetHeatingCoolingState.HEAT
33
+ Cool = 2, // this.hap.Characteristic.TargetHeatingCoolingState.COOL
34
+ Auto = 3, // this.hap.Characteristic.TargetHeatingCoolingState.AUTO
35
+ }
36
+
37
+ // Don't change the order of these!
38
+ export enum ResideoModes {
39
+ Off = 'Off',
40
+ Heat = 'Heat',
41
+ Cool = 'Cool',
42
+ Auto = 'Auto',
43
+ };
44
+
45
+ /*
46
+ export enum holdModes {
47
+ NoHold = 0, //this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS
48
+ TemporaryHold = 1, //this.hap.Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS
49
+ PermanentHold = 2, //this.hap.Characteristic.ProgrammableSwitchEvent.LONG_PRESS
50
+ }
51
+
52
+ export enum fanModes {
53
+ Auto = 0, //this.hap.Characteristic.TargetFanState.AUTO
54
+ On = 1, //this.hap.Characteristic.TargetFanState.ON
55
+ }
56
+
57
+ export type resideoHold = {
58
+ NoHold: 'NoHold',
59
+ TemporaryHold: 'TemporaryHold',
60
+ PermanentHold: 'PermanentHold'
61
+ }; */
@@ -0,0 +1,18 @@
1
+ import type { API } from 'homebridge'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import registerPlatform from '../src/index.js'
5
+ import { ResideoPlatform } from '../src/platform.js'
6
+ import { PLATFORM_NAME, PLUGIN_NAME } from '../src/settings.js'
7
+
8
+ describe('index.ts', () => {
9
+ it('should register the platform with homebridge', () => {
10
+ const apiMock: API = {
11
+ registerPlatform: vi.fn(),
12
+ } as unknown as API
13
+
14
+ registerPlatform(apiMock)
15
+
16
+ expect(apiMock.registerPlatform).toHaveBeenCalledWith(PLUGIN_NAME, PLATFORM_NAME, ResideoPlatform)
17
+ })
18
+ })
@@ -0,0 +1,65 @@
1
+ import type { API, Logging } from 'homebridge'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+
4
+ import { ResideoPlatform } from '../src/platform.js'
5
+ import type { ResideoPlatformConfig } from '../src/settings.js'
6
+
7
+ vi.mock('axios')
8
+
9
+ describe('resideoPlatform', () => {
10
+ let platform: ResideoPlatform
11
+ let log: Logging
12
+ let config: ResideoPlatformConfig
13
+ let api: API
14
+
15
+ beforeEach(() => {
16
+ log = {
17
+ info: vi.fn(),
18
+ warn: vi.fn(),
19
+ error: vi.fn(),
20
+ debug: vi.fn(),
21
+ } as unknown as Logging
22
+
23
+ config = {
24
+ platform: 'FirstAlert',
25
+ name: 'Test Platform',
26
+ credentials: {
27
+ accessToken: 'testAccessToken',
28
+ consumerKey: 'testConsumerKey',
29
+ consumerSecret: 'testConsumerSecret',
30
+ refreshToken: 'testRefreshToken',
31
+ },
32
+ options: {
33
+ refreshRate: 120,
34
+ pushRate: 0.1,
35
+ devices: [],
36
+ },
37
+ }
38
+
39
+ api = {
40
+ hap: {
41
+ uuid: {
42
+ generate: vi.fn().mockReturnValue('test-uuid'),
43
+ },
44
+ },
45
+ platformAccessory: vi.fn().mockImplementation((name, uuid) => ({
46
+ displayName: name,
47
+ UUID: uuid,
48
+ context: {},
49
+ })),
50
+ user: {
51
+ configPath: vi.fn().mockReturnValue('/path/to/config.json'),
52
+ },
53
+ on: vi.fn(),
54
+ updatePlatformAccessories: vi.fn(),
55
+ } as unknown as API
56
+
57
+ platform = new ResideoPlatform(log, config, api)
58
+ })
59
+
60
+ it('should initialize platform and call discoverDevices', async () => {
61
+ // The constructor should call discoverDevices (which logs an error if no refreshToken)
62
+ expect(platform).toBeDefined()
63
+ // Optionally, you can spy on log methods or client methods here
64
+ })
65
+ })