From 77138508f0c3d0ce04833211269baab31ff79f9e Mon Sep 17 00:00:00 2001 From: Jacky Liang Date: Sun, 7 Jan 2024 15:00:17 -0500 Subject: [PATCH] Performance improvements and fixes ### UPDATED - Renamed `AxiosResponseWithRequest` to `AxiosResponseNodeJs` for better clarity. - Switched from assert JSON import for ESM to `createRequire` because method is still experiemental. - Consolidated detect report url to one place. - Reduced the import of alias types, where the types would directly point to another file. - After the plugin removes all related accessories (reset mode), the log should be `info`, not `warn`. - Fetching accessory statuses for the "status" mode is now moved to the bottom to better accomodate other optional characteristics. - "Status Tampered" for security panels now temporarily set to "Not Tampered" until there are enough statuses to determine a tamper. ### FIXED - Sensors and panel status would not be refreshed unless the user forces the retrieval and/or refresh manually (credits to @NorthernMan54). - If accessories were removed from config, this may cause a array out of bounds error due to the loop iterating forwards, not backwards. - "Status Active" for sensors improperly detecting statuses and icons. ### ADDED - Support for "Heat (Rate-of-Rise) Detector" sensor. - `sensor-mismatch` type for `stackTracer` in events where the data responses of `sensorInfo` and `sensorStatus` are not the same. - Unknown information dispatcher in `ADTPulsePlatform` in attempts to improve better collection of undocumented sensor statuses. - A notice for users if they upgraded from `v2` to `v3` to update their configuration. - Attempt to ask `axios` or portal to not cache responses. ### REMOVED - Is portable accessible (internet check) due to redundancy and performance bottleneck. - New information dispatcher in `ADTPulseAccessory`. - Description in body for when the detector sends information to plugin author since it's redundant. --- README.md | 15 +- config.schema.json | 6 + package.json | 2 +- src/lib/accessory.ts | 374 +++++++++++++++++----------------------- src/lib/api.ts | 188 +------------------- src/lib/detect.ts | 197 +++++++++++---------- src/lib/items.ts | 92 ++++++++++ src/lib/platform.ts | 59 ++++++- src/lib/schema.ts | 1 + src/lib/utility.ts | 22 ++- src/types/constant.d.ts | 2 + src/types/index.d.ts | 207 +++++++++++----------- src/types/shared.d.ts | 7 +- 13 files changed, 542 insertions(+), 630 deletions(-) diff --git a/README.md b/README.md index 9383433..2a212a4 100644 --- a/README.md +++ b/README.md @@ -68,13 +68,14 @@ This plugin can expose these devices (in read-only mode) based on your configura 3. `fire` - Fire (Smoke/Heat) Detector 4. `flood` - Water/Flood Sensor 5. `glass` - Glass Break Detector -6. `keypad` - Keypad/Touchpad -7. `motion` - Motion Sensor __::__ Motion Sensor (Notable Events Only) -8. `panic` - Audible Panic Button/Pendant __::__ Silent Panic Button/Pendant -9. `shock` - Shock Sensor -10. `supervisory` - System/Supervisory -11. `temperature` - Temperature Sensor -12. `unknown` - Unknown Device Type +6. `heat` - Heat (Rate-of-Rise) Detector +7. `keypad` - Keypad/Touchpad +8. `motion` - Motion Sensor __::__ Motion Sensor (Notable Events Only) +9. `panic` - Audible Panic Button/Pendant __::__ Silent Panic Button/Pendant +10. `shock` - Shock Sensor +11. `supervisory` - System/Supervisory +12. `temperature` - Temperature Sensor +13. `unknown` - Unknown Device Type Due to implementation complexity and platform instability, all Z-Wave accessories connected to the ADT Pulse gateway will not be planned for development or be supported overall. Consider purchasing the [Hubitat Hub](https://hubitat.com) for a seamless setup experience, or read about the [Home Assistant Z-Wave](https://www.home-assistant.io/integrations/zwave_js/) integration. diff --git a/config.schema.json b/config.schema.json index 78ea1f1..6691af5 100644 --- a/config.schema.json +++ b/config.schema.json @@ -187,6 +187,12 @@ "glass" ] }, + { + "title": "Heat (Rate-of-Rise) Detector", + "enum": [ + "heat" + ] + }, { "title": "Keypad/Touchpad", "enum": [ diff --git a/package.json b/package.json index da9244c..5906543 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebridge-adt-pulse", "displayName": "Homebridge ADT Pulse", - "version": "3.0.0-beta.19", + "version": "3.0.0-beta.20", "description": "Homebridge security system platform for ADT Pulse", "main": "./build/src/index.js", "exports": "./build/src/index.js", diff --git a/src/lib/accessory.ts b/src/lib/accessory.ts index 7ea40e5..95fd188 100644 --- a/src/lib/accessory.ts +++ b/src/lib/accessory.ts @@ -1,8 +1,7 @@ import chalk from 'chalk'; -import { detectedUnknownAccessoryAction } from '@/lib/detect.js'; import { condensedSensorTypeItems } from '@/lib/items.js'; -import { condensePanelStates, generateHash, stackTracer } from '@/lib/utility.js'; +import { condensePanelStates, stackTracer } from '@/lib/utility.js'; import type { ADTPulseAccessoryAccessory, ADTPulseAccessoryApi, @@ -10,12 +9,10 @@ import type { ADTPulseAccessoryConstructorAccessory, ADTPulseAccessoryConstructorApi, ADTPulseAccessoryConstructorCharacteristic, - ADTPulseAccessoryConstructorDebugMode, ADTPulseAccessoryConstructorInstance, ADTPulseAccessoryConstructorLog, ADTPulseAccessoryConstructorService, ADTPulseAccessoryConstructorState, - ADTPulseAccessoryDebugMode, ADTPulseAccessoryGetPanelStatusContext, ADTPulseAccessoryGetPanelStatusMode, ADTPulseAccessoryGetPanelStatusReturns, @@ -24,11 +21,6 @@ import type { ADTPulseAccessoryGetSensorStatusReturns, ADTPulseAccessoryInstance, ADTPulseAccessoryLog, - ADTPulseAccessoryNewInformationDispatcherContext, - ADTPulseAccessoryNewInformationDispatcherReturns, - ADTPulseAccessoryNewInformationDispatcherStatus, - ADTPulseAccessoryNewInformationDispatcherType, - ADTPulseAccessoryReportedHashes, ADTPulseAccessoryServices, ADTPulseAccessorySetPanelStatusArm, ADTPulseAccessorySetPanelStatusContext, @@ -69,15 +61,6 @@ export class ADTPulseAccessory { */ #characteristic: ADTPulseAccessoryCharacteristic; - /** - * ADT Pulse Accessory - Debug mode. - * - * @private - * - * @since 1.0.0 - */ - readonly #debugMode: ADTPulseAccessoryDebugMode; - /** * ADT Pulse Accessory - Instance. * @@ -96,15 +79,6 @@ export class ADTPulseAccessory { */ readonly #log: ADTPulseAccessoryLog; - /** - * ADT Pulse Accessory - Reported hashes. - * - * @private - * - * @since 1.0.0 - */ - #reportedHashes: ADTPulseAccessoryReportedHashes; - /** * ADT Pulse Accessory - Services. * @@ -133,18 +107,15 @@ export class ADTPulseAccessory { * @param {ADTPulseAccessoryConstructorCharacteristic} characteristic - Characteristic. * @param {ADTPulseAccessoryConstructorApi} api - Api. * @param {ADTPulseAccessoryConstructorLog} log - Log. - * @param {ADTPulseAccessoryConstructorDebugMode} debugMode - Debug mode. * * @since 1.0.0 */ - public constructor(accessory: ADTPulseAccessoryConstructorAccessory, state: ADTPulseAccessoryConstructorState, instance: ADTPulseAccessoryConstructorInstance, service: ADTPulseAccessoryConstructorService, characteristic: ADTPulseAccessoryConstructorCharacteristic, api: ADTPulseAccessoryConstructorApi, log: ADTPulseAccessoryConstructorLog, debugMode: ADTPulseAccessoryConstructorDebugMode) { + public constructor(accessory: ADTPulseAccessoryConstructorAccessory, state: ADTPulseAccessoryConstructorState, instance: ADTPulseAccessoryConstructorInstance, service: ADTPulseAccessoryConstructorService, characteristic: ADTPulseAccessoryConstructorCharacteristic, api: ADTPulseAccessoryConstructorApi, log: ADTPulseAccessoryConstructorLog) { this.#accessory = accessory; this.#api = api; this.#characteristic = characteristic; - this.#debugMode = debugMode; this.#instance = instance; this.#log = log; - this.#reportedHashes = []; this.#services = {}; this.#state = state; @@ -205,6 +176,9 @@ export class ADTPulseAccessory { case 'glass': this.#services.Primary = this.#accessory.getService(service.OccupancySensor) ?? this.#accessory.addService(service.OccupancySensor); break; + case 'heat': + this.#services.Primary = this.#accessory.getService(service.OccupancySensor) ?? this.#accessory.addService(service.OccupancySensor); + break; case 'keypad': this.#services.Primary = this.#accessory.getService(service.OccupancySensor) ?? this.#accessory.addService(service.OccupancySensor); break; @@ -247,10 +221,11 @@ export class ADTPulseAccessory { break; case 'panel': this.#services.Primary.getCharacteristic(this.#characteristic.SecuritySystemCurrentState) - .onGet(async () => this.getPanelStatus('current', context)); + .updateValue(this.getPanelStatus('current', context)); + this.#services.Primary.getCharacteristic(this.#characteristic.SecuritySystemTargetState) - .onGet(async () => this.getPanelStatus('target', context)) - .onSet(async (value) => this.setPanelStatus(value, context)); + .updateValue(this.getPanelStatus('target', context)) + .onSet((value) => this.setPanelStatus(value, context)); break; default: break; @@ -260,51 +235,55 @@ export class ADTPulseAccessory { switch (type) { case 'co': this.#services.Primary.getCharacteristic(this.#characteristic.CarbonMonoxideDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'doorWindow': this.#services.Primary.getCharacteristic(this.#characteristic.ContactSensorState) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'fire': this.#services.Primary.getCharacteristic(this.#characteristic.SmokeDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'flood': this.#services.Primary.getCharacteristic(this.#characteristic.LeakDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'glass': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); + break; + case 'heat': + this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) + .updateValue(this.getSensorStatus('status', context)); break; case 'keypad': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'motion': this.#services.Primary.getCharacteristic(this.#characteristic.MotionDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'panic': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'shock': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'supervisory': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'temperature': this.#services.Primary.getCharacteristic(this.#characteristic.CurrentTemperature) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; case 'unknown': this.#services.Primary.getCharacteristic(this.#characteristic.OccupancyDetected) - .onGet(async () => this.getSensorStatus('status', context)); + .updateValue(this.getSensorStatus('status', context)); break; default: break; @@ -317,13 +296,13 @@ export class ADTPulseAccessory { break; case 'panel': this.#services.Primary.getCharacteristic(this.#characteristic.SecuritySystemAlarmType) - .onGet(async () => this.getPanelStatus('alarmType', context)); + .updateValue(this.getPanelStatus('alarmType', context)); this.#services.Primary.getCharacteristic(this.#characteristic.StatusFault) - .onGet(async () => this.getPanelStatus('fault', context)); + .updateValue(this.getPanelStatus('fault', context)); this.#services.Primary.getCharacteristic(this.#characteristic.StatusTampered) - .onGet(async () => this.getPanelStatus('tamper', context)); + .updateValue(this.getPanelStatus('tamper', context)); break; default: break; @@ -336,6 +315,7 @@ export class ADTPulseAccessory { case 'fire': case 'flood': case 'glass': + case 'heat': case 'keypad': case 'motion': case 'panic': @@ -344,16 +324,16 @@ export class ADTPulseAccessory { case 'temperature': case 'unknown': this.#services.Primary.getCharacteristic(this.#characteristic.StatusActive) - .onGet(async () => this.getSensorStatus('active', context)); + .updateValue(this.getSensorStatus('active', context)); this.#services.Primary.getCharacteristic(this.#characteristic.StatusFault) - .onGet(async () => this.getSensorStatus('fault', context)); + .updateValue(this.getSensorStatus('fault', context)); this.#services.Primary.getCharacteristic(this.#characteristic.StatusLowBattery) - .onGet(async () => this.getSensorStatus('lowBattery', context)); + .updateValue(this.getSensorStatus('lowBattery', context)); this.#services.Primary.getCharacteristic(this.#characteristic.StatusTampered) - .onGet(async () => this.getSensorStatus('tamper', context)); + .updateValue(this.getSensorStatus('tamper', context)); break; default: break; @@ -372,7 +352,7 @@ export class ADTPulseAccessory { * * @since 1.0.0 */ - private async getSensorStatus(mode: ADTPulseAccessoryGetSensorStatusMode, context: ADTPulseAccessoryGetSensorStatusContext): ADTPulseAccessoryGetSensorStatusReturns { + private getSensorStatus(mode: ADTPulseAccessoryGetSensorStatusMode, context: ADTPulseAccessoryGetSensorStatusContext): ADTPulseAccessoryGetSensorStatusReturns { const { id, name, @@ -393,87 +373,18 @@ export class ADTPulseAccessory { ) { this.#log.error(`Attempted to get sensor status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but sensor is not found or sensor type is not supported.`); - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_DOES_NOT_EXIST); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_DOES_NOT_EXIST); } const { icon, statuses } = matchedSensorStatus; - // Find the state for "Sensor" (required characteristic). - if (mode === 'status') { - switch (type) { - case 'co': - if (statuses.includes('Okay')) { - return this.#characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL; - } - break; - case 'doorWindow': - if (statuses.includes('Open')) { - return this.#characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - } - - if (statuses.includes('Closed')) { - return this.#characteristic.ContactSensorState.CONTACT_DETECTED; - } - break; - case 'fire': - if (statuses.includes('Okay')) { - return this.#characteristic.SmokeDetected.SMOKE_NOT_DETECTED; - } - break; - case 'flood': - if (statuses.includes('Okay')) { - return this.#characteristic.LeakDetected.LEAK_NOT_DETECTED; - } - break; - case 'glass': - if (statuses.includes('Tripped')) { - return true; - } - - if (statuses.includes('Okay')) { - return false; - } - break; - case 'keypad': - // TODO: Nothing done here yet. - break; - case 'motion': - if (statuses.includes('No Motion') || statuses.includes('Okay')) { - return false; - } - - if (statuses.includes('Motion')) { - return true; - } - break; - case 'panic': - // TODO: Nothing done here yet. - break; - case 'shock': - // TODO: Nothing done here yet. - break; - case 'supervisory': - // TODO: Nothing done here yet. - break; - case 'temperature': - // TODO: Nothing done here yet. - break; - case 'unknown': - // TODO: Nothing done here yet. - break; - default: - break; - } - } - // Find the state for "Status Active" (optional characteristic). if (mode === 'active') { // If status or icon does not include these, the sensor is active. - return !( - statuses.includes('Offline') - || statuses.includes('Unknown') - || icon === 'devStatOffline' - ); + return !statuses.includes('Offline') + && !statuses.includes('Unknown') + && icon !== 'devStatOffline' + && icon !== 'devStatUnknown'; } // Find the state for "Status Fault" (optional characteristic). @@ -512,18 +423,89 @@ export class ADTPulseAccessory { return this.#characteristic.StatusTampered.NOT_TAMPERED; } - // If sensor is currently "Offline" or "Unknown". Should be detected last to prevent breaking other modes. + // Find the state for the sensor (required characteristic). + switch (type) { + case 'co': + if (statuses.includes('Tripped')) { + return this.#characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL; + } + + if (statuses.includes('Okay')) { + return this.#characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL; + } + break; + case 'doorWindow': + if (statuses.includes('Open')) { + return this.#characteristic.ContactSensorState.CONTACT_NOT_DETECTED; + } + + if (statuses.includes('Closed')) { + return this.#characteristic.ContactSensorState.CONTACT_DETECTED; + } + break; + case 'fire': + if (statuses.includes('Okay')) { + return this.#characteristic.SmokeDetected.SMOKE_NOT_DETECTED; + } + break; + case 'flood': + if (statuses.includes('Okay')) { + return this.#characteristic.LeakDetected.LEAK_NOT_DETECTED; + } + break; + case 'glass': + if (statuses.includes('Tripped')) { + return true; + } + + if (statuses.includes('Okay')) { + return false; + } + break; + case 'heat': + // TODO: Nothing done here yet. + break; + case 'keypad': + // TODO: Nothing done here yet. + break; + case 'motion': + if (statuses.includes('No Motion') || statuses.includes('Okay')) { + return false; + } + + if (statuses.includes('Motion')) { + return true; + } + break; + case 'panic': + // TODO: Nothing done here yet. + break; + case 'shock': + // TODO: Nothing done here yet. + break; + case 'supervisory': + // TODO: Nothing done here yet. + break; + case 'temperature': + // TODO: Nothing done here yet. + break; + case 'unknown': + // TODO: Nothing done here yet. + break; + default: + break; + } + + // If sensor is currently "Offline" or "Unknown". Should be detected last to prevent breaking other modes (getting battery statuses, etc.). if (statuses.includes('Offline') || statuses.includes('Unknown')) { this.#log.warn(`Attempted to get sensor status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but sensor is currently "Offline" or "Unknown".`); - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE); } this.#log.warn(`Attempted to get sensor status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but actions have not been implemented yet.`); - await this.newInformationDispatcher(type, matchedSensorStatus, context); - - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST); } /** @@ -538,7 +520,7 @@ export class ADTPulseAccessory { * * @since 1.0.0 */ - private async getPanelStatus(mode: ADTPulseAccessoryGetPanelStatusMode, context: ADTPulseAccessoryGetPanelStatusContext): ADTPulseAccessoryGetPanelStatusReturns { + private getPanelStatus(mode: ADTPulseAccessoryGetPanelStatusMode, context: ADTPulseAccessoryGetPanelStatusContext): ADTPulseAccessoryGetPanelStatusReturns { const { id, name, @@ -550,7 +532,7 @@ export class ADTPulseAccessory { if (type !== 'panel') { this.#log.error(`Attempted to get panel status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but device is not a security panel.`); - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST); } // If panel status has not been retrieved yet. @@ -561,12 +543,12 @@ export class ADTPulseAccessory { ) { this.#log.debug(`Attempted to get panel status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but panel status has not been retrieved yet.`); - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_BUSY); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_BUSY); } const { panelStates, panelStatuses } = this.#state.data.panelStatus; - // If mode is "alarmType". + // Find the state for "Security System Alarm Type" (optional characteristic). if (mode === 'alarmType') { if ( panelStatuses.includes('BURGLARY ALARM') @@ -581,30 +563,7 @@ export class ADTPulseAccessory { return 0; } - // If mode is "current". - if (mode === 'current') { - switch (true) { - // TODO: Try to test Smoke or CO alarm while status is Disarmed and see if I'm able to disarm the system in Home app. - case panelStatuses.includes('BURGLARY ALARM'): - case panelStatuses.includes('Carbon Monoxide Alarm'): - case panelStatuses.includes('FIRE ALARM'): - case panelStatuses.includes('Uncleared Alarm'): - case panelStatuses.includes('WATER ALARM'): - return this.#characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; - case panelStates.includes('Armed Away'): - return this.#characteristic.SecuritySystemCurrentState.AWAY_ARM; - case panelStates.includes('Armed Stay'): - return this.#characteristic.SecuritySystemCurrentState.STAY_ARM; - case panelStates.includes('Armed Night'): - return this.#characteristic.SecuritySystemCurrentState.NIGHT_ARM; - case panelStates.includes('Disarmed'): - return this.#characteristic.SecuritySystemCurrentState.DISARMED; - default: - break; - } - } - - // If mode is "fault". + // Find the state for "Status Fault" (optional characteristic). if (mode === 'fault') { if (!panelStatuses.includes('All Quiet')) { return this.#characteristic.StatusFault.GENERAL_FAULT; @@ -613,42 +572,58 @@ export class ADTPulseAccessory { return this.#characteristic.StatusFault.NO_FAULT; } - // If mode is "tamper". + // Find the state for "Status Tampered" (optional characteristic). if (mode === 'tamper') { // TODO: Not enough statuses currently to determine whether system is tampered or not. - if (!panelStatuses.some((panelStatus) => /tamp/gi.test(panelStatus))) { - return this.#characteristic.StatusTampered.NOT_TAMPERED; - } + return this.#characteristic.StatusTampered.NOT_TAMPERED; } - // If mode is "target". - if (mode === 'target') { - switch (true) { - case panelStates.includes('Armed Away'): - return this.#characteristic.SecuritySystemTargetState.AWAY_ARM; - case panelStates.includes('Armed Stay'): - return this.#characteristic.SecuritySystemTargetState.STAY_ARM; - case panelStates.includes('Armed Night'): - return this.#characteristic.SecuritySystemTargetState.NIGHT_ARM; - case panelStates.includes('Disarmed'): - return this.#characteristic.SecuritySystemTargetState.DISARM; - default: - break; - } + // Find the current state for the panel (required characteristic). + switch (true) { + case mode === 'current' && panelStatuses.includes('BURGLARY ALARM'): + case mode === 'current' && panelStatuses.includes('Carbon Monoxide Alarm'): + case mode === 'current' && panelStatuses.includes('FIRE ALARM'): + case mode === 'current' && panelStatuses.includes('Uncleared Alarm'): + case mode === 'current' && panelStatuses.includes('WATER ALARM'): + // TODO: Try to test Smoke or CO alarm while status is Disarmed and see if I'm able to disarm the system in Home app. + // TODO: If I cannot disarm, then I can only include BURGLARY ALARM in here. + return this.#characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + case mode === 'current' && panelStates.includes('Armed Away'): + return this.#characteristic.SecuritySystemCurrentState.AWAY_ARM; + case mode === 'current' && panelStates.includes('Armed Stay'): + return this.#characteristic.SecuritySystemCurrentState.STAY_ARM; + case mode === 'current' && panelStates.includes('Armed Night'): + return this.#characteristic.SecuritySystemCurrentState.NIGHT_ARM; + case mode === 'current' && panelStates.includes('Disarmed'): + return this.#characteristic.SecuritySystemCurrentState.DISARMED; + default: + break; } - // If panel state is "Status Unavailable". Should be detected last to prevent breaking other modes. + // Find the target state for the panel (required characteristic). + switch (true) { + case mode === 'target' && panelStates.includes('Armed Away'): + return this.#characteristic.SecuritySystemTargetState.AWAY_ARM; + case mode === 'target' && panelStates.includes('Armed Stay'): + return this.#characteristic.SecuritySystemTargetState.STAY_ARM; + case mode === 'target' && panelStates.includes('Armed Night'): + return this.#characteristic.SecuritySystemTargetState.NIGHT_ARM; + case mode === 'target' && panelStates.includes('Disarmed'): + return this.#characteristic.SecuritySystemTargetState.DISARM; + default: + break; + } + + // If panel state is "Status Unavailable". Should be detected last to prevent breaking other modes (getting battery statuses, etc.). if (panelStates.includes('Status Unavailable')) { this.#log.warn(`Attempted to get panel status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but panel state is "Status Unavailable".`); - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_BUSY); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_BUSY); } this.#log.warn(`Attempted to get panel status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but actions have not been implemented yet.`); - await this.newInformationDispatcher(type, this.#state.data.panelStatus, context); - - throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); + return new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); } /** @@ -736,35 +711,4 @@ export class ADTPulseAccessory { throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.OPERATION_TIMED_OUT); } } - - /** - * ADT Pulse Accessory - New information dispatcher. - * - * @param {ADTPulseAccessoryNewInformationDispatcherType} type - Type. - * @param {ADTPulseAccessoryNewInformationDispatcherStatus} status - Status. - * @param {ADTPulseAccessoryNewInformationDispatcherContext} context - Context. - * - * @private - * - * @returns {ADTPulseAccessoryNewInformationDispatcherReturns} - * - * @since 1.0.0 - */ - private async newInformationDispatcher(type: ADTPulseAccessoryNewInformationDispatcherType, status: ADTPulseAccessoryNewInformationDispatcherStatus, context: ADTPulseAccessoryNewInformationDispatcherContext): ADTPulseAccessoryNewInformationDispatcherReturns { - const dataHash = generateHash(`${type}: ${JSON.stringify(status)} ${JSON.stringify(context)}`); - - // If the detector has not reported this event before. - if (this.#reportedHashes.find((reportedHash) => dataHash === reportedHash) === undefined) { - const data = { - status, - context, - }; - const detectedNew = await detectedUnknownAccessoryAction(data, this.#log, this.#debugMode); - - // Save this hash so the detector does not detect the same thing multiple times. - if (detectedNew) { - this.#reportedHashes.push(dataHash); - } - } - } } diff --git a/src/lib/api.ts b/src/lib/api.ts index bd2911a..d04e4e6 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -87,7 +87,6 @@ import type { ADTPulseHandleLoginFailureSession, ADTPulseInternal, ADTPulseIsAuthenticatedReturns, - ADTPulseIsPortalAccessibleReturns, ADTPulseLoginPortalVersion, ADTPulseLoginReturns, ADTPulseLoginSessions, @@ -222,18 +221,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseLoginSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'LOGIN', - success: false, - info: internet.info, - }; - } - // Check if "this instance" has already authenticated. if (this.isAuthenticated()) { if (this.#internal.debug) { @@ -539,18 +528,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseLogoutSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'LOGOUT', - success: false, - info: internet.info, - }; - } - // Check if "this instance" has already de-authenticated. if (!this.isAuthenticated()) { if (this.#internal.debug) { @@ -701,18 +680,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseGetGatewayInformationSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'GET_GATEWAY_INFORMATION', - success: false, - info: internet.info, - }; - } - // sessions.axiosSystemGateway: Load the system gateway page. sessions.axiosSystemGateway = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/gateway.jsp`, @@ -937,18 +906,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseGetPanelInformationSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'GET_PANEL_INFORMATION', - success: false, - info: internet.info, - }; - } - // sessions.axiosSystemDeviceId1: Load the system device id 1 page. sessions.axiosSystemDeviceId1 = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/device.jsp?id=1`, @@ -1145,18 +1104,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseGetPanelStatusSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'GET_PANEL_STATUS', - success: false, - info: internet.info, - }; - } - // sessions.axiosSummary: Load the summary page. sessions.axiosSummary = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`, @@ -1418,18 +1367,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseSetPanelStatusSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'SET_PANEL_STATUS', - success: false, - info: internet.info, - }; - } - // sessions.axiosSummary: Load the summary page. sessions.axiosSummary = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`, @@ -1809,18 +1748,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseGetSensorsInformationSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'GET_SENSORS_INFORMATION', - success: false, - info: internet.info, - }; - } - // sessions.axiosSystem: Load the system page. sessions.axiosSystem = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/system/system.jsp`, @@ -1946,6 +1875,7 @@ export class ADTPulse { * 'Door Sensor' * 'Fire (Smoke/Heat) Detector' * 'Glass Break Detector' + * 'Heat (Rate-of-Rise) Detector' * 'Keypad/Touchpad' * 'Motion Sensor' * 'Motion Sensor (Notable Events Only)' @@ -2009,18 +1939,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseGetSensorsStatusSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'GET_SENSORS_STATUS', - success: false, - info: internet.info, - }; - } - // sessions.axiosSummary: Load the summary page. sessions.axiosSummary = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/summary/summary.jsp`, @@ -2255,18 +2175,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulsePerformSyncCheckSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'PERFORM_SYNC_CHECK', - success: false, - info: internet.info, - }; - } - // sessions.axiosSyncCheck: Load the sync check page. sessions.axiosSyncCheck = await this.#session.httpClient.get( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/Ajax/SyncCheckServ?t=${Date.now()}`, @@ -2411,18 +2321,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulsePerformKeepAliveSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'PERFORM_KEEP_ALIVE', - success: false, - info: internet.info, - }; - } - // sessions.axiosKeepAlive: Load the keep alive page. sessions.axiosKeepAlive = await this.#session.httpClient.post( `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/KeepAlive`, @@ -2567,18 +2467,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseArmDisarmHandlerSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'ARM_DISARM_HANDLER', - success: false, - info: internet.info, - }; - } - // Build an "application/x-www-form-urlencoded" form for use with arming and disarming. const armDisarmForm = new URLSearchParams(); armDisarmForm.append('href', href); @@ -2993,18 +2883,8 @@ export class ADTPulse { } try { - const internet = await this.isPortalAccessible(); const sessions: ADTPulseForceArmHandlerSessions = {}; - // Check if portal is accessible. - if (!internet.success) { - return { - action: 'FORCE_ARM_HANDLER', - success: false, - info: internet.info, - }; - } - // Make sure we are able to use JSDOM on the response data. if (typeof response.data !== 'string') { if (this.#internal.debug) { @@ -3339,70 +3219,6 @@ export class ADTPulse { }; } - /** - * ADT Pulse - Is portal accessible. - * - * @private - * - * @returns {ADTPulseIsPortalAccessibleReturns} - * - * @since 1.0.0 - */ - private async isPortalAccessible(): ADTPulseIsPortalAccessibleReturns { - let errorObject; - - if (this.#internal.debug) { - debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'info', `Attempting to check if "${this.#internal.baseUrl}" is accessible`); - } - - try { - // Send request using a new instance to prevent cookie jar cross-contamination. - const response = await axios.head( - this.#internal.baseUrl, - this.getRequestConfig(), - ); - - if (response.status !== 200 || response.statusText !== 'OK') { - if (this.#internal.debug) { - debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'error', `The portal at "${this.#internal.baseUrl}" is not accessible`); - } - - return { - action: 'IS_PORTAL_ACCESSIBLE', - success: false, - info: { - message: `The portal at "${this.#internal.baseUrl}" is not accessible`, - }, - }; - } - - if (this.#internal.debug) { - debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'success', `Successfully checked if "${this.#internal.baseUrl}" is accessible`); - } - - return { - action: 'IS_PORTAL_ACCESSIBLE', - success: true, - info: null, - }; - } catch (error) { - errorObject = serializeError(error); - } - - if (this.#internal.debug) { - debugLog(this.#internal.logger, 'api.ts / ADTPulse.isPortalAccessible()', 'error', 'Method encountered an error during execution'); - stackTracer('serialize-error', errorObject); - } - - return { - action: 'IS_PORTAL_ACCESSIBLE', - success: false, - info: { - error: errorObject, - }, - }; - } - /** * ADT Pulse - New information dispatcher. * @@ -3477,8 +3293,10 @@ export class ADTPulse { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9', + 'Cache-Control': 'no-cache', Connection: 'keep-alive', Host: `${this.#credentials.subdomain}.adtpulse.com`, + Pragma: 'no-cache', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 61fde73..f7ceb9e 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -15,6 +15,7 @@ import { orbSecurityButtonUrlParamsHrefItems, panelInformationStatusItems, portalVersionItems, + sensorActionItems, sensorInformationDeviceTypeItems, sensorInformationStatusItems, sensorStatusIconItems, @@ -22,6 +23,7 @@ import { } from '@/lib/items.js'; import { debugLog, + getDetectReportUrl, isPluginOutdated, removePersonalIdentifiableInformation, stackTracer, @@ -59,10 +61,10 @@ import type { DetectedNewSensorsStatusLogger, DetectedNewSensorsStatusReturns, DetectedNewSensorsStatusSensors, - DetectedUnknownAccessoryActionData, - DetectedUnknownAccessoryActionDebugMode, - DetectedUnknownAccessoryActionLogger, - DetectedUnknownAccessoryActionReturns, + DetectedUnknownSensorsActionDebugMode, + DetectedUnknownSensorsActionLogger, + DetectedUnknownSensorsActionReturns, + DetectedUnknownSensorsActionSensors, } from '@/types/index.d.ts'; /** @@ -134,11 +136,8 @@ export async function detectedNewDoSubmitHandlers(handlers: DetectedNewDoSubmitH try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -221,11 +220,8 @@ export async function detectedNewGatewayInformation(device: DetectedNewGatewayIn try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -330,11 +326,8 @@ export async function detectedNewOrbSecurityButtons(buttons: DetectedNewOrbSecur try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -417,11 +410,8 @@ export async function detectedNewPanelInformation(device: DetectedNewPanelInform try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -504,11 +494,8 @@ export async function detectedNewPanelStatus(summary: DetectedNewPanelStatusSumm try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -591,11 +578,8 @@ export async function detectedNewPortalVersion(version: DetectedNewPortalVersion try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -681,11 +665,8 @@ export async function detectedNewSensorsInformation(sensors: DetectedNewSensorsI try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -768,11 +749,8 @@ export async function detectedNewSensorsStatus(sensors: DetectedNewSensorsStatus try { await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), + getDetectReportUrl(), + JSON.stringify(cleanedData, null, 2), { family: 4, headers: { @@ -798,82 +776,99 @@ export async function detectedNewSensorsStatus(sensors: DetectedNewSensorsStatus } /** - * Detected unknown accessory action. + * Detected unknown sensors action. * - * @param {DetectedUnknownAccessoryActionData} data - Data. - * @param {DetectedUnknownAccessoryActionLogger} logger - Logger. - * @param {DetectedUnknownAccessoryActionDebugMode} debugMode - Debug mode. + * @param {DetectedUnknownSensorsActionSensors} sensors - Sensors + * @param {DetectedUnknownSensorsActionLogger} logger - Logger. + * @param {DetectedUnknownSensorsActionDebugMode} debugMode - Debug mode. * - * @returns {DetectedUnknownAccessoryActionReturns} + * @returns {DetectedUnknownSensorsActionReturns} * * @since 1.0.0 */ -export async function detectedUnknownAccessoryAction(data: DetectedUnknownAccessoryActionData, logger: DetectedUnknownAccessoryActionLogger, debugMode: DetectedUnknownAccessoryActionDebugMode): DetectedUnknownAccessoryActionReturns { - const cleanedData = removePersonalIdentifiableInformation(data); +export async function detectedUnknownSensorsAction(sensors: DetectedUnknownSensorsActionSensors, logger: DetectedUnknownSensorsActionLogger, debugMode: DetectedUnknownSensorsActionDebugMode): DetectedUnknownSensorsActionReturns { + const detectedNewActions = sensors.filter((sensor) => { + const sensorStatusStatuses = sensor.status.statuses; + const sensorType = sensor.type; - // If outdated, it means plugin may already have support. - try { - const outdated = await isPluginOutdated(); + const stringifiedStatuses = sensorStatusStatuses.join(', '); + const currentType = sensorActionItems.find((sensorActionItem) => sensorActionItem.type === sensorType); - if (outdated) { - if (logger !== null) { - logger.warn('Plugin has detected unknown accessory action. You are running an older plugin version, please update soon.'); - } + // Need to start with a list of documented actions for that sensor type. + if (currentType === undefined) { + return false; + } + + // If status for that sensor type is documented, no need to continue. + return !currentType.statuses.includes(stringifiedStatuses); + }); + + if (detectedNewActions.length > 0) { + const cleanedData = removePersonalIdentifiableInformation(detectedNewActions); + + // If outdated, it means plugin may already have support. + try { + const outdated = await isPluginOutdated(); + + if (outdated) { + if (logger !== null) { + logger.warn('Plugin has detected unknown sensors action. You are running an older plugin version, please update soon.'); + } + + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownSensorsAction()', 'warn', 'Plugin has detected unknown sensors action. You are running an older plugin version, please update soon'); + } - // This is intentionally duplicated if using Homebridge debug mode. - if (debugMode) { - debugLog(logger, 'detect.ts / detectedUnknownAccessoryAction()', 'warn', 'Plugin has detected unknown accessory action. You are running an older plugin version, please update soon'); + // Do not send analytics for users running outdated plugin versions. + return false; + } + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownSensorsAction()', 'error', 'Failed to check if plugin is outdated'); + stackTracer('serialize-error', serializeError(error)); } - // Do not send analytics for users running outdated plugin versions. + // Try to check if plugin is outdated later on. return false; } - } catch (error) { - if (debugMode === true) { - debugLog(logger, 'detect.ts / detectedUnknownAccessoryAction()', 'error', 'Failed to check if plugin is outdated'); - stackTracer('serialize-error', serializeError(error)); - } - - // Try to check if plugin is outdated later on. - return false; - } - if (logger !== null) { - logger.warn('Plugin has detected unknown accessory action. Notifying plugin author about this discovery ...'); - } + if (logger !== null) { + logger.warn('Plugin has detected unknown sensors action. Notifying plugin author about this discovery ...'); + } - // This is intentionally duplicated if using Homebridge debug mode. - if (debugMode) { - debugLog(logger, 'detect.ts / detectedUnknownAccessoryAction()', 'warn', 'Plugin has detected unknown accessory action. Notifying plugin author about this discovery'); - } + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownSensorsAction()', 'warn', 'Plugin has detected unknown sensors action. Notifying plugin author about this discovery'); + } - // Show content being sent to author. - stackTracer('detect-content', cleanedData); + // Show content being sent to author. + stackTracer('detect-content', cleanedData); - try { - await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', + try { + await axios.post( + getDetectReportUrl(), JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), - { - family: 4, - headers: { - 'User-Agent': 'homebridge-adt-pulse', - 'X-Title': 'Detected unknown accessory action', + { + family: 4, + headers: { + 'User-Agent': 'homebridge-adt-pulse', + 'X-Title': 'Detected unknown sensors action', + }, }, - }, - ); + ); - return true; - } catch (error) { - if (debugMode === true) { - debugLog(logger, 'detect.ts / detectedUnknownAccessoryAction()', 'error', 'Failed to notify plugin author about the unknown accessory action'); - stackTracer('serialize-error', serializeError(error)); - } + return true; + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownSensorsAction()', 'error', 'Failed to notify plugin author about the unknown sensors action'); + stackTracer('serialize-error', serializeError(error)); + } - // Try to send information to author later. - return false; + // Try to send information to author later. + return false; + } } + + return false; } diff --git a/src/lib/items.ts b/src/lib/items.ts index cddcd4d..514b342 100644 --- a/src/lib/items.ts +++ b/src/lib/items.ts @@ -19,6 +19,7 @@ import type { PanelStatusStatusItems, PanelStatusStatusItemsSensorsOpen, PortalVersionItems, + SensorActionItems, SensorInformationDeviceTypeItems, SensorInformationStatusItems, SensorStatusIconItems, @@ -36,6 +37,7 @@ export const condensedSensorTypeItems: CondensedSensorTypeItems = [ 'fire', 'flood', 'glass', + 'heat', 'keypad', 'motion', 'panic', @@ -260,6 +262,95 @@ export const portalVersionItems: PortalVersionItems = [ '27.0.0-140', ]; +/** + * Sensor action items. + * + * @since 1.0.0 + */ +export const sensorActionItems: SensorActionItems = [ + { + type: 'co', + statuses: [ + 'Okay', + ], + }, + { + type: 'doorWindow', + statuses: [ + 'Closed', + 'Open', + ], + }, + { + type: 'fire', + statuses: [ + 'Okay', + ], + }, + { + type: 'flood', + statuses: [ + '', + ], + }, + { + type: 'glass', + statuses: [ + 'Okay', + ], + }, + { + type: 'heat', + statuses: [ + '', + ], + }, + { + type: 'keypad', + statuses: [ + '', + ], + }, + { + type: 'motion', + statuses: [ + 'Motion', + 'No Motion', + 'Okay', + ], + }, + { + type: 'panic', + statuses: [ + '', + ], + }, + { + type: 'shock', + statuses: [ + '', + ], + }, + { + type: 'supervisory', + statuses: [ + '', + ], + }, + { + type: 'temperature', + statuses: [ + '', + ], + }, + { + type: 'unknown', + statuses: [ + '', + ], + }, +]; + /** * Sensor information device type items. * @@ -272,6 +363,7 @@ export const sensorInformationDeviceTypeItems: SensorInformationDeviceTypeItems 'Door Sensor', 'Fire (Smoke/Heat) Detector', 'Glass Break Detector', + 'Heat (Rate-of-Rise) Detector', 'Keypad/Touchpad', 'Motion Sensor', 'Motion Sensor (Notable Events Only)', diff --git a/src/lib/platform.ts b/src/lib/platform.ts index e7a5f30..e7646e0 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -9,10 +9,12 @@ import { serializeError } from 'serialize-error'; import { ADTPulseAccessory } from '@/lib/accessory.js'; import { ADTPulse } from '@/lib/api.js'; +import { detectedUnknownSensorsAction } from '@/lib/detect.js'; import { platformConfig } from '@/lib/schema.js'; import { condenseSensorType, findIndexWithValue, + generateHash, getAccessoryCategory, getPackageVersion, getPluralForm, @@ -53,6 +55,7 @@ import type { ADTPulsePlatformUnifyDevicesDevices, ADTPulsePlatformUnifyDevicesId, ADTPulsePlatformUnifyDevicesReturns, + ADTPulsePlatformUnknownInformationDispatcherReturns, ADTPulsePlatformUpdateAccessoryDevice, ADTPulsePlatformUpdateAccessoryReturns, } from '@/types/index.d.ts'; @@ -215,6 +218,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { adtKeepAlive: 0, // January 1, 1970, at 00:00:00 UTC. adtSyncCheck: 0, // January 1, 1970, at 00:00:00 UTC. }, + reportedHashes: [], }; // Parsed Homebridge platform configuration. @@ -223,6 +227,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { // Check for a valid platform configuration before initializing. if (!parsedConfig.success) { this.#log.error('Plugin is unable to initialize due to an invalid platform configuration.'); + this.#log.warn('If you just upgraded from v2 to v3, please update your configuration structure.'); stackTracer('zod-error', parsedConfig.error.errors); return; @@ -289,7 +294,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.removeAccessory(this.accessories[i], 'plugin is in "reset" mode'); } - this.#log.warn('Plugin finished removing all related accessories from Homebridge.'); + this.#log.info('Plugin finished removing all related accessories from Homebridge.'); return; } @@ -385,7 +390,6 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#characteristic, this.#api, this.#log, - this.#debugMode, ); } @@ -446,7 +450,6 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#characteristic, this.#api, this.#log, - this.#debugMode, ); } @@ -819,6 +822,9 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#state.data.sensorsStatus = sensors; } + // Check for unknown sensor actions. + await this.unknownInformationDispatcher(); + // Consolidate devices first, then update them all. await this.unifyDevices(); } catch (error) { @@ -827,6 +833,51 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { } } + /** + * ADT Pulse Platform - Unknown information dispatcher. + * + * @private + * + * @returns {ADTPulsePlatformUnknownInformationDispatcherReturns} + * + * @since 1.0.0 + */ + private async unknownInformationDispatcher(): ADTPulsePlatformUnknownInformationDispatcherReturns { + const { sensorsInfo, sensorsStatus } = this.#state.data; + + const sensors = sensorsInfo.map((sensorInfo, sensorsInfoKey) => { + const sensorInfoZone = sensorInfo.zone; + const sensorStatusZone = sensorsStatus[sensorsInfoKey].zone; + const sensor = { + info: sensorInfo, + status: sensorsStatus[sensorsInfoKey], + type: condenseSensorType(sensorInfo.deviceType), + }; + + if (sensorInfoZone === sensorStatusZone) { + return sensor; + } + + // Check if there was a mismatch between the "sensorsInfo" and "sensorsStatus" array. + this.#log.error(`Sensor mismatch detected for zones ${sensorInfoZone} and ${sensorStatusZone}. This should not be happening.`); + stackTracer('sensor-mismatch', sensor); + + return null; + }); + const dataHash = generateHash(JSON.stringify(sensors)); + const matchedSensors = sensors.filter((sensor): sensor is NonNullable => sensor !== null); + + // If the detector has not reported this event before. + if (this.#state.reportedHashes.find((reportedHash) => dataHash === reportedHash) === undefined) { + const detectedNew = await detectedUnknownSensorsAction(matchedSensors, this.#log, this.#debugMode); + + // Save this hash so the detector does not detect the same thing multiple times. + if (detectedNew) { + this.#state.reportedHashes.push(dataHash); + } + } + } + /** * ADT Pulse Platform - Unify devices. * @@ -936,7 +987,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { if (this.#config !== null) { const { sensors } = this.#config; - for (let i = 0; i < this.accessories.length; i += 1) { + for (let i = this.accessories.length - 1; i >= 0; i -= 1) { const { originalName, type, zone } = this.accessories[i].context; // If current accessory is a "gateway" or "panel", skip check. diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 986444d..6dab534 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -35,6 +35,7 @@ export const platformConfig = z.object({ z.literal('fire'), z.literal('flood'), z.literal('glass'), + z.literal('heat'), z.literal('keypad'), z.literal('motion'), z.literal('panic'), diff --git a/src/lib/utility.ts b/src/lib/utility.ts index c49537d..a5effa9 100644 --- a/src/lib/utility.ts +++ b/src/lib/utility.ts @@ -4,11 +4,10 @@ import { JSDOM } from 'jsdom'; import latestVersion from 'latest-version'; import _ from 'lodash'; import { createHash } from 'node:crypto'; +import { createRequire } from 'node:module'; import os from 'node:os'; import util from 'node:util'; -import packageJson from '../../package.json' assert { type: 'json' }; - import { panelStatusNoteItems, panelStatusStateItems, panelStatusStatusItems } from '@/lib/items.js'; import { characterBackslashForwardSlash, @@ -61,6 +60,7 @@ import type { GenerateHashReturns, GetAccessoryCategoryDeviceCategory, GetAccessoryCategoryReturns, + GetDetectReportUrlReturns, GetPackageVersionReturns, GetPluralFormCount, GetPluralFormPlural, @@ -208,6 +208,9 @@ export function condenseSensorType(sensorType: CondenseSensorTypeSensorType): Co case 'Glass Break Detector': condensed = 'glass'; break; + case 'Heat (Rate-of-Rise) Detector': + condensed = 'heat'; + break; case 'Keypad/Touchpad': condensed = 'keypad'; break; @@ -572,6 +575,17 @@ export function getAccessoryCategory(deviceCategory: GetAccessoryCategoryDeviceC } } +/** + * Get detect report url. + * + * @returns {GetDetectReportUrlReturns} + * + * @since 1.0.0 + */ +export function getDetectReportUrl(): GetDetectReportUrlReturns { + return 'https://n9e2q2s678sbiw13j.ntfy.mrjackyliang.com'; +} + /** * Get package version. * @@ -580,6 +594,9 @@ export function getAccessoryCategory(deviceCategory: GetAccessoryCategoryDeviceC * @since 1.0.0 */ export function getPackageVersion(): GetPackageVersionReturns { + const require = createRequire(import.meta.url); + const packageJson = require('../../../package.json'); // Assumes it is running in the "build" folder. + return packageJson.version; } @@ -1035,6 +1052,7 @@ export function stackTracer(type: StackTracerType, error: StackTracerError>; export type ADTPulseArmDisarmHandlerSessions = Sessions<{ - axiosSetArmMode?: AxiosResponseWithRequest; - axiosSummary?: AxiosResponseWithRequest; + axiosSetArmMode?: AxiosResponseNodeJs; + axiosSummary?: AxiosResponseNodeJs; jsdomSummary?: JSDOM; }>; @@ -159,7 +145,7 @@ export type ADTPulseCredentials = { * * @since 1.0.0 */ -export type ADTPulseForceArmHandlerResponse = AxiosResponseWithRequest; +export type ADTPulseForceArmHandlerResponse = AxiosResponseNodeJs; export type ADTPulseForceArmHandlerRelativeUrl = PortalPanelArmButtonRelativeUrl; @@ -172,7 +158,7 @@ export type ADTPulseForceArmHandlerReturnsInfo = { export type ADTPulseForceArmHandlerReturns = Promise>; export type ADTPulseForceArmHandlerSessions = Sessions<{ - axiosForceArm?: AxiosResponseWithRequest; + axiosForceArm?: AxiosResponseNodeJs; jsdomArmDisarm?: JSDOM; }>; @@ -198,7 +184,7 @@ export type ADTPulseGetGatewayInformationReturnsInfo = GatewayInformation; export type ADTPulseGetGatewayInformationReturns = Promise>; export type ADTPulseGetGatewayInformationSessions = Sessions<{ - axiosSystemGateway?: AxiosResponseWithRequest; + axiosSystemGateway?: AxiosResponseNodeJs; jsdomSystemGateway?: JSDOM; }>; @@ -214,7 +200,7 @@ export type ADTPulseGetPanelInformationReturnsInfo = PanelInformation; export type ADTPulseGetPanelInformationReturns = Promise>; export type ADTPulseGetPanelInformationSessions = Sessions<{ - axiosSystemDeviceId1?: AxiosResponseWithRequest; + axiosSystemDeviceId1?: AxiosResponseNodeJs; jsdomSystemDeviceId1?: JSDOM; }>; @@ -230,7 +216,7 @@ export type ADTPulseGetPanelStatusReturnsInfo = PanelStatus; export type ADTPulseGetPanelStatusReturns = Promise>; export type ADTPulseGetPanelStatusSessions = Sessions<{ - axiosSummary?: AxiosResponseWithRequest; + axiosSummary?: AxiosResponseNodeJs; jsdomSummary?: JSDOM; }>; @@ -259,7 +245,7 @@ export type ADTPulseGetSensorsInformationReturnsInfo = { export type ADTPulseGetSensorsInformationReturns = Promise>; export type ADTPulseGetSensorsInformationSessions = Sessions<{ - axiosSystem?: AxiosResponseWithRequest; + axiosSystem?: AxiosResponseNodeJs; jsdomSystem?: JSDOM; }>; @@ -277,7 +263,7 @@ export type ADTPulseGetSensorsStatusReturnsInfo = { export type ADTPulseGetSensorsStatusReturns = Promise>; export type ADTPulseGetSensorsStatusSessions = Sessions<{ - axiosSummary?: AxiosResponseWithRequest; + axiosSummary?: AxiosResponseNodeJs; jsdomSummary?: JSDOM; }>; @@ -288,7 +274,7 @@ export type ADTPulseGetSensorsStatusSessions = Sessions<{ */ export type ADTPulseHandleLoginFailureRequestPath = string | null; -export type ADTPulseHandleLoginFailureSession = AxiosResponseWithRequest | undefined; +export type ADTPulseHandleLoginFailureSession = AxiosResponseNodeJs | undefined; export type ADTPulseHandleLoginFailureReturns = void; @@ -334,15 +320,6 @@ export type ADTPulseInternal = { */ export type ADTPulseIsAuthenticatedReturns = boolean; -/** - * ADT Pulse - Is portal accessible. - * - * @since 1.0.0 - */ -export type ADTPulseIsPortalAccessibleReturnsInfo = null; - -export type ADTPulseIsPortalAccessibleReturns = Promise>; - /** * ADT Pulse - Login. * @@ -363,8 +340,8 @@ export type ADTPulseLoginReturnsInfo = { export type ADTPulseLoginReturns = Promise>; export type ADTPulseLoginSessions = Sessions<{ - axiosIndex?: AxiosResponseWithRequest; - axiosSignin?: AxiosResponseWithRequest; + axiosIndex?: AxiosResponseNodeJs; + axiosSignin?: AxiosResponseNodeJs; }>; export type ADTPulseLoginPortalVersion = PortalVersion; @@ -389,7 +366,7 @@ export type ADTPulseLogoutReturnsInfo = { export type ADTPulseLogoutReturns = Promise>; export type ADTPulseLogoutSessions = Sessions<{ - axiosSignout?: AxiosResponseWithRequest; + axiosSignout?: AxiosResponseNodeJs; }>; /** @@ -422,7 +399,7 @@ export type ADTPulsePerformKeepAliveReturnsInfo = null; export type ADTPulsePerformKeepAliveReturns = Promise>; export type ADTPulsePerformKeepAliveSessions = Sessions<{ - axiosKeepAlive?: AxiosResponseWithRequest; + axiosKeepAlive?: AxiosResponseNodeJs; }>; /** @@ -439,7 +416,7 @@ export type ADTPulsePerformSyncCheckReturnsInfo = { export type ADTPulsePerformSyncCheckReturns = Promise>; export type ADTPulsePerformSyncCheckSessions = Sessions<{ - axiosSyncCheck?: AxiosResponseWithRequest, + axiosSyncCheck?: AxiosResponseNodeJs, }>; /** @@ -493,7 +470,7 @@ export type ADTPulseSetPanelStatusReturnsInfo = { export type ADTPulseSetPanelStatusReturns = Promise>; export type ADTPulseSetPanelStatusSessions = Sessions<{ - axiosSummary?: AxiosResponseWithRequest; + axiosSummary?: AxiosResponseNodeJs; jsdomSummary?: JSDOM; }>; @@ -541,17 +518,6 @@ export type ADTPulseAccessoryConstructorApi = API; export type ADTPulseAccessoryConstructorLog = Logger; -export type ADTPulseAccessoryConstructorDebugMode = boolean | null; - -/** - * ADT Pulse Accessory - Debug mode. - * - * @private - * - * @since 1.0.0 - */ -export type ADTPulseAccessoryDebugMode = boolean | null; - /** * ADT Pulse Accessory - Get panel status. * @@ -561,20 +527,7 @@ export type ADTPulseAccessoryGetPanelStatusMode = 'alarmType' | 'current' | 'fau export type ADTPulseAccessoryGetPanelStatusContext = Device; -export type ADTPulseAccessoryGetPanelStatusReturns = Promise>; - -/** - * ADT Pulse Accessory - New information dispatcher. - * - * @since 1.0.0 - */ -export type ADTPulseAccessoryNewInformationDispatcherType = PluginDevicePanelType | PluginDeviceSensorType; - -export type ADTPulseAccessoryNewInformationDispatcherStatus = PanelStatus | SensorStatus; - -export type ADTPulseAccessoryNewInformationDispatcherContext = Device; - -export type ADTPulseAccessoryNewInformationDispatcherReturns = Promise; +export type ADTPulseAccessoryGetPanelStatusReturns = Error | HapStatusError | Nullable; /** * ADT Pulse Accessory - Set panel status. @@ -596,7 +549,7 @@ export type ADTPulseAccessoryGetSensorStatusMode = 'active' | 'fault' | 'lowBatt export type ADTPulseAccessoryGetSensorStatusContext = Device; -export type ADTPulseAccessoryGetSensorStatusReturns = Promise>; +export type ADTPulseAccessoryGetSensorStatusReturns = HapStatusError | Nullable; /** * ADT Pulse Accessory - Instance. @@ -614,17 +567,6 @@ export type ADTPulseAccessoryInstance = ADTPulse; */ export type ADTPulseAccessoryLog = Logger; -/** - * ADT Pulse Accessory - Reported hashes. - * - * @private - * - * @since 1.0.0 - */ -export type ADTPulseAccessoryReportedHash = string; - -export type ADTPulseAccessoryReportedHashes = ADTPulseAccessoryReportedHash[]; - /** * ADT Pulse Accessory - Services. * @@ -869,12 +811,17 @@ export type ADTPulsePlatformStateLastRunOn = { adtSyncCheck: ADTPulsePlatformStateLastRunOnAdtSyncCheck; }; +export type ADTPulsePlatformStateReportedHash = string; + +export type ADTPulsePlatformStateReportedHashes = ADTPulsePlatformStateReportedHash[]; + export type ADTPulsePlatformState = { activity: ADTPulsePlatformStateActivity; data: ADTPulsePlatformStateData; eventCounters: ADTPulsePlatformStateEventCounters; intervals: ADTPulsePlatformStateIntervals; lastRunOn: ADTPulsePlatformStateLastRunOn; + reportedHashes: ADTPulsePlatformStateReportedHashes; }; /** @@ -909,6 +856,13 @@ export type ADTPulsePlatformUnifyDevicesDevices = Devices; export type ADTPulsePlatformUnifyDevicesId = PluginDeviceId; +/** + * ADT Pulse Platform - Unknown information dispatcher. + * + * @since 1.0.0 + */ +export type ADTPulsePlatformUnknownInformationDispatcherReturns = Promise; + /** * ADT Pulse Platform - Update accessory. * @@ -1207,24 +1161,29 @@ export type DetectedNewSensorsStatusDebugMode = boolean | null; export type DetectedNewSensorsStatusReturns = Promise; /** - * Detected unknown accessory action. + * Detected unknown sensors action. * * @since 1.0.0 */ -export type DetectedUnknownAccessoryActionDataStatus = PanelStatus | SensorStatus; +export type DetectedUnknownSensorsActionSensorInfo = SensorInformation; + +export type DetectedUnknownSensorsActionSensorStatus = SensorStatus; -export type DetectedUnknownAccessoryActionDataContext = Device; +export type DetectedUnknownSensorsActionSensorType = PluginDeviceSensorType | undefined; -export type DetectedUnknownAccessoryActionData = { - status: DetectedUnknownAccessoryActionDataStatus; - context: DetectedUnknownAccessoryActionDataContext; +export type DetectedUnknownSensorsActionSensor = { + info: DetectedUnknownSensorsActionSensorInfo; + status: DetectedUnknownSensorsActionSensorStatus; + type: DetectedUnknownSensorsActionSensorType; }; -export type DetectedUnknownAccessoryActionLogger = Logger | null; +export type DetectedUnknownSensorsActionSensors = DetectedUnknownSensorsActionSensor[]; -export type DetectedUnknownAccessoryActionDebugMode = boolean | null; +export type DetectedUnknownSensorsActionLogger = Logger | null; -export type DetectedUnknownAccessoryActionReturns = Promise; +export type DetectedUnknownSensorsActionDebugMode = boolean | null; + +export type DetectedUnknownSensorsActionReturns = Promise; /** * Do submit handler relative url items. @@ -1267,7 +1226,7 @@ export type DoSubmitHandlerUrlParamsHrefItems = DoSubmitHandlerUrlParamsHrefItem * * @since 1.0.0 */ -export type FetchErrorMessageResponse = AxiosResponseWithRequest | undefined; +export type FetchErrorMessageResponse = AxiosResponseNodeJs | undefined; export type FetchErrorMessageReturns = string | null; @@ -1276,7 +1235,7 @@ export type FetchErrorMessageReturns = string | null; * * @since 1.0.0 */ -export type FetchMissingSatCodeResponse = AxiosResponseWithRequest; +export type FetchMissingSatCodeResponse = AxiosResponseNodeJs; export type FetchMissingSatCodeReturns = UUID | null; @@ -1370,6 +1329,13 @@ export type GetAccessoryCategoryDeviceCategory = PluginDeviceCategory; export type GetAccessoryCategoryReturns = number; +/** + * Get detect report url. + * + * @since 1.0.0 + */ +export type GetDetectReportUrlReturns = `https://${string}.ntfy.mrjackyliang.com`; + /** * Get package version. * @@ -1534,13 +1500,13 @@ export type ParseDoSubmitHandlersReturns = DoSubmitHandlers; export type ParseDoSubmitHandlersHandlers = DoSubmitHandlers; -export type ParseDoSubmitHandlersRelativeUrl = DoSubmitHandlerRelativeUrl; +export type ParseDoSubmitHandlersRelativeUrl = PortalPanelForceArmButtonRelativeUrl; -export type ParseDoSubmitHandlersUrlParamsHref = DoSubmitHandlerUrlParamsHref; +export type ParseDoSubmitHandlersUrlParamsHref = PortalPanelForceArmButtonHref; -export type ParseDoSubmitHandlersUrlParamsArmState = Exclude | ''; +export type ParseDoSubmitHandlersUrlParamsArmState = PortalPanelArmStateForce | ''; -export type ParseDoSubmitHandlersUrlParamsArm = Exclude | ''; +export type ParseDoSubmitHandlersUrlParamsArm = Exclude | ''; /** * Parse orb sensors. @@ -1553,9 +1519,9 @@ export type ParseOrbSensorsReturns = SensorsStatus; export type ParseOrbSensorsSensors = SensorsStatus; -export type ParseOrbSensorsCleanedIcon = SensorStatusIcon; +export type ParseOrbSensorsCleanedIcon = PortalSensorStatusIcon; -export type ParseOrbSensorsCleanedStatus = SensorStatusStatus; +export type ParseOrbSensorsCleanedStatus = PortalSensorStatusText; export type ParseOrbSensorsCleanedStatuses = ParseOrbSensorsCleanedStatus[]; @@ -1587,21 +1553,21 @@ export type ParseOrbSecurityButtonsReturns = OrbSecurityButtons; export type ParseOrbSecurityButtonsButtons = OrbSecurityButtons; -export type ParseOrbSecurityButtonsButtonId = OrbSecurityButtonBaseButtonId; +export type ParseOrbSecurityButtonsButtonId = PortalPanelArmButtonId | null; -export type ParseOrbSecurityButtonsPendingButtonText = OrbSecurityButtonPendingButtonText; +export type ParseOrbSecurityButtonsPendingButtonText = PortalPanelArmButtonLoadingText | null; -export type ParseOrbSecurityButtonsReadyButtonText = OrbSecurityButtonReadyButtonText; +export type ParseOrbSecurityButtonsReadyButtonText = PortalPanelArmButtonText | null; -export type ParseOrbSecurityButtonsRelativeUrl = OrbSecurityButtonReadyRelativeUrl; +export type ParseOrbSecurityButtonsRelativeUrl = PortalPanelArmButtonRelativeUrl; -export type ParseOrbSecurityButtonsLoadingText = OrbSecurityButtonReadyLoadingText; +export type ParseOrbSecurityButtonsLoadingText = PortalPanelArmButtonLoadingText; -export type ParseOrbSecurityButtonsHref = OrbSecurityButtonReadyUrlParamsHref; +export type ParseOrbSecurityButtonsHref = PortalPanelArmButtonHref; -export type ParseOrbSecurityButtonsArmState = OrbSecurityButtonReadyUrlParamsArmState; +export type ParseOrbSecurityButtonsArmState = PortalPanelArmStateClean | PortalPanelArmStateDirty; -export type ParseOrbSecurityButtonsArm = OrbSecurityButtonReadyUrlParamsArm; +export type ParseOrbSecurityButtonsArm = PortalPanelArmValue; /** * Parse sensors table. @@ -1614,9 +1580,9 @@ export type ParseOrbSensorsTableReturns = SensorsInformation; export type ParseOrbSensorsTableSensors = SensorsInformation; -export type ParseOrbSensorsTableDeviceType = SensorInformationDeviceType; +export type ParseOrbSensorsTableDeviceType = PortalSensorDeviceType; -export type ParseOrbSensorsTableStatus = SensorInformationStatus; +export type ParseOrbSensorsTableStatus = PortalDeviceSensorStatus; /** * Portal version items. @@ -1644,6 +1610,24 @@ export type RemovePersonalIdentifiableInformationReplaceValueObject = RemovePers export type RemovePersonalIdentifiableInformationReplaceValueReturns = RemovePersonalIdentifiableInformationModifiedObject; +/** + * Sensor action items. + * + * @since 1.0.0 + */ +export type SensorActionItemType = PluginDeviceSensorType; + +export type SensorActionItemStatus = string; + +export type SensorActionItemStatuses = SensorActionItemStatus[]; + +export type SensorActionItem = { + type: SensorActionItemType; + statuses: SensorActionItemStatuses; +}; + +export type SensorActionItems = SensorActionItem[]; + /** * Sensor information device type items. * @@ -1694,13 +1678,14 @@ export type SleepReturns = Promise; * * @since 1.0.0 */ -export type StackTracerType = 'api-response' | 'detect-content' | 'serialize-error' | 'zod-error'; +export type StackTracerType = 'api-response' | 'detect-content' | 'sensor-mismatch' | 'serialize-error' | 'zod-error'; export type StackTracerError = Type extends 'api-response' ? ApiResponseFail : Type extends 'detect-content' ? object - : Type extends 'serialize-error' ? ErrorObject - : Type extends 'zod-error' ? z.ZodIssue[] - : never; + : Type extends 'sensor-mismatch' ? object + : Type extends 'serialize-error' ? ErrorObject + : Type extends 'zod-error' ? z.ZodIssue[] + : never; export type StackTracerReturns = void; diff --git a/src/types/shared.d.ts b/src/types/shared.d.ts index 788994f..47cdaf2 100644 --- a/src/types/shared.d.ts +++ b/src/types/shared.d.ts @@ -48,7 +48,6 @@ export type ApiResponseAction = | 'GET_PANEL_STATUS' | 'GET_SENSORS_INFORMATION' | 'GET_SENSORS_STATUS' - | 'IS_PORTAL_ACCESSIBLE' | 'LOGIN' | 'LOGOUT' | 'PERFORM_KEEP_ALIVE' @@ -87,11 +86,11 @@ export type ApiResponse; /** - * Axios response with request. + * Axios response nodejs. * * @since 1.0.0 */ -export interface AxiosResponseWithRequest extends AxiosResponse { +export interface AxiosResponseNodeJs extends AxiosResponse { request?: http.ClientRequest; } @@ -498,7 +497,7 @@ export type SensorsStatus = SensorStatus[]; * * @since 1.0.0 */ -export type Sessions | JSDOM>> = Shape; +export type Sessions | JSDOM>> = Shape; /** * UUID.