diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index 633c046..95eff8d 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -71,7 +71,7 @@ body: - type: textarea id: test-api-logs attributes: - label: Log output of the `test-api` command + label: Log output of the "test-api" command description: From the Homebridge plugin path, most likely will be `/var/lib/homebridge/node_modules`, run this command `cd homebridge-adt-pulse && npm run test-api` and paste in the results below. __Do not include logs from other commands.__ This block will be automatically formatted into code, so __no need for backticks__. render: Shell validations: diff --git a/README.md b/README.md index e4e9e81..05f709d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Here is an example of how the `config.json` file for this plugin should be confi "fingerprint": "VGhpc0lzQVNlY3VyZVBhc3N3b3JkMTIzIQ==", "mode": "normal", "speed": 1, + "options": [], "sensors": [ { "name": "Family Room Couch Window 1", @@ -111,6 +112,20 @@ However, for certain consumer routers, this plugin may induce a network slowdown If the plugin does not operate under "Normal" mode, a warning will be issued on every startup, and this warning cannot be disabled. +## Specifying Advanced Options +Each alarm system is uniquely designed, and at times, functionalities may not align with your preferences. + +The options provided give you the flexibility to deactivate specific aspects of the plugin; however, please exercise caution, as doing so may result in the loss of expected functionality. + +- __To disable the "Alarm Ringing" switch:__ + - Include the `"disableAlarmRingingSwitch"` value in the `options` array. + - ⚠️ Enabling this option will prevent you from being able to silence a ringing alarm when the system is in "Disarmed" mode. +- __To ignore "Sensor Problem" statuses:__ + - Include the `"ignoreSensorProblemStatus"` value in the `options` array. + - ⚠️ Enabling this option will prevent you from being able to silence a ringing alarm triggered by a "Sensor Problem" or "Sensor Problems" status. + +If the `options` array is not empty, a warning will be displayed upon every startup for every enabled option, and this warning cannot be disabled. + ## Specifying the Sensors In the past, this plugin would automatically detect sensors and dynamically manage their addition and removal based on its observations. @@ -158,6 +173,24 @@ Consumers would enable debug mode, but forget to also enable Homebridge debug mo To improve this, debug mode is now activated __ONLY when debug mode is enabled on Homebridge__ itself. This approach promotes isolation (logs can be separated for each bridge) and helps enhance the troubleshooting experience in case any issues arise. +## API Test and REPL Playground Scripts +If any unusual occurrences arise, utilize the provided `test-api` or `repl` commands within this plugin to troubleshoot potential issues. + +- To confirm if the plugin is communicating with the portal correctly, use the command `npm run test-api`. +- To access the playground (Read-eval-print loop mode), use the command `npm run repl`. + +Ensure you are inside the `node_modules/homebridge-adt-pulse` directory when attempting to access these commands. The location of `node_modules` may vary based on the system you are using: +- [Raspbian](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Raspbian#configuration-reference) +- [Debian or Ubuntu Linux](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Debian-or-Ubuntu-Linux#configuration-reference) +- [Red Hat, CentOS or Fedora Linux](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Red-Hat%2C-CentOS-or-Fedora-Linux#configuration-reference) +- [Arch Linux](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Arch-Linux#configuration-reference) +- [macOS](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-macOS#configuration-reference) +- [Windows 10 Using Hyper V](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Windows-10-Using-Hyper-V#configuration-reference) +- [Docker](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Docker#configuration-reference) +- [Unraid](https://github.com/homebridge/docker-homebridge/wiki/Homebridge-on-Unraid#configuration-reference) +- [TrueNAS Scale](https://github.com/homebridge/docker-homebridge/wiki/Homebridge-on-TrueNAS-Scale#configuration-reference) +- [Synology DSM](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Synology-DSM#configuration-reference) + ## Temperature Sensors in HAP Protocol The Temperature Sensor (`temperature`) functions differently compared to standard contact sensors when it comes to processing sensor statuses. diff --git a/config.schema.json b/config.schema.json index 804a689..0352b4d 100644 --- a/config.schema.json +++ b/config.schema.json @@ -125,6 +125,19 @@ ], "default": 1 }, + "options": { + "title": "Advanced Options", + "type": "array", + "required": true, + "description": "Customize the features of this plugin. Please note these advanced options will disable expected functionality. Only enable them if necessary.", + "items": { + "type": "string", + "enum": [ + "disableAlarmRingingSwitch", + "ignoreSensorProblemStatus" + ] + } + }, "sensors": { "title": "Sensors", "type": "array", @@ -248,6 +261,20 @@ { "key": "speed", "type": "select" + }, + { + "key": "options", + "type": "checkboxes", + "titleMap": [ + { + "value": "disableAlarmRingingSwitch", + "name": "Disable \"Alarm Ringing\" switch" + }, + { + "value": "ignoreSensorProblemStatus", + "name": "Ignore Sensor Problem Status" + } + ] } ] }, diff --git a/package.json b/package.json index b2cf69b..8403e5f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebridge-adt-pulse", "displayName": "Homebridge ADT Pulse", - "version": "3.0.6", + "version": "3.1.0", "description": "Homebridge security system platform for ADT Pulse", "main": "./build/index.js", "exports": "./build/index.js", diff --git a/src/lib/accessory.ts b/src/lib/accessory.ts index 076a095..92289da 100644 --- a/src/lib/accessory.ts +++ b/src/lib/accessory.ts @@ -12,9 +12,11 @@ import type { ADTPulseAccessoryActivity, ADTPulseAccessoryApi, ADTPulseAccessoryCharacteristic, + ADTPulseAccessoryConfig, ADTPulseAccessoryConstructorAccessory, ADTPulseAccessoryConstructorApi, ADTPulseAccessoryConstructorCharacteristic, + ADTPulseAccessoryConstructorConfig, ADTPulseAccessoryConstructorInstance, ADTPulseAccessoryConstructorLog, ADTPulseAccessoryConstructorService, @@ -77,6 +79,15 @@ export class ADTPulseAccessory { */ readonly #characteristic: ADTPulseAccessoryCharacteristic; + /** + * ADT Pulse Accessory - Config. + * + * @private + * + * @since 1.0.0 + */ + #config: ADTPulseAccessoryConfig; + /** * ADT Pulse Accessory - Instance. * @@ -118,6 +129,7 @@ export class ADTPulseAccessory { * * @param {ADTPulseAccessoryConstructorAccessory} accessory - Accessory. * @param {ADTPulseAccessoryConstructorState} state - State. + * @param {ADTPulseAccessoryConstructorConfig} config - Config. * @param {ADTPulseAccessoryConstructorInstance} instance - Instance. * @param {ADTPulseAccessoryConstructorService} service - Service. * @param {ADTPulseAccessoryConstructorCharacteristic} characteristic - Characteristic. @@ -126,7 +138,7 @@ export class ADTPulseAccessory { * * @since 1.0.0 */ - public constructor(accessory: ADTPulseAccessoryConstructorAccessory, state: ADTPulseAccessoryConstructorState, instance: ADTPulseAccessoryConstructorInstance, service: ADTPulseAccessoryConstructorService, characteristic: ADTPulseAccessoryConstructorCharacteristic, api: ADTPulseAccessoryConstructorApi, log: ADTPulseAccessoryConstructorLog) { + public constructor(accessory: ADTPulseAccessoryConstructorAccessory, state: ADTPulseAccessoryConstructorState, config: ADTPulseAccessoryConstructorConfig, instance: ADTPulseAccessoryConstructorInstance, service: ADTPulseAccessoryConstructorService, characteristic: ADTPulseAccessoryConstructorCharacteristic, api: ADTPulseAccessoryConstructorApi, log: ADTPulseAccessoryConstructorLog) { this.#accessory = accessory; this.#activity = { isBusy: false, @@ -136,6 +148,7 @@ export class ADTPulseAccessory { }; this.#api = api; this.#characteristic = characteristic; + this.#config = config; this.#instance = instance; this.#log = log; this.#services = {}; @@ -613,7 +626,7 @@ export class ADTPulseAccessory { // Find the state for "Security System Alarm Type" (optional characteristic). if (mode === 'alarmType') { - if (isPanelAlarmActive(panelStatuses)) { + if (isPanelAlarmActive(panelStatuses, this.#config?.options.includes('ignoreSensorProblemStatus') ?? false)) { return 1; } @@ -649,7 +662,7 @@ export class ADTPulseAccessory { switch (true) { case mode === 'current' && this.#activity.isBusy && this.#activity.setCurrentValue !== null: return this.#activity.setCurrentValue; - case mode === 'current' && isPanelAlarmActive(panelStatuses): + case mode === 'current' && isPanelAlarmActive(panelStatuses, this.#config?.options.includes('ignoreSensorProblemStatus') ?? false): return this.#characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; case mode === 'current' && panelStates.includes('Armed Stay'): return this.#characteristic.SecuritySystemCurrentState.STAY_ARM; @@ -735,7 +748,7 @@ export class ADTPulseAccessory { } // Only show as "On" if alarm is ringing. - return isPanelAlarmActive(panelStatuses); + return isPanelAlarmActive(panelStatuses, this.#config?.options.includes('ignoreSensorProblemStatus') ?? false); } /** @@ -785,7 +798,7 @@ export class ADTPulseAccessory { const { panelStates } = this.#state.data.panelStatus; const condensedPanelStates = condensePanelStates(this.#characteristic, panelStates); - const isAlarmActive = isPanelAlarmActive(this.#state.data.panelStatus.panelStatuses); + const isAlarmActive = isPanelAlarmActive(this.#state.data.panelStatus.panelStatuses, this.#config?.options.includes('ignoreSensorProblemStatus') ?? false); // If panel status cannot be found or most likely "Status Unavailable". if (condensedPanelStates === undefined) { @@ -924,7 +937,7 @@ export class ADTPulseAccessory { const { panelStates } = this.#state.data.panelStatus; const condensedPanelStates = condensePanelStates(this.#characteristic, panelStates); - const isAlarmActive = isPanelAlarmActive(this.#state.data.panelStatus.panelStatuses); + const isAlarmActive = isPanelAlarmActive(this.#state.data.panelStatus.panelStatuses, this.#config?.options.includes('ignoreSensorProblemStatus') ?? false); // If panel status cannot be found or most likely "Status Unavailable". if (condensedPanelStates === undefined) { diff --git a/src/lib/platform.ts b/src/lib/platform.ts index 3f8df90..a48a489 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -234,7 +234,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.'); + this.#log.warn('If you just upgraded from v2 to v3 / v3.1, please update your configuration structure.'); stackTracer('zod-error', parsedConfig.error.errors); return; @@ -314,6 +314,16 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#constants.intervalTimestamps.synchronize *= (1 / this.#config.speed); } + // If the config specifies that the plugin should disable the "Alarm Ringing" switch. + if (this.#config.options.includes('disableAlarmRingingSwitch')) { + this.#log.warn('Plugin accessory for "Alarm Ringing" is disabled. You will NOT be able to silence a ringing alarm when the system is in "Disarmed" mode.'); + } + + // If the config specifies that the plugin should ignore "Sensor Problem" and "Sensor Problems" statuses. + if (this.#config.options.includes('ignoreSensorProblemStatus')) { + this.#log.warn('Plugin ignoring "Sensor Problem" and "Sensor Problems" statuses. You will not be able to silence a ringing alarm triggered by a "Sensor Problem" or "Sensor Problems" status.'); + } + // Initialize the API instance. this.#instance = new ADTPulse( this.#config, @@ -391,6 +401,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#handlers[device.id] = new ADTPulseAccessory( typedAccessory, this.#state, + this.#config, this.#instance, this.#service, this.#characteristic, @@ -453,6 +464,7 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { this.#handlers[device.id] = new ADTPulseAccessory( value, this.#state, + this.#config, this.#instance, this.#service, this.#characteristic, @@ -1088,21 +1100,23 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { }); // A separate switch designed to turn off ringing alarm while in "Disarmed" state. - devices.push({ - id: idSwitch, - name: 'Alarm Ringing', - originalName: 'Alarm Ringing', - type: 'panelSwitch', - zone: null, - category: 'SWITCH', - manufacturer: 'ADT Pulse for Homebridge', - model: 'N/A', - serial: 'N/A', - firmware: null, - hardware: null, - software: getPackageVersion(), - uuid: this.#api.hap.uuid.generate(idSwitch), - }); + if (this.#config !== null && !this.#config.options.includes('disableAlarmRingingSwitch')) { + devices.push({ + id: idSwitch, + name: 'Alarm Ringing', + originalName: 'Alarm Ringing', + type: 'panelSwitch', + zone: null, + category: 'SWITCH', + manufacturer: 'ADT Pulse for Homebridge', + model: 'N/A', + serial: 'N/A', + firmware: null, + hardware: null, + software: getPackageVersion(), + uuid: this.#api.hap.uuid.generate(idSwitch), + }); + } } // Add sensors as an accessory. @@ -1156,11 +1170,16 @@ export class ADTPulsePlatform implements ADTPulsePlatformPlugin { // Check if accessories were removed from config. if (this.#config !== null) { - const { sensors } = this.#config; + const { options, sensors } = this.#config; for (let i = this.#accessories.length - 1; i >= 0; i -= 1) { const { originalName, type, zone } = this.#accessories[i].context; + // Remove the "panelSwitch" since the user disabled it. + if (type === 'panelSwitch' && options.includes('disableAlarmRingingSwitch')) { + this.removeAccessory(this.#accessories[i], 'user disabled this feature'); + } + // If current accessory is a "gateway", "panel", or "panelSwitch", skip check. if (type === 'gateway' || type === 'panel' || type === 'panelSwitch') { continue; diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 7e24314..94c980d 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -26,6 +26,10 @@ export const platformConfig = z.object({ z.literal(0.5), z.literal(0.25), ]), + options: z.array(z.union([ + z.literal('disableAlarmRingingSwitch'), + z.literal('ignoreSensorProblemStatus'), + ])), sensors: z.array(z.object({ name: z.string().min(1).max(50).optional(), adtName: z.string().min(1).max(100), diff --git a/src/lib/utility.ts b/src/lib/utility.ts index b6c2600..bf12a20 100644 --- a/src/lib/utility.ts +++ b/src/lib/utility.ts @@ -81,6 +81,7 @@ import type { IsEmptyOrbTextSummaryMatch, IsEmptyOrbTextSummaryReturns, IsForwardSlashOSReturns, + IsPanelAlarmActiveIgnoreSensorProblem, IsPanelAlarmActivePanelStatuses, IsPanelAlarmActiveReturns, IsPluginOutdatedReturns, @@ -861,19 +862,26 @@ export function isForwardSlashOS(): IsForwardSlashOSReturns { /** * Is panel alarm active. * - * @param {IsPanelAlarmActivePanelStatuses} panelStatuses - Panel statuses. + * @param {IsPanelAlarmActivePanelStatuses} panelStatuses - Panel statuses. + * @param {IsPanelAlarmActiveIgnoreSensorProblem} ignoreSensorProblem - Ignore sensor problem. * * @returns {IsPanelAlarmActiveReturns} * * @since 1.0.0 */ -export function isPanelAlarmActive(panelStatuses: IsPanelAlarmActivePanelStatuses): IsPanelAlarmActiveReturns { +export function isPanelAlarmActive(panelStatuses: IsPanelAlarmActivePanelStatuses, ignoreSensorProblem: IsPanelAlarmActiveIgnoreSensorProblem): IsPanelAlarmActiveReturns { return ( panelStatuses.includes('BURGLARY ALARM') || panelStatuses.includes('Carbon Monoxide Alarm') || panelStatuses.includes('FIRE ALARM') - || panelStatuses.includes('Sensor Problem') // User must fix the sensor issue before "Triggered" in Home app will go away. - || panelStatuses.includes('Sensor Problems') // User must fix the sensor issue before "Triggered" in Home app will go away. + || ( + panelStatuses.includes('Sensor Problem') + && !ignoreSensorProblem + ) + || ( + panelStatuses.includes('Sensor Problems') + && !ignoreSensorProblem + ) || panelStatuses.includes('Uncleared Alarm') || panelStatuses.includes('WATER ALARM') ); diff --git a/src/scripts/repl.ts b/src/scripts/repl.ts index d944987..d1db854 100644 --- a/src/scripts/repl.ts +++ b/src/scripts/repl.ts @@ -166,6 +166,7 @@ class ADTPulseRepl { fingerprint, mode: 'normal', speed: 1, + options: [], sensors: [], }, { debug: true, diff --git a/src/types/index.d.ts b/src/types/index.d.ts index a827a35..306c705 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -534,6 +534,13 @@ export type ADTPulseAccessoryApi = API; */ export type ADTPulseAccessoryCharacteristic = typeof Characteristic; +/** + * ADT Pulse Accessory - Config. + * + * @since 1.0.0 + */ +export type ADTPulseAccessoryConfig = ADTPulsePlatformConfig; + /** * ADT Pulse Accessory - Constructor. * @@ -543,6 +550,8 @@ export type ADTPulseAccessoryConstructorAccessory = PlatformAccessory; export type ADTPulseAccessoryConstructorState = ADTPulsePlatformState; +export type ADTPulseAccessoryConstructorConfig = ADTPulsePlatformConfig; + export type ADTPulseAccessoryConstructorInstance = ADTPulse; export type ADTPulseAccessoryConstructorService = typeof Service; @@ -1575,6 +1584,8 @@ export type IsForwardSlashOSReturns = boolean; */ export type IsPanelAlarmActivePanelStatuses = PanelStatusStatuses; +export type IsPanelAlarmActiveIgnoreSensorProblem = boolean; + export type IsPanelAlarmActiveReturns = boolean; /**