Skip to content

Commit

Permalink
Adds ability to limit the log output. Adds ability to change authenti…
Browse files Browse the repository at this point in the history
…cation and device state polling.
  • Loading branch information
mkormendy committed Dec 24, 2018
1 parent 0984473 commit 506f114
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 59 deletions.
45 changes: 45 additions & 0 deletions README.md
Expand Up @@ -67,9 +67,54 @@ Forked from John Hurliman's FrontPoint* plugin for Homebridge<small>[↗](https:
* "username": Alarm.com login username, same as app (required)
* "password": Alarm.com login password, same as app (required)
* "armingModes": Object of objects with arming mode options of boolean choices
* "authTimeoutMinutes": Timeout to Re-Authenticate session (**WARNING:** choosing a time less than 10 minutes could possibly ban/disable your account from Alarm.com)
* "pollTimeoutSeconds": Device polling interval (**WARNING:** choosing a time less than 60 seconds could possibly ban/disable your account from Alarm.com)
* <details><summary>"logLevel": Adjust what gets reported in the logs **(click to expand)**</summary>
* 0 = NO LOG ENTRIES
* 1 = ONLY ERRORS
* 2 = ONLY WARNINGS and ERRORS
* **3 = GENERAL NOTICES, ERRORS and WARNINGS (default)**
* 4 = VERBOSE (everything including )
</details>

# Troubleshooting

