Skip to content

Commit

Permalink
New features
Browse files Browse the repository at this point in the history
### FIXED
- Label for `test-api-logs` in `BUG-REPORT.yml` did not convert to markdown properly since it is unsupported.

### ADDED
- Advanced options to disable the "Alarm Ringing" switch and to "Ignore Sensor Problem Status".

### PLEASE READ
- The configuration has changed. Please add `"options": [],` to the configuration before upgrading or you will not be able to start the plugin.
  • Loading branch information
mrjackyliang committed Jan 26, 2024
1 parent 89e4ac6 commit 2efa519
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/BUG-REPORT.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
27 changes: 27 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
]
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
25 changes: 19 additions & 6 deletions src/lib/accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import type {
ADTPulseAccessoryActivity,
ADTPulseAccessoryApi,
ADTPulseAccessoryCharacteristic,
ADTPulseAccessoryConfig,
ADTPulseAccessoryConstructorAccessory,
ADTPulseAccessoryConstructorApi,
ADTPulseAccessoryConstructorCharacteristic,
ADTPulseAccessoryConstructorConfig,
ADTPulseAccessoryConstructorInstance,
ADTPulseAccessoryConstructorLog,
ADTPulseAccessoryConstructorService,
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -136,6 +148,7 @@ export class ADTPulseAccessory {
};
this.#api = api;
this.#characteristic = characteristic;
this.#config = config;
this.#instance = instance;
this.#log = log;
this.#services = {};
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
53 changes: 36 additions & 17 deletions src/lib/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
16 changes: 12 additions & 4 deletions src/lib/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import type {
IsEmptyOrbTextSummaryMatch,
IsEmptyOrbTextSummaryReturns,
IsForwardSlashOSReturns,
IsPanelAlarmActiveIgnoreSensorProblem,
IsPanelAlarmActivePanelStatuses,
IsPanelAlarmActiveReturns,
IsPluginOutdatedReturns,
Expand Down Expand Up @@ -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')
);
Expand Down
1 change: 1 addition & 0 deletions src/scripts/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class ADTPulseRepl {
fingerprint,
mode: 'normal',
speed: 1,
options: [],
sensors: [],
}, {
debug: true,
Expand Down
11 changes: 11 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -543,6 +550,8 @@ export type ADTPulseAccessoryConstructorAccessory = PlatformAccessory<Device>;

export type ADTPulseAccessoryConstructorState = ADTPulsePlatformState;

export type ADTPulseAccessoryConstructorConfig = ADTPulsePlatformConfig;

export type ADTPulseAccessoryConstructorInstance = ADTPulse;

export type ADTPulseAccessoryConstructorService = typeof Service;
Expand Down Expand Up @@ -1575,6 +1584,8 @@ export type IsForwardSlashOSReturns = boolean;
*/
export type IsPanelAlarmActivePanelStatuses = PanelStatusStatuses;

export type IsPanelAlarmActiveIgnoreSensorProblem = boolean;

export type IsPanelAlarmActiveReturns = boolean;

/**
Expand Down

0 comments on commit 2efa519

Please sign in to comment.