Before assuming that something is wrong with the plugin, please review the [issues on this project's github repository](https://github.com/mkormendy/homebridge-node-alarm-dot-com/issues?utf8=%E2%9C%93&q=sort%3Aupdated-desc+) to see if there's already a similar issue reported where a solution has been proposed or the outcome is expected due to limitations with the Alarm.com web API.

### Migrating from Bryan Bartow's homebridge-alarm.com

If you are replacing the Bryan Bartow's Homebridge plugin with this implementation, you may be required to delete the `~/.homebridge/accessories/cachedAccessories` file for the new platform to show up with the new panel, accessories and devices.

**WARNING:** If you delete the contents of the `~/.homebridge/persist` folder, your Homebridge and devices will become unresponsive and you will have to entirely re-pair the Homebridge bridge (remove and re-scan the QR-code for Homebridge and set up all of your accessories/devices again).

### Logging

The default setting for log entries is set to report critical errors, warnings about devices and notices about connecting to the Alarm.com account. Once you feel that your security system devices are being represented in HomeKit correctly you can choose to reduce the amount of information being output to the logs to save space or remove cruft while troubleshooting other Homebridge plugins.

To modify the log behaviour, add the "logLevel" field to the Alarmdotcom platform block in the Homebridge configuration file. The following example illustrates that we only want critical errors to be reported in the log.
#### Sample config.json with "logLevel" setting:
```json
//...
{
"platform": "Alarmdotcom",
"name": "Security System",
"username": "<ENTER YOUR ALARM.COM USERNAME>",
"password": "<ENTER YOUR ALARM.COM PASSWORD>",
"armingModes": {
"away": {
"noEntryDelay": false,
"silentArming": false
},
"night": {
"noEntryDelay": false,
"silentArming": false
},
"stay": {
"noEntryDelay": false,
"silentArming": false
}
},
"logLevel": 1
}
//...
```
154 changes: 95 additions & 59 deletions index.js
Expand Up @@ -3,8 +3,9 @@ const nodeADC = require('node-alarm-dot-com')
const PLUGIN_ID = 'homebridge-node-alarm-dot-com'
const PLUGIN_NAME = 'Alarmdotcom'
const MANUFACTURER = 'Alarm.com'
const AUTH_TIMEOUT_MS = 1000 * 60 * 10
const DEFAULT_REFRESH_S = 60
const AUTH_TIMEOUT_MINS = 10 // default for session authentication refresh
const POLL_TIMEOUT_SECS = 60 // default for device state polling
const LOG_LEVEL = 3 // default for log entries: 0 = NONE, 1 = ERROR, 2 = WARN, 3 = NOTICE, 4 = VERBOSE

let Accessory, Service, Characteristic, UUIDGen

Expand All @@ -22,13 +23,15 @@ class ADCPlatform {
this.log = log
this.config = config || { platform: PLUGIN_NAME }
this.debug = this.config.debug || false
this.config.logLevel = this.config.logLevel || LOG_LEVEL

if (!this.config.username)
throw new Error('Alarm.com: Missing required username in config')
if (!this.config.password)
throw new Error('Alarm.com: Missing required password in config')

this.config.refreshSeconds = this.config.refreshSeconds || DEFAULT_REFRESH_S
this.config.authTimeoutMinutes = this.config.authTimeoutMinutes || AUTH_TIMEOUT_MINS
this.config.pollTimeoutSeconds = this.config.pollTimeoutSeconds || POLL_TIMEOUT_SECS

this.accessories = {}
this.authOpts = { expires: +new Date() - 1 }
Expand Down Expand Up @@ -68,39 +71,44 @@ class ADCPlatform {
didFinishLaunching() {
this.listDevices()
.then(res => {
this.log(
`Received ${res.partitions.length} partitions(s) and ${
res.sensors.length
} sensor(s) from Alarm.com`
)
if (this.config.logLevel > 2)
this.log(
`Received ${res.partitions.length} partitions(s) and ${
res.sensors.length
} sensor(s) from Alarm.com`
)

res.partitions.forEach(p => {
this.addPartition(p)
this.log(`Added partition ${p.attributes.description} (${p.id})`)
if (this.config.logLevel > 2)
this.log(`Added partition ${p.attributes.description} (${p.id})`)
})

res.sensors.forEach(s => {
this.addSensor(s)
this.log(`Added sensor ${s.attributes.description} (${s.id})`)
if (this.config.logLevel > 2)
this.log(`Added sensor ${s.attributes.description} (${s.id})`)
})
})
.catch(err => {
this.log(`UNHANDLED ERROR: ${err.stack}`)
if (this.config.logLevel > 0)
this.log(`UNHANDLED ERROR: ${err.stack}`)
})

// Start a timer to periodically refresh status
this.timerID = setInterval(
() => this.refreshDevices(),
this.config.refreshSeconds * 1000
this.config.pollTimeoutSeconds * 1000
)
}

configureAccessory(accessory) {
this.log(
`Loaded from cache: ${accessory.context.name} (${
accessory.context.accID
})`
)
if (this.config.logLevel > 2)
this.log(
`Loaded from cache: ${accessory.context.name} (${
accessory.context.accID
})`
)

const existing = this.accessories[accessory.context.accID]
if (existing) this.removeAccessory(existing)
Expand All @@ -110,7 +118,8 @@ class ADCPlatform {
} else if (accessory.context.sensorType) {
this.setupSensor(accessory)
} else {
this.log(`Unrecognized accessory ${accessory.context.accID}`)
if (this.config.logLevel > 1)
this.log(`Unrecognized accessory ${accessory.context.accID}`)
}

this.accessories[accessory.context.accID] = accessory
Expand All @@ -121,17 +130,22 @@ class ADCPlatform {
login() {
// Cache expiration check
const now = +new Date()
if (this.authOpts.expires > now) return Promise.resolve(this.authOpts)
if (this.authOpts.expires > now)
return Promise.resolve(this.authOpts)

if (this.config.logLevel > 2)
this.log(`Logging into Alarm.com as ${this.config.username}`)

this.log(`Logging into Alarm.com as ${this.config.username}`)
return nodeADC
.login(this.config.username, this.config.password)
.then(authOpts => {
// Cache login response and estimated expiration time
authOpts.expires = +new Date() + AUTH_TIMEOUT_MS
authOpts.expires = +new Date() + 1000 * 60 * this.config.authTimeoutMinutes
this.authOpts = authOpts

this.log(`Logged into Alarm.com as ${this.config.username}`)
if (this.config.logLevel > 2)
this.log(`Logged into Alarm.com as ${this.config.username}`)

return authOpts
})
}
Expand All @@ -158,14 +172,16 @@ class ADCPlatform {
systemStates.forEach(system => {
system.partitions.forEach(partition => {
const accessory = this.accessories[partition.id]
if (!accessory) return this.addPartition(partition)
if (!accessory)
return this.addPartition(partition)

this.setPartitionState(accessory, partition)
})

system.sensors.forEach(sensor => {
const accessory = this.accessories[sensor.id]
if (!accessory) return this.addSensor(sensor)
if (!accessory)
return this.addSensor(sensor)

this.setSensorState(accessory, sensor)
})
Expand All @@ -177,7 +193,8 @@ class ADCPlatform {
addPartition(partition) {
const id = partition.id
let accessory = this.accessories[id]
if (accessory) this.removeAccessory(accessory)
if (accessory)
this.removeAccessory(accessory)

const name = partition.attributes.description
const uuid = UUIDGen.generate(id)
Expand All @@ -192,7 +209,9 @@ class ADCPlatform {
partitionType: 'default'
}

this.log(`Adding partition ${name} (id=${id}, uuid=${uuid})`)
if (this.config.logLevel > 2)
this.log(`Adding partition ${name} (id=${id}, uuid=${uuid})`)

this.addAccessory(accessory, Service.SecuritySystem, 'Security Panel')

this.setupPartition(accessory)
Expand All @@ -217,9 +236,10 @@ class ADCPlatform {
.setCharacteristic(Characteristic.SerialNumber, id)

// Setup event listeners

accessory.on('identify', (paired, callback) => {
this.log(`${name} identify requested, paired=${paired}`)
if (this.config.logLevel > 3)
this.log(`${name} identify requested, paired=${paired}`)

callback()
})

Expand All @@ -244,11 +264,14 @@ class ADCPlatform {
addSensor(sensor) {
const id = sensor.id
let accessory = this.accessories[id]
if (accessory) this.removeAccessory(accessory)
if (accessory)
this.removeAccessory(accessory)

const [type, characteristic, model] = getSensorType(sensor)
if (type === undefined) {
this.log(`Warning: Sensor with unknown state ${sensor.attributes.state}`)
if (this.config.logLevel > 1)
this.log(`Warning: Sensor with unknown state ${sensor.attributes.state}`)

return
}

Expand All @@ -264,7 +287,9 @@ class ADCPlatform {
sensorType: model
}

this.log(`Adding ${model} "${name}" (id=${id}, uuid=${uuid})`)
if (this.config.logLevel > 2)
this.log(`Adding ${model} "${name}" (id=${id}, uuid=${uuid})`)

this.addAccessory(accessory, type, model)

this.setupSensor(accessory)
Expand All @@ -278,7 +303,7 @@ class ADCPlatform {
const name = accessory.context.name
const model = accessory.context.sensorType
const [type, characteristic] = sensorModelToType(model)
if (!characteristic)
if (!characteristic && this.config.logLevel > 1)
throw new Error(`Unrecognized sensor ${accessory.context.accID}`)

// Always reachable
Expand All @@ -294,7 +319,9 @@ class ADCPlatform {
// Setup event listeners

accessory.on('identify', (paired, callback) => {
this.log(`${name} identify requested, paired=${paired}`)
if (this.config.logLevel > 2)
this.log(`${name} identify requested, paired=${paired}`)

callback()
})

Expand Down Expand Up @@ -328,11 +355,12 @@ class ADCPlatform {
const statusFault = Boolean(partition.attributes.needsClearIssuesPrompt)

if (state !== accessory.context.state) {
this.log(
`Updating partition ${id}, state=${state}, prev=${
accessory.context.state
}`
)
if (this.config.logLevel > 2)
this.log(
`Updating partition ${id}, state=${state}, prev=${
accessory.context.state
}`
)

accessory.context.state = state
accessory
Expand All @@ -342,11 +370,12 @@ class ADCPlatform {
}

if (desiredState !== accessory.context.desiredState) {
this.log(
`Updating partition ${id}, desiredState=${desiredState}, prev=${
accessory.context.desiredState
}`
)
if (this.config.logLevel > 2)
this.log(
`Updating partition ${id}, desiredState=${desiredState}, prev=${
accessory.context.desiredState
}`
)

accessory.context.desiredState = desiredState
accessory
Expand All @@ -356,11 +385,12 @@ class ADCPlatform {
}

if (statusFault !== accessory.context.statusFault) {
this.log(
`Updating partition ${id}, statusFault=${statusFault}, prev=${
accessory.context.statusFault
}`
)
if (this.config.logLevel > 2)
this.log(
`Updating partition ${id}, statusFault=${statusFault}, prev=${
accessory.context.statusFault
}`
)

accessory.context.statusFault = statusFault
accessory
Expand All @@ -379,9 +409,10 @@ class ADCPlatform {
const [type, characteristic, model] = getSensorType(sensor)

if (state !== accessory.context.state) {
this.log(
`Updating sensor ${id}, state=${state}, prev=${accessory.context.state}`
)
if (this.config.logLevel > 2)
this.log(
`Updating sensor ${id}, state=${state}, prev=${accessory.context.state}`
)

accessory.context.state = state
accessory
Expand All @@ -391,11 +422,12 @@ class ADCPlatform {
}

if (batteryLow !== accessory.context.batteryLow) {
this.log(
`Updating sensor ${id}, batteryLow=${batteryLow}, prev=${
accessory.context.batteryLow
}`
)
if (this.config.logLevel > 2)
this.log(
`Updating sensor ${id}, batteryLow=${batteryLow}, prev=${
accessory.context.batteryLow
}`
)

accessory.context.batteryLow = batteryLow
accessory
Expand All @@ -409,7 +441,8 @@ class ADCPlatform {
if (!accessory) return

const id = accessory.context.accID
this.log(`${accessory.context.name} (${id}) removed from HomeBridge.`)
if (this.config.logLevel > 2)
this.log(`${accessory.context.name} (${id}) removed from HomeBridge.`)
this.api.unregisterPlatformAccessories(PLUGIN_ID, PLUGIN_NAME, [accessory])
delete this.accessories[id]
}
Expand Down Expand Up @@ -444,11 +477,14 @@ class ADCPlatform {
break
default:
const msg = `Can't set SecuritySystem to unknown value ${value}`
this.log(msg)
if (this.config.logLevel > 1)
this.log(msg)
return callback(new Error(msg))
}

this.log(`changePartitionState(${accessory.context.accID}, ${value})`)
if (this.config.logLevel > 2)
this.log(`changePartitionState(${accessory.context.accID}, ${value})`)

accessory.context.desiredState = value

this.login()
Expand Down

0 comments on commit 506f114

Please sign in to comment.