From 8766957bc0c7398fe44b9f3eda288225ddc91bba Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:28:19 -0500 Subject: [PATCH 01/14] Extra spaces in .gitignore --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index af50d98..f5c4034 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .DS_Store - node_modules/ - package-lock.json - dist/ +node_modules/ +package-lock.json +dist/ From 1a1a2b1d8b0f31d7737c86091ae809f7750a1a3b Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:28:36 -0500 Subject: [PATCH 02/14] Add linting to package.json --- .eslintrc.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +++++++--- 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..1a0e2aa --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "ignorePatterns": ["dist"], + "rules": { + "quotes": ["warn", "single"], + "indent": [ + "warn", + 2, + { + "SwitchCase": 1 + } + ], + "linebreak-style": ["warn", "unix"], + "semi": ["warn", "always"], + "comma-dangle": ["warn", "always-multiline"], + "dot-notation": "warn", + "eqeqeq": "warn", + "curly": ["warn", "all"], + "brace-style": ["warn"], + "prefer-arrow-callback": ["warn"], + "max-len": ["warn", 140], + "no-console": ["warn"], + "lines-between-class-members": [ + "warn", + "always", + { + "exceptAfterSingleLine": true + } + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-this-alias": "off" + } +} diff --git a/package.json b/package.json index cd428b6..c26cea0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "clean": "rimraf ./dist", "build": "rimraf ./dist && tsc", - "prepublishOnly": "npm run build", + "lint": "eslint src/**.ts", + "prepublishOnly": "npm run lint && npm run build", "postpublish": "npm run clean", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -42,9 +43,12 @@ "homepage": "https://github.com/hjdhjd/homebridge-myq2#readme", "devDependencies": { "@types/node": "10.17.19", - "typescript": "^3.8.3", + "@typescript-eslint/eslint-plugin": "^3.1.0", + "@typescript-eslint/parser": "^3.1.0", + "eslint": "^7.4.0", + "homebridge": "^1.0.4", "rimraf": "^3.0.2", - "homebridge": "^1.0.4" + "typescript": "^3.8.3" }, "dependencies": { "@types/node-fetch": "^2.5.7", From ceb79cf2c8d55ccaad71922df7cbe2790c228b00 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:31:48 -0500 Subject: [PATCH 03/14] Simplify logic --- src/index.ts | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9824e79..35e6965 100644 --- a/src/index.ts +++ b/src/index.ts @@ -469,20 +469,17 @@ class myQPlatform implements DynamicPlatformPlugin { // our target state needs to be the completion of those actions. If we're stopped or // obstructed, we're going to assume the desired target state is to be open, since that // is the typical garage door behavior. - if(myQState == hap.Characteristic.CurrentDoorState.OPEN) { - return hap.Characteristic.CurrentDoorState.OPEN; - } else if(myQState == hap.Characteristic.CurrentDoorState.CLOSED) { - return hap.Characteristic.CurrentDoorState.CLOSED; - } else if(myQState == hap.Characteristic.CurrentDoorState.OPENING) { - return hap.Characteristic.CurrentDoorState.OPEN; - } else if(myQState == hap.Characteristic.CurrentDoorState.CLOSING) { - return hap.Characteristic.CurrentDoorState.CLOSED; - } else if(myQState == hap.Characteristic.CurrentDoorState.STOPPED) { - return hap.Characteristic.CurrentDoorState.OPEN; - } else if(myQState == this.myQOBSTRUCTED) { - return hap.Characteristic.CurrentDoorState.OPEN; - } else { - return hap.Characteristic.CurrentDoorState.CLOSED; + switch(myQState) { + case hap.Characteristic.CurrentDoorState.OPEN: + case hap.Characteristic.CurrentDoorState.OPENING: + case hap.Characteristic.CurrentDoorState.STOPPED: + case this.myQOBSTRUCTED: + return hap.Characteristic.CurrentDoorState.OPEN; + + case hap.Characteristic.CurrentDoorState.CLOSED: + case hap.Characteristic.CurrentDoorState.CLOSING: + default: + return hap.Characteristic.CurrentDoorState.CLOSED; } } @@ -493,20 +490,17 @@ class myQPlatform implements DynamicPlatformPlugin { // our target state needs to be the completion of those actions. If we're stopped or // obstructed, we're going to assume the desired target state is to be open, since that // is the typical garage door behavior. - if(myQState == hap.Characteristic.CurrentDoorState.OPEN) { - return hap.Characteristic.TargetDoorState.OPEN; - } else if(myQState == hap.Characteristic.CurrentDoorState.CLOSED) { - return hap.Characteristic.TargetDoorState.CLOSED; - } else if(myQState == hap.Characteristic.CurrentDoorState.OPENING) { - return hap.Characteristic.TargetDoorState.OPEN; - } else if(myQState == hap.Characteristic.CurrentDoorState.CLOSING) { - return hap.Characteristic.TargetDoorState.CLOSED; - } else if(myQState == hap.Characteristic.CurrentDoorState.STOPPED) { - return hap.Characteristic.TargetDoorState.OPEN; - } else if(myQState == this.myQOBSTRUCTED) { - return hap.Characteristic.TargetDoorState.OPEN; - } else { - return hap.Characteristic.TargetDoorState.CLOSED; + switch(myQState) { + case hap.Characteristic.CurrentDoorState.OPEN: + case hap.Characteristic.CurrentDoorState.OPENING: + case hap.Characteristic.CurrentDoorState.STOPPED: + case this.myQOBSTRUCTED: + return hap.Characteristic.TargetDoorState.OPEN; + + case hap.Characteristic.CurrentDoorState.CLOSED: + case hap.Characteristic.CurrentDoorState.CLOSING: + default: + return hap.Characteristic.TargetDoorState.CLOSED; } } From 748294852d688282c73dfd33d5a67357333f576e Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:32:30 -0500 Subject: [PATCH 04/14] Linting --- src/index.ts | 183 +++++++++++++++++++++++++------------------------ src/myq.ts | 187 +++++++++++++++++++++++---------------------------- 2 files changed, 179 insertions(+), 191 deletions(-) diff --git a/src/index.ts b/src/index.ts index 35e6965..cb98bd4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,12 +13,12 @@ import { PlatformAccessory, PlatformAccessoryEvent, PlatformConfig, -} from "homebridge"; +} from 'homebridge'; import { myQ, myQDevice } from './myq'; -const PLUGIN_NAME = "homebridge-myq2"; -const PLATFORM_NAME = "myQ"; +const PLUGIN_NAME = 'homebridge-myq2'; +const PLATFORM_NAME = 'myQ'; let hap: HAP; let Accessory: typeof PlatformAccessory; @@ -35,7 +35,7 @@ class myQPlatform implements DynamicPlatformPlugin { private readonly api: API; private myQ!: myQ; private myQOBSTRUCTED = 8675309; - + private configOptions: string[] = []; private configPoll = { @@ -45,20 +45,25 @@ class myQPlatform implements DynamicPlatformPlugin { closeDuration: 25, shortPollDuration: 600, maxCount: 0, - count: 0 + count: 0, + }; + + private configDevices = { + gateways: [], + openers: [], }; private pollingTimer!: NodeJS.Timeout; private myQStateMap: {[index: number]: string} = { - [hap.Characteristic.CurrentDoorState.OPEN]: "open", - [hap.Characteristic.CurrentDoorState.CLOSED]: "closed", - [hap.Characteristic.CurrentDoorState.OPENING]: "opening", - [hap.Characteristic.CurrentDoorState.CLOSING]: "closing", - [hap.Characteristic.CurrentDoorState.STOPPED]: "stopped", - [this.myQOBSTRUCTED]: "obstructed" + [hap.Characteristic.CurrentDoorState.OPEN]: 'open', + [hap.Characteristic.CurrentDoorState.CLOSED]: 'closed', + [hap.Characteristic.CurrentDoorState.OPENING]: 'opening', + [hap.Characteristic.CurrentDoorState.CLOSING]: 'closing', + [hap.Characteristic.CurrentDoorState.STOPPED]: 'stopped', + [this.myQOBSTRUCTED]: 'obstructed', }; - + private readonly accessories: PlatformAccessory[] = []; constructor(log: Logging, config: PlatformConfig, api: API) { @@ -72,7 +77,7 @@ class myQPlatform implements DynamicPlatformPlugin { // We need login credentials or we're not starting. if(!config.email || !config.password) { - this.log("No myQ login credentials specified."); + this.log('No myQ login credentials specified.'); return; } @@ -80,7 +85,7 @@ class myQPlatform implements DynamicPlatformPlugin { if(config.options) { this.configOptions = config.options; } - + if(config.longPoll) { this.configPoll.longPoll = config.longPoll; } @@ -118,7 +123,7 @@ class myQPlatform implements DynamicPlatformPlugin { configureAccessory(accessory: PlatformAccessory): void { // Give this accessory an identity handler. accessory.on(PlatformAccessoryEvent.IDENTIFY, () => { - this.log("%s identified!", accessory.displayName); + this.log('%s identified!', accessory.displayName); }); const gdOpener = accessory.getService(hap.Service.GarageDoorOpener); @@ -131,9 +136,10 @@ class myQPlatform implements DynamicPlatformPlugin { // Add the garage door opener service to the accessory. const gdService = new hap.Service.GarageDoorOpener(accessory.displayName); - // The initial door state when we first startup. The bias functions will help us figure out what to do if we're caught in a tweener state. - var doorCurrentState = this.doorCurrentBias(accessory.context.doorState); - var doorTargetState = this.doorTargetBias(doorCurrentState); + // The initial door state when we first startup. The bias functions will help us + // figure out what to do if we're caught in a tweener state. + const doorCurrentState = this.doorCurrentBias(accessory.context.doorState); + const doorTargetState = this.doorTargetBias(doorCurrentState); // Add all the events to our accessory so we can act on HomeKit actions. We also set the current and target door states // based on our saved state from previous sessions. @@ -143,22 +149,23 @@ class myQPlatform implements DynamicPlatformPlugin { .setCharacteristic(hap.Characteristic.TargetDoorState, doorTargetState) .getCharacteristic(hap.Characteristic.TargetDoorState)! .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - var myQState = this.doorStatus(accessory); + const myQState = this.doorStatus(accessory); // If we are already opening or closing the garage door, we error out. myQ doesn't appear to allow // interruptions to an open or close command that is currently executing - it must be allowed to // complete it's action before accepting a new one. - if((myQState == hap.Characteristic.CurrentDoorState.OPENING) || (myQState == hap.Characteristic.CurrentDoorState.CLOSING)) { - var actionExisting = myQState == hap.Characteristic.CurrentDoorState.OPENING ? "opening" : "closing"; - var actionAttempt = value == hap.Characteristic.TargetDoorState.CLOSED ? "close" : "open"; + if((myQState === hap.Characteristic.CurrentDoorState.OPENING) || (myQState === hap.Characteristic.CurrentDoorState.CLOSING)) { + const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? 'opening' : 'closing'; + const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? 'close' : 'open'; - this.log("%s - unable to %s door while currently trying to finish %s. myQ must complete it's existing action before attmepting a new one.", accessory.displayName, actionAttempt, actionExisting); + this.log('%s - unable to %s door while currently trying to finish %s. myQ must complete it\'s existing action ' + + 'before attmepting a new one.', accessory.displayName, actionAttempt, actionExisting); - callback(new Error("Unable to accept a new set event while another is completing.")); - } else if (value == hap.Characteristic.TargetDoorState.CLOSED) { + callback(new Error('Unable to accept a new set event while another is completing.')); + } else if (value === hap.Characteristic.TargetDoorState.CLOSED) { // HomeKit is informing us to close the door. - this.log("%s is closing.", accessory.displayName); - this.doorCommand(accessory, "close"); + this.log('%s is closing.', accessory.displayName); + this.doorCommand(accessory, 'close'); callback(); @@ -169,10 +176,10 @@ class myQPlatform implements DynamicPlatformPlugin { accessory .getService(hap.Service.GarageDoorOpener)! .setCharacteristic(hap.Characteristic.CurrentDoorState, hap.Characteristic.CurrentDoorState.CLOSING); - } else if (value == hap.Characteristic.TargetDoorState.OPEN) { + } else if (value === hap.Characteristic.TargetDoorState.OPEN) { // HomeKit is informing us to open the door. - this.log("%s is opening.", accessory.displayName); - this.doorCommand(accessory, "open"); + this.log('%s is opening.', accessory.displayName); + this.doorCommand(accessory, 'open'); callback(); @@ -182,8 +189,8 @@ class myQPlatform implements DynamicPlatformPlugin { .setCharacteristic(hap.Characteristic.CurrentDoorState, hap.Characteristic.CurrentDoorState.OPENING); } else { // HomeKit has told us something that we don't know how to handle. - this.log("Unknown SET event received: %s", value); - callback(new Error("Unknown SET event received: " + value)); + this.log('Unknown SET event received: %s', value); + callback(new Error('Unknown SET event received: ' + value)); } }); @@ -192,14 +199,14 @@ class myQPlatform implements DynamicPlatformPlugin { .getService(hap.Service.GarageDoorOpener)! .getCharacteristic(hap.Characteristic.CurrentDoorState)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - var err = null; + const err = null; // If the accessory is reachable, report back with status. Otherwise, appear as // unreachable. if(accessory.reachable) { - callback(err, this.doorStatus(accessory)) + callback(err, this.doorStatus(accessory)); } else { - callback(new Error("NO RESPONSE")); + callback(new Error('NO RESPONSE')); } }); @@ -208,25 +215,25 @@ class myQPlatform implements DynamicPlatformPlugin { .getService(hap.Service.GarageDoorOpener)! .getCharacteristic(hap.Characteristic.ObstructionDetected)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - var err = null; + const err = null; // If the accessory is reachable, report back with status. Otherwise, appear as // unreachable. if(accessory.reachable) { - var doorState = this.doorStatus(accessory); + const doorState = this.doorStatus(accessory); - if(doorState == this.myQOBSTRUCTED) { - this.log("%s has detected an obstruction.", accessory.displayName); + if(doorState === this.myQOBSTRUCTED) { + this.log('%s has detected an obstruction.', accessory.displayName); } - callback(err, doorState == this.myQOBSTRUCTED); + callback(err, doorState === this.myQOBSTRUCTED); } else { - callback(new Error("NO RESPONSE")); + callback(new Error('NO RESPONSE')); } }); - // Add this to the accessory array so we can track it. - this.accessories.push(accessory); + // Add this to the accessory array so we can track it. + this.accessories.push(accessory); } // Sync our devies between HomeKit and what the myQ API is showing us. @@ -235,9 +242,9 @@ class myQPlatform implements DynamicPlatformPlugin { // First we check if all the existing accessories we've cached still exist on the myQ API. // Login to myQ and refresh the full device list from the myQ API. if(!await this.myQ.refreshDevices()) { - this.log("Unable to login to the myQ API. Will continue to retry at regular polling intervals."); + this.log('Unable to login to the myQ API. Will continue to retry at regular polling intervals.'); return 0; - }; + } // Iterate through the list of devices that myQ has returned and sync them with what we show HomeKit. this.myQ.Devices.forEach((device:myQDevice) => { @@ -247,28 +254,28 @@ class myQPlatform implements DynamicPlatformPlugin { } // We are only interested in garage door openers. Perhaps more types in the future. - if(!device.device_type || (device.device_type.indexOf('garagedooropener') == -1)) { + if(!device.device_type || (device.device_type.indexOf('garagedooropener') === -1)) { return; } - + // Exclude or include certain openers based on configuration parameters. if(!this.myQDeviceVisible(device)) { return; } const uuid = hap.uuid.generate(device.serial_number); - var accessory; - var isNew = 0; + let accessory; + let isNew = 0; // See if we already know about this accessory or if it's truly new. - if((accessory = this.accessories.find((x: PlatformAccessory) => x.UUID === uuid)) == undefined) { + if((accessory = this.accessories.find((x: PlatformAccessory) => x.UUID === uuid)) === undefined) { isNew = 1; - accessory = new Accessory("myQ " + device.name, uuid); + accessory = new Accessory('myQ ' + device.name, uuid); } // Fun fact: This firmware information is stored on the gateway not the opener. - var gwParent = this.myQ.Devices.find((x: myQDevice) => x.serial_number === device.parent_device_id); - var fwVersion = "0.0"; + const gwParent: any = this.myQ.Devices.find((x: myQDevice) => x.serial_number === device.parent_device_id); + let fwVersion = '0.0'; if(gwParent && gwParent.state && gwParent.state.firmware_version) { fwVersion = gwParent.state.firmware_version; @@ -277,8 +284,8 @@ class myQPlatform implements DynamicPlatformPlugin { // Now let's set (or update) the information on this accessory. accessory.getService(hap.Service.AccessoryInformation)! .setCharacteristic(hap.Characteristic.FirmwareRevision, fwVersion) - .setCharacteristic(hap.Characteristic.Manufacturer, "Liftmaster") - .setCharacteristic(hap.Characteristic.Model, "myQ") + .setCharacteristic(hap.Characteristic.Manufacturer, 'Liftmaster') + .setCharacteristic(hap.Characteristic.Model, 'myQ') .setCharacteristic(hap.Characteristic.SerialNumber, device.serial_number); // Only add this device if we previously haven't added it to HomeKit. @@ -290,7 +297,7 @@ class myQPlatform implements DynamicPlatformPlugin { this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); } else { // Refresh the accessory with these values. - this.api.updatePlatformAccessories([accessory]) + this.api.updatePlatformAccessories([accessory]); } // Not strictly needed, but helpful for non-default HomeKit apps. @@ -299,17 +306,18 @@ class myQPlatform implements DynamicPlatformPlugin { // Remove myQ devices that are no longer found in the myQ API, but we still have in HomeKit. this.accessories.forEach((oldAccessory: PlatformAccessory) => { - var device = this.myQ.getDevice(hap, oldAccessory.UUID); + const device = this.myQ.getDevice(hap, oldAccessory.UUID); - // We found this accessory in myQ and we want to see it in HomeKit. + // We found this accessory in myQ and we want to see it in HomeKit. if(device && this.myQDeviceVisible(device)) { return; } - + // Remove the device and inform the user about it. this.log("Removing myQ %s device: %s (serial number: %s%s from HomeKit.", device.device_family, device.name, device.serial_number, device.parent_device_id ? ", gateway: " + device.parent_device_id + ")" : ")"); + this.log('Removing myQ device: %s', oldAccessory.displayName); this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [oldAccessory]); delete this.accessories[this.accessories.indexOf(oldAccessory)]; }); @@ -333,21 +341,20 @@ class myQPlatform implements DynamicPlatformPlugin { // Iterate through our accessories and update it's status with the corresponding myQ // status. this.accessories.forEach((accessory: PlatformAccessory) => { - var oldState = accessory.context.doorState; - var myQState = this.doorStatus(accessory); - var targetState; + const oldState = accessory.context.doorState; + const myQState = this.doorStatus(accessory); // If we can't get our status, we're probably not able to connect to the myQ API. - if(myQState == undefined) { - this.log("Unable to retrieve status for device: %s", accessory.displayName); + if(myQState === undefined) { + this.log('Unable to retrieve status for device: %s', accessory.displayName); return; } // Mark us as reachable. accessory.updateReachability(true); - if(oldState != myQState) { - this.log("%s is %s.", accessory.displayName, this.myQStateMap[myQState as number]); + if(oldState !== myQState) { + this.log('%s is %s.', accessory.displayName, this.myQStateMap[myQState as number]); } // Update the state in HomeKit. Thanks to @dxdc for suggesting looking at using updateValue @@ -356,7 +363,7 @@ class myQPlatform implements DynamicPlatformPlugin { accessory.getService(hap.Service.GarageDoorOpener) ?.getCharacteristic(hap.Characteristic.CurrentDoorState)?.updateValue(myQState); - targetState = this.doorTargetBias(myQState); + const targetState = this.doorTargetBias(myQState); accessory.getService(hap.Service.GarageDoorOpener) ?.getCharacteristic(hap.Characteristic.TargetDoorState)?.updateValue(targetState); @@ -368,7 +375,7 @@ class myQPlatform implements DynamicPlatformPlugin { // Periodically poll the myQ API for status. private myQPolling(delay: number) { - var refresh = this.configPoll.longPoll + delay; + let refresh = this.configPoll.longPoll + delay; // Clear the last polling interval out. clearTimeout(this.pollingTimer); @@ -385,12 +392,12 @@ class myQPlatform implements DynamicPlatformPlugin { } // Setup periodic update with our polling interval. - var self = this; + const self = this; - this.pollingTimer = setTimeout(async function() { + this.pollingTimer = setTimeout(async () => { // Refresh our myQ information and gracefully handle myQ errors. if(!self.updateAccessories()) { - self.log("Polling error: unable to connect to the myQ API."); + self.log('Polling error: unable to connect to the myQ API.'); self.configPoll.count = self.configPoll.maxCount - 1; } @@ -411,20 +418,20 @@ class myQPlatform implements DynamicPlatformPlugin { opening: hap.Characteristic.CurrentDoorState.OPENING, closing: hap.Characteristic.CurrentDoorState.CLOSING, stopped: hap.Characteristic.CurrentDoorState.STOPPED, - autoreverse: this.myQOBSTRUCTED + autoreverse: this.myQOBSTRUCTED, }; - var device = this.myQ.getDevice(hap, accessory.UUID); + const device = this.myQ.getDevice(hap, accessory.UUID); if(!device) { - this.log("Can't find device: %s - %s", accessory.displayName, accessory.UUID); + this.log('Can\'t find device: %s - %s', accessory.displayName, accessory.UUID); return 0; } - var myQState = doorStates[device.state.door_state]; + const myQState = doorStates[device.state.door_state]; - if(myQState == undefined) { - this.log("Unknown door state encountered on myQ device %s: %s", device.name, device.state.door_state); + if(myQState === undefined) { + this.log('Unknown door state encountered on myQ device %s: %s', device.name, device.state.door_state); return 0; } @@ -440,18 +447,18 @@ class myQPlatform implements DynamicPlatformPlugin { // myQ commands and the associated polling intervals to go with them. const myQCommandPolling: {[index: string]: number} = { open: this.configPoll.openDuration, - close: this.configPoll.closeDuration + close: this.configPoll.closeDuration, }; - var device = this.myQ.getDevice(hap, accessory.UUID); + const device = this.myQ.getDevice(hap, accessory.UUID); if(!device) { - this.log("Can't find device: %s - %s", accessory.displayName, accessory.UUID); + this.log('Can\'t find device: %s - %s', accessory.displayName, accessory.UUID); return; } - if(myQCommandPolling[command] == undefined) { - this.log("Unknown door commmand encountered on myQ device %s: %s", device.name, command); + if(myQCommandPolling[command] === undefined) { + this.log('Unknown door commmand encountered on myQ device %s: %s', device.name, command); return; } @@ -503,7 +510,7 @@ class myQPlatform implements DynamicPlatformPlugin { return hap.Characteristic.TargetDoorState.CLOSED; } } - + // Utility function to let us know if a my! device should be visible in HomeKit or not. private myQDeviceVisible(device: myQDevice): boolean { // There are a couple of ways to hide and show devices that we support. The rules of the road are: @@ -516,17 +523,17 @@ class myQPlatform implements DynamicPlatformPlugin { // This means that it's possible to hide a gateway, and all the openers that are attached to it, and then // override that behavior on a single opener device that it's connected to. // - + // Nothing configured - we show all myQ devices to HomeKit. if(!this.configOptions) { return true; } - + // No device. Sure, we'll show it. if(!device) { return true; } - + // We've explicitly enabled this opener. if(this.configOptions.indexOf("Show." + (device.serial_number as any)) != -1) { return true; @@ -536,7 +543,7 @@ class myQPlatform implements DynamicPlatformPlugin { if(this.configOptions.indexOf("Hide." + device.serial_number) != -1) { return false; } - + // If we don't have a gateway device attached to this opener, we're done here. if(!device.parent_device_id) { return true; @@ -551,7 +558,7 @@ class myQPlatform implements DynamicPlatformPlugin { if(this.configOptions.indexOf("Hide." + device.parent_device_id) != -1) { return false; } - + // Nothing special to do - make this opener visible. return true; } diff --git a/src/myq.ts b/src/myq.ts index c50848b..c40fa16 100644 --- a/src/myq.ts +++ b/src/myq.ts @@ -1,22 +1,14 @@ /* Copyright(C) 2020, HJD (https://github.com/hjdhjd). All rights reserved. */ import { - API, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback, - CharacteristicValue, HAP, - IndependentPlatformPlugin, Logging, - PlatformAccessory, - PlatformConfig -} from "homebridge"; +} from 'homebridge'; -import fetch, { Response } from 'node-fetch'; +import fetch from 'node-fetch'; import util from 'util'; -// An incomplete description of the myQ JSON, but enough for our purposes. +// An incomplete description of the myQ JSON, but enough for our purposes. export interface myQDevice { readonly device_family: string, readonly device_platform: string, @@ -31,7 +23,7 @@ export interface myQDevice { } } -var debug = 0; +let debug = 0; /* * myQ API version information. This is more intricate than it seems because the myQ @@ -92,13 +84,13 @@ export class myQ { // Headers that the myQ API expects. private myqHeaders = { - "Content-Type": "application/json", - "User-Agent": myqAgent, - "ApiVersion": myqVersion, - "BrandId": "2", - "Culture": "en", - "MyQApplicationId": myqAppId, - "SecurityToken": "" + 'Content-Type': 'application/json', + 'User-Agent': myqAgent, + 'ApiVersion': myqVersion, + 'BrandId': '2', + 'Culture': 'en', + 'MyQApplicationId': myqAppId, + 'SecurityToken': '', }; // Initialize this instance with our login information. @@ -106,30 +98,29 @@ export class myQ { this.log = log; this.Email = email; this.Password = password; - this.securityToken = ""; - this.accountID = ""; + this.securityToken = ''; + this.accountID = ''; } // Log us into myQ and get a security token. private async myqAuthenticate() { - var response, data; // Login to the myQ API and get a security token for our session. - response = await this.myqFetch(myqApi + '/Login', - { - method: "POST", - headers: this.myqHeaders, - body: JSON.stringify({UserName: this.Email, Password: this.Password}) - } - ); + const response = await this.myqFetch(myqApi + '/Login', + { + method: 'POST', + headers: this.myqHeaders, + body: JSON.stringify({UserName: this.Email, Password: this.Password}), + }, + ); if(!response) { - this.log("myQ API error: unable to authenticate. Will retry later."); + this.log('myQ API error: unable to authenticate. Will retry later.'); return 0; } // Now let's get our security token. - data = await response.json(); + const data = await response.json(); if(debug) { this.log(data); @@ -138,50 +129,49 @@ export class myQ { // What we should get back upon successfully calling /Login is a security token for // use in future API calls this session. if(!data || !data.SecurityToken) { - this.log("Unable to get a security token from the myQ API."); + this.log('Unable to get a security token from the myQ API.'); return 0; } this.securityToken = data.SecurityToken; - this.log("Successfully connected to the myQ API."); + this.log('Successfully connected to the myQ API.'); if(debug) { - this.log("Token: %s", this.securityToken); + this.log('Token: %s', this.securityToken); } // Add the token to our headers that we will use for subsequent API calls. - this.myqHeaders["SecurityToken"] = this.securityToken; + this.myqHeaders.SecurityToken = this.securityToken; return 1; } // Login and get our account information. async login() { - var response, data, params; // If we don't have a security token yet, acquire one before proceeding. if(!this.securityToken && !await this.myqAuthenticate()) { - return 0; + return 0; } // Get the account information. - params = new URLSearchParams({ expand: 'account' }); + const params = new URLSearchParams({ expand: 'account' }); - response = await this.myqFetch(myqApi + '/My?' + params, - { - method: 'GET', - headers: this.myqHeaders - } - ); + const response = await this.myqFetch(myqApi + '/My?' + params, + { + method: 'GET', + headers: this.myqHeaders, + }, + ); if(!response) { - this.log("myQ API error: unable to login. Will retry later."); + this.log('myQ API error: unable to login. Will retry later.'); return 0; } // Now let's get our account information. - data = await response.json(); + const data = await response.json(); if(debug) { this.log(data); @@ -189,7 +179,7 @@ export class myQ { // No account information returned. if(!data || !data.Account) { - this.log("Unable to retrieve account information from myQ servers.") + this.log('Unable to retrieve account information from myQ servers.'); return 0; } @@ -197,7 +187,7 @@ export class myQ { this.accountID = data.Account.Id; if(debug) { - this.log("myQ accountID: " + this.accountID); + this.log('myQ accountID: ' + this.accountID); } return 1; @@ -205,9 +195,7 @@ export class myQ { // Get the list of myQ devices associated with an account. async refreshDevices() { - var items: Array; - var response, data, params; - var now = Date.now(); + const now = Date.now(); // We want to throttle how often we call this API as a failsafe. If we call it more // than once every five seconds or so, bad things can happen on the myQ side leading @@ -215,11 +203,11 @@ export class myQ { // hard way. if(this.lastCall && (now - this.lastCall) < (5 * 1000)) { if(debug) { - this.log("Throttling myQ API call."); + this.log('Throttling myQ API call.'); } if(!this.Devices) { - return 0 + return 0; } return 1; @@ -230,41 +218,39 @@ export class myQ { // If we don't have our account information yet, acquire it before proceeding. if(!this.accountID && !await this.login()) { - return 0; + return 0; } // Get the list of device information. - params = new URLSearchParams({ filterOn: 'true' }); + const params = new URLSearchParams({ filterOn: 'true' }); - response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices?' + params, - { - method: 'GET', - headers: this.myqHeaders - } - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices?' + params, + { + method: 'GET', + headers: this.myqHeaders, + }, + ); if(!response) { - this.log("myQ API error: unable to refresh. Will retry later."); + this.log('myQ API error: unable to refresh. Will retry later.'); return 0; } // Now let's get our account information. - data = await response.json(); + const data = await response.json(); if(debug) { this.log(data); } - items = data.items; + const items: Array = data.items; // Notify the user about any new devices that we've discovered. if(items) { items.forEach((newDevice: any) => { - var existingDevice; - if(this.Devices) { // We already know about this device. - if((existingDevice = this.Devices.find((x: any) => x.serial_number === newDevice.serial_number)) != undefined) { + if((this.Devices.find((x: any) => x.serial_number === newDevice.serial_number)) !== undefined) { return; } } @@ -282,17 +268,15 @@ export class myQ { // Notify the user about any devices that have disappeared. if(this.Devices) { this.Devices.forEach((existingDevice: any) => { - var newDevice; - if(items) { // This device still is visible. - if((newDevice = items.find((x: any) => x.serial_number === existingDevice.serial_number)) != undefined) { + if((items.find((x: any) => x.serial_number === existingDevice.serial_number)) !== undefined) { return; } } // We've had a device disappear. - this.log("myQ %s device removed: %s - %s.", existingDevice.device_family, existingDevice.name, existingDevice.serial_number); + this.log('myQ %s device removed: %s - %s.', existingDevice.device_family, existingDevice.name, existingDevice.serial_number); if(debug) { this.log(util.inspect(existingDevice, { colors: true, sorted: true, depth: 3 })); @@ -308,33 +292,32 @@ export class myQ { // Query the details of a specific myQ device. async queryDevice(log: Logging, deviceId: string) { - var response, data; // If we don't have our account information yet, acquire it before proceeding. if(!this.accountID && !await this.login()) { - return 0; + return 0; } debug = 1; // Get the list of device information. - response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/devices/' + deviceId, - { - method: 'GET', - headers: this.myqHeaders - } - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/devices/' + deviceId, + { + method: 'GET', + headers: this.myqHeaders, + }, + ); if(!response) { - this.log("myQ API error: unable to query device. Will retry later."); + this.log('myQ API error: unable to query device. Will retry later.'); return 0; } // Now let's get our account information. - data = await response.json(); + const data = await response.json(); if(!data || !data.items) { - log("Error querying device '%s'", deviceId); + log('Error querying device \'%s\'', deviceId); return 0; } @@ -345,7 +328,7 @@ export class myQ { this.Devices = data.items; this.Devices.forEach((device: any) => { - this.log("Device:"); + this.log('Device:'); this.log(util.inspect(device, { colors: true, sorted: true, depth: 2 })); }); @@ -356,24 +339,22 @@ export class myQ { // Execute an action on a myQ device. async execute(deviceId: string, command: string) { - var response; // If we don't have our account information yet, acquire it before proceeding. if(!this.accountID && !await this.login()) { - return 0; + return 0; } - response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices/' + - deviceId + '/actions', - { - method: 'PUT', - headers: this.myqHeaders, - body: JSON.stringify({action_type: command}) - } - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices/' + deviceId + '/actions', + { + method: 'PUT', + headers: this.myqHeaders, + body: JSON.stringify({action_type: command}), + }, + ); if(!response) { - this.log("myQ API error: unable to execute command."); + this.log('myQ API error: unable to execute command.'); return 0; } @@ -382,8 +363,8 @@ export class myQ { // Get the details of a specific device in our list. getDevice(hap: HAP, uuid: string) { - var device : any; - var now = Date.now(); + let device : any; + const now = Date.now(); // Check to make sure we have fresh information from myQ. If it's less than a minute // old, it looks good to us. @@ -395,8 +376,8 @@ export class myQ { // This works because homebridge always generates the same UUID for a given input - // in this case the device serial number. if((device = this.Devices.find((x: any) => - x.device_type && (x.device_type.indexOf('garagedooropener') != -1) && - x.serial_number && hap.uuid.generate(x.serial_number) === uuid)) != undefined) { + x.device_type && (x.device_type.indexOf('garagedooropener') !== -1) && + x.serial_number && hap.uuid.generate(x.serial_number) === uuid)) !== undefined) { return device; } @@ -405,26 +386,26 @@ export class myQ { // Utility to let us streamline error handling and return checking from the myQ API. private async myqFetch (url: string, options: any) { - var response; + let response; try { response = await fetch(url, options); // Bad username and password. - if(response.status == 401) { - this.log("Invalid username or password given. Check your login and password."); + if(response.status === 401) { + this.log('Invalid username or password given. Check your login and password.'); return undefined; } // Some other unknown error occurred. if(!response.ok) { - this.log("myQ API error: %s %s", response.status, response.statusText); + this.log('myQ API error: %s %s', response.status, response.statusText); return undefined; } return response; } catch(error) { - this.log.error("Fetch error encountered: " + error); + this.log.error('Fetch error encountered: ' + error); return undefined; } } From 2f5266e8c35fc9b738ed644d74e3673e3b19c81c Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:51:29 -0500 Subject: [PATCH 05/14] Update dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c26cea0..b3be541 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,13 @@ }, "homepage": "https://github.com/hjdhjd/homebridge-myq2#readme", "devDependencies": { - "@types/node": "10.17.19", - "@typescript-eslint/eslint-plugin": "^3.1.0", - "@typescript-eslint/parser": "^3.1.0", + "@types/node": "14.0.14", + "@typescript-eslint/eslint-plugin": "^3.5.0", + "@typescript-eslint/parser": "^3.5.0", "eslint": "^7.4.0", - "homebridge": "^1.0.4", + "homebridge": "^1.1.1", "rimraf": "^3.0.2", - "typescript": "^3.8.3" + "typescript": "^3.9.6" }, "dependencies": { "@types/node-fetch": "^2.5.7", From d56bb144c2c7ef15da8bd24f3ea68295cdfcb2ab Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:52:11 -0500 Subject: [PATCH 06/14] Additional linting --- src/index.ts | 116 +++++++++++++++++++++++++-------------------------- src/myq.ts | 105 +++++++++++++++++++++------------------------- 2 files changed, 105 insertions(+), 116 deletions(-) diff --git a/src/index.ts b/src/index.ts index cb98bd4..4475998 100644 --- a/src/index.ts +++ b/src/index.ts @@ -154,15 +154,20 @@ class myQPlatform implements DynamicPlatformPlugin { // If we are already opening or closing the garage door, we error out. myQ doesn't appear to allow // interruptions to an open or close command that is currently executing - it must be allowed to // complete it's action before accepting a new one. - if((myQState === hap.Characteristic.CurrentDoorState.OPENING) || (myQState === hap.Characteristic.CurrentDoorState.CLOSING)) { + if(myQState === hap.Characteristic.CurrentDoorState.OPENING || myQState === hap.Characteristic.CurrentDoorState.CLOSING) { const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? 'opening' : 'closing'; const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? 'close' : 'open'; - this.log('%s - unable to %s door while currently trying to finish %s. myQ must complete it\'s existing action ' + - 'before attmepting a new one.', accessory.displayName, actionAttempt, actionExisting); + this.log( + "%s - unable to %s door while currently trying to finish %s. myQ must complete it's existing action " + + 'before attmepting a new one.', + accessory.displayName, + actionAttempt, + actionExisting, + ); callback(new Error('Unable to accept a new set event while another is completing.')); - } else if (value === hap.Characteristic.TargetDoorState.CLOSED) { + } else if(value === hap.Characteristic.TargetDoorState.CLOSED) { // HomeKit is informing us to close the door. this.log('%s is closing.', accessory.displayName); this.doorCommand(accessory, 'close'); @@ -176,7 +181,7 @@ class myQPlatform implements DynamicPlatformPlugin { accessory .getService(hap.Service.GarageDoorOpener)! .setCharacteristic(hap.Characteristic.CurrentDoorState, hap.Characteristic.CurrentDoorState.CLOSING); - } else if (value === hap.Characteristic.TargetDoorState.OPEN) { + } else if(value === hap.Characteristic.TargetDoorState.OPEN) { // HomeKit is informing us to open the door. this.log('%s is opening.', accessory.displayName); this.doorCommand(accessory, 'open'); @@ -194,67 +199,66 @@ class myQPlatform implements DynamicPlatformPlugin { } }); - // Add all the events to our accessory so we can tell HomeKit our state. - accessory - .getService(hap.Service.GarageDoorOpener)! - .getCharacteristic(hap.Characteristic.CurrentDoorState)! - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - const err = null; - - // If the accessory is reachable, report back with status. Otherwise, appear as - // unreachable. - if(accessory.reachable) { - callback(err, this.doorStatus(accessory)); - } else { - callback(new Error('NO RESPONSE')); - } - }); + // Add all the events to our accessory so we can tell HomeKit our state. + accessory + .getService(hap.Service.GarageDoorOpener)! + .getCharacteristic(hap.Characteristic.CurrentDoorState)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { + const err = null; + + // If the accessory is reachable, report back with status. Otherwise, appear as + // unreachable. + if(accessory.reachable) { + callback(err, this.doorStatus(accessory)); + } else { + callback(new Error('NO RESPONSE')); + } + }); - // Make sure we can detect obstructions. - accessory - .getService(hap.Service.GarageDoorOpener)! - .getCharacteristic(hap.Characteristic.ObstructionDetected)! - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - const err = null; + // Make sure we can detect obstructions. + accessory + .getService(hap.Service.GarageDoorOpener)! + .getCharacteristic(hap.Characteristic.ObstructionDetected)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { + const err = null; - // If the accessory is reachable, report back with status. Otherwise, appear as - // unreachable. - if(accessory.reachable) { - const doorState = this.doorStatus(accessory); + // If the accessory is reachable, report back with status. Otherwise, appear as + // unreachable. + if(accessory.reachable) { + const doorState = this.doorStatus(accessory); - if(doorState === this.myQOBSTRUCTED) { - this.log('%s has detected an obstruction.', accessory.displayName); - } + if(doorState === this.myQOBSTRUCTED) { + this.log('%s has detected an obstruction.', accessory.displayName); + } - callback(err, doorState === this.myQOBSTRUCTED); - } else { - callback(new Error('NO RESPONSE')); - } - }); + callback(err, doorState === this.myQOBSTRUCTED); + } else { + callback(new Error('NO RESPONSE')); + } + }); - // Add this to the accessory array so we can track it. - this.accessories.push(accessory); + // Add this to the accessory array so we can track it. + this.accessories.push(accessory); } // Sync our devies between HomeKit and what the myQ API is showing us. async myQUpdateDeviceList() { - // First we check if all the existing accessories we've cached still exist on the myQ API. // Login to myQ and refresh the full device list from the myQ API. - if(!await this.myQ.refreshDevices()) { + if(!(await this.myQ.refreshDevices())) { this.log('Unable to login to the myQ API. Will continue to retry at regular polling intervals.'); return 0; } // Iterate through the list of devices that myQ has returned and sync them with what we show HomeKit. - this.myQ.Devices.forEach((device:myQDevice) => { + this.myQ.Devices.forEach((device: myQDevice) => { // If we have no serial number, something is wrong. if(!device.serial_number) { return; } // We are only interested in garage door openers. Perhaps more types in the future. - if(!device.device_type || (device.device_type.indexOf('garagedooropener') === -1)) { + if(!device.device_type || device.device_type.indexOf('garagedooropener') === -1) { return; } @@ -282,7 +286,8 @@ class myQPlatform implements DynamicPlatformPlugin { } // Now let's set (or update) the information on this accessory. - accessory.getService(hap.Service.AccessoryInformation)! + accessory + .getService(hap.Service.AccessoryInformation)! .setCharacteristic(hap.Characteristic.FirmwareRevision, fwVersion) .setCharacteristic(hap.Characteristic.Manufacturer, 'Liftmaster') .setCharacteristic(hap.Characteristic.Model, 'myQ') @@ -328,8 +333,7 @@ class myQPlatform implements DynamicPlatformPlugin { // Update HomeKit with the latest status from myQ. private async updateAccessories() { // Refresh our state from the myQ API. - if(!await this.myQ.refreshDevices()) { - + if(!(await this.myQ.refreshDevices())) { // We can't get a connection to the myQ API. Set all our accessories as unnreachable for now. this.accessories.forEach((accessory: PlatformAccessory) => { accessory.updateReachability(false); @@ -360,13 +364,11 @@ class myQPlatform implements DynamicPlatformPlugin { // Update the state in HomeKit. Thanks to @dxdc for suggesting looking at using updateValue // here instead of the more intuitive setCharacteristic due to inevitable race conditions and // set loops that can occur in HomeKit if you aren't careful. - accessory.getService(hap.Service.GarageDoorOpener) - ?.getCharacteristic(hap.Characteristic.CurrentDoorState)?.updateValue(myQState); + accessory.getService(hap.Service.GarageDoorOpener)?.getCharacteristic(hap.Characteristic.CurrentDoorState)?.updateValue(myQState); const targetState = this.doorTargetBias(myQState); - accessory.getService(hap.Service.GarageDoorOpener) - ?.getCharacteristic(hap.Characteristic.TargetDoorState)?.updateValue(targetState); + accessory.getService(hap.Service.GarageDoorOpener)?.getCharacteristic(hap.Characteristic.TargetDoorState)?.updateValue(targetState); }); // Check for any new or removed accessories from myQ. @@ -386,7 +388,7 @@ class myQPlatform implements DynamicPlatformPlugin { // shortPollDuration and shortPoll which specify the maximum length of time for this // increased polling frequency (shortPollDuration) and the actual frequency of each // update (shortPoll). - if(this.configPoll.count < this.configPoll.maxCount) { + if(this.configPoll.count < this.configPoll.maxCount) { this.configPoll.count++; refresh = this.configPoll.shortPoll + delay; } @@ -408,7 +410,6 @@ class myQPlatform implements DynamicPlatformPlugin { // Return the status of the door for an accessory. It maps myQ door status to HomeKit door status. private doorStatus(accessory: PlatformAccessory): CharacteristicValue { - // Door state cheat sheet. // autoreverse is how the myQ API communicated an obstruction...go figure. Unfortunately, it // only seems to last the duration of the door reopening (reversal). @@ -424,7 +425,7 @@ class myQPlatform implements DynamicPlatformPlugin { const device = this.myQ.getDevice(hap, accessory.UUID); if(!device) { - this.log('Can\'t find device: %s - %s', accessory.displayName, accessory.UUID); + this.log("Can't find device: %s - %s", accessory.displayName, accessory.UUID); return 0; } @@ -443,17 +444,16 @@ class myQPlatform implements DynamicPlatformPlugin { // Open or close the door for an accessory. private doorCommand(accessory: PlatformAccessory, command: string) { - // myQ commands and the associated polling intervals to go with them. const myQCommandPolling: {[index: string]: number} = { - open: this.configPoll.openDuration, - close: this.configPoll.closeDuration, + open: this.configPoll.openDuration, + close: this.configPoll.closeDuration, }; const device = this.myQ.getDevice(hap, accessory.UUID); if(!device) { - this.log('Can\'t find device: %s - %s', accessory.displayName, accessory.UUID); + this.log("Can't find device: %s - %s", accessory.displayName, accessory.UUID); return; } diff --git a/src/myq.ts b/src/myq.ts index c40fa16..7d2b04f 100644 --- a/src/myq.ts +++ b/src/myq.ts @@ -1,9 +1,6 @@ /* Copyright(C) 2020, HJD (https://github.com/hjdhjd). All rights reserved. */ -import { - HAP, - Logging, -} from 'homebridge'; +import { HAP, Logging } from 'homebridge'; import fetch from 'node-fetch'; import util from 'util'; @@ -86,11 +83,11 @@ export class myQ { private myqHeaders = { 'Content-Type': 'application/json', 'User-Agent': myqAgent, - 'ApiVersion': myqVersion, - 'BrandId': '2', - 'Culture': 'en', - 'MyQApplicationId': myqAppId, - 'SecurityToken': '', + ApiVersion: myqVersion, + BrandId: '2', + Culture: 'en', + MyQApplicationId: myqAppId, + SecurityToken: '', }; // Initialize this instance with our login information. @@ -104,15 +101,12 @@ export class myQ { // Log us into myQ and get a security token. private async myqAuthenticate() { - // Login to the myQ API and get a security token for our session. - const response = await this.myqFetch(myqApi + '/Login', - { - method: 'POST', - headers: this.myqHeaders, - body: JSON.stringify({UserName: this.Email, Password: this.Password}), - }, - ); + const response = await this.myqFetch(myqApi + '/Login', { + method: 'POST', + headers: this.myqHeaders, + body: JSON.stringify({ UserName: this.Email, Password: this.Password }), + }); if(!response) { this.log('myQ API error: unable to authenticate. Will retry later.'); @@ -149,21 +143,18 @@ export class myQ { // Login and get our account information. async login() { - // If we don't have a security token yet, acquire one before proceeding. - if(!this.securityToken && !await this.myqAuthenticate()) { + if(!this.securityToken && !(await this.myqAuthenticate())) { return 0; } // Get the account information. const params = new URLSearchParams({ expand: 'account' }); - const response = await this.myqFetch(myqApi + '/My?' + params, - { - method: 'GET', - headers: this.myqHeaders, - }, - ); + const response = await this.myqFetch(myqApi + '/My?' + params, { + method: 'GET', + headers: this.myqHeaders, + }); if(!response) { this.log('myQ API error: unable to login. Will retry later.'); @@ -201,7 +192,7 @@ export class myQ { // than once every five seconds or so, bad things can happen on the myQ side leading // to potential accounnt lockouts. The author was definitely learned this one the // hard way. - if(this.lastCall && (now - this.lastCall) < (5 * 1000)) { + if(this.lastCall && ((now - this.lastCall) < (5*1000))) { if(debug) { this.log('Throttling myQ API call.'); } @@ -217,19 +208,17 @@ export class myQ { this.lastCall = now; // If we don't have our account information yet, acquire it before proceeding. - if(!this.accountID && !await this.login()) { + if(!this.accountID && !(await this.login())) { return 0; } // Get the list of device information. const params = new URLSearchParams({ filterOn: 'true' }); - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices?' + params, - { - method: 'GET', - headers: this.myqHeaders, - }, - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices?' + params, { + method: 'GET', + headers: this.myqHeaders, + }); if(!response) { this.log('myQ API error: unable to refresh. Will retry later.'); @@ -250,7 +239,7 @@ export class myQ { items.forEach((newDevice: any) => { if(this.Devices) { // We already know about this device. - if((this.Devices.find((x: any) => x.serial_number === newDevice.serial_number)) !== undefined) { + if(this.Devices.find((x: any) => x.serial_number === newDevice.serial_number) !== undefined) { return; } } @@ -270,7 +259,7 @@ export class myQ { this.Devices.forEach((existingDevice: any) => { if(items) { // This device still is visible. - if((items.find((x: any) => x.serial_number === existingDevice.serial_number)) !== undefined) { + if(items.find((x: any) => x.serial_number === existingDevice.serial_number) !== undefined) { return; } } @@ -292,21 +281,18 @@ export class myQ { // Query the details of a specific myQ device. async queryDevice(log: Logging, deviceId: string) { - // If we don't have our account information yet, acquire it before proceeding. - if(!this.accountID && !await this.login()) { + if(!this.accountID && !(await this.login())) { return 0; } debug = 1; // Get the list of device information. - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/devices/' + deviceId, - { - method: 'GET', - headers: this.myqHeaders, - }, - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/devices/' + deviceId, { + method: 'GET', + headers: this.myqHeaders, + }); if(!response) { this.log('myQ API error: unable to query device. Will retry later.'); @@ -317,7 +303,7 @@ export class myQ { const data = await response.json(); if(!data || !data.items) { - log('Error querying device \'%s\'', deviceId); + log("Error querying device '%s'", deviceId); return 0; } @@ -339,19 +325,16 @@ export class myQ { // Execute an action on a myQ device. async execute(deviceId: string, command: string) { - // If we don't have our account information yet, acquire it before proceeding. - if(!this.accountID && !await this.login()) { + if(!this.accountID && !(await this.login())) { return 0; } - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices/' + deviceId + '/actions', - { - method: 'PUT', - headers: this.myqHeaders, - body: JSON.stringify({action_type: command}), - }, - ); + const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices/' + deviceId + '/actions', { + method: 'PUT', + headers: this.myqHeaders, + body: JSON.stringify({ action_type: command }), + }); if(!response) { this.log('myQ API error: unable to execute command.'); @@ -363,7 +346,7 @@ export class myQ { // Get the details of a specific device in our list. getDevice(hap: HAP, uuid: string) { - let device : any; + let device: any; const now = Date.now(); // Check to make sure we have fresh information from myQ. If it's less than a minute @@ -375,9 +358,15 @@ export class myQ { // Iterate through the list and find the device that matches the UUID we seek. // This works because homebridge always generates the same UUID for a given input - // in this case the device serial number. - if((device = this.Devices.find((x: any) => - x.device_type && (x.device_type.indexOf('garagedooropener') !== -1) && - x.serial_number && hap.uuid.generate(x.serial_number) === uuid)) !== undefined) { + if( + (device = this.Devices.find( + (x: any) => + x.device_type && + x.device_type.indexOf('garagedooropener') !== -1 && + x.serial_number && + hap.uuid.generate(x.serial_number) === uuid, + )) !== undefined + ) { return device; } @@ -385,7 +374,7 @@ export class myQ { } // Utility to let us streamline error handling and return checking from the myQ API. - private async myqFetch (url: string, options: any) { + private async myqFetch(url: string, options: any) { let response; try { From 1d75e0d90b56849e0954200a5f8f90674b5376d7 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 11:55:12 -0500 Subject: [PATCH 07/14] Update eslint rules --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1a0e2aa..5fa45ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,7 @@ }, "ignorePatterns": ["dist"], "rules": { - "quotes": ["warn", "single"], + "quotes": ["warn", "single", {"avoidEscape": true}], "indent": [ "warn", 2, From f5d723f49f176d8938bc6bbe0bdfe6a535cb20c0 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 12:12:54 -0500 Subject: [PATCH 08/14] Fix typo in comments --- src/myq.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/myq.ts b/src/myq.ts index 7d2b04f..0ee3cd5 100644 --- a/src/myq.ts +++ b/src/myq.ts @@ -190,8 +190,7 @@ export class myQ { // We want to throttle how often we call this API as a failsafe. If we call it more // than once every five seconds or so, bad things can happen on the myQ side leading - // to potential accounnt lockouts. The author was definitely learned this one the - // hard way. + // to potential account lockouts. The author definitely learned this one the hard way. if(this.lastCall && ((now - this.lastCall) < (5*1000))) { if(debug) { this.log('Throttling myQ API call.'); From 196c0090f19660b83f10b0c194aeb4f683f41b6a Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 12:14:55 -0500 Subject: [PATCH 09/14] Simplify logic --- src/myq.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/myq.ts b/src/myq.ts index 0ee3cd5..fcf212f 100644 --- a/src/myq.ts +++ b/src/myq.ts @@ -196,11 +196,7 @@ export class myQ { this.log('Throttling myQ API call.'); } - if(!this.Devices) { - return 0; - } - - return 1; + return this.Devices ? 1 : 0; } // Reset the API call time. From ea3724be1a5365f3739ae2d449cdd10e4e7b76c8 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 16:44:04 -0500 Subject: [PATCH 10/14] Use double-quotes, additional linting --- .eslintrc.json | 2 +- src/index.ts | 168 ++++++++++++++++++++++++------------------------- src/myq.ts | 90 +++++++++++++------------- 3 files changed, 130 insertions(+), 130 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5fa45ad..e284467 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,7 @@ }, "ignorePatterns": ["dist"], "rules": { - "quotes": ["warn", "single", {"avoidEscape": true}], + "quotes": ["warn", "double", {"avoidEscape": true}], "indent": [ "warn", 2, diff --git a/src/index.ts b/src/index.ts index 4475998..4c0120c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,12 +13,12 @@ import { PlatformAccessory, PlatformAccessoryEvent, PlatformConfig, -} from 'homebridge'; +} from "homebridge"; -import { myQ, myQDevice } from './myq'; +import { myQ, myQDevice } from "./myq"; -const PLUGIN_NAME = 'homebridge-myq2'; -const PLATFORM_NAME = 'myQ'; +const PLUGIN_NAME = "homebridge-myq2"; +const PLATFORM_NAME = "myQ"; let hap: HAP; let Accessory: typeof PlatformAccessory; @@ -56,12 +56,12 @@ class myQPlatform implements DynamicPlatformPlugin { private pollingTimer!: NodeJS.Timeout; private myQStateMap: {[index: number]: string} = { - [hap.Characteristic.CurrentDoorState.OPEN]: 'open', - [hap.Characteristic.CurrentDoorState.CLOSED]: 'closed', - [hap.Characteristic.CurrentDoorState.OPENING]: 'opening', - [hap.Characteristic.CurrentDoorState.CLOSING]: 'closing', - [hap.Characteristic.CurrentDoorState.STOPPED]: 'stopped', - [this.myQOBSTRUCTED]: 'obstructed', + [hap.Characteristic.CurrentDoorState.OPEN]: "open", + [hap.Characteristic.CurrentDoorState.CLOSED]: "closed", + [hap.Characteristic.CurrentDoorState.OPENING]: "opening", + [hap.Characteristic.CurrentDoorState.CLOSING]: "closing", + [hap.Characteristic.CurrentDoorState.STOPPED]: "stopped", + [this.myQOBSTRUCTED]: "obstructed", }; private readonly accessories: PlatformAccessory[] = []; @@ -77,13 +77,13 @@ class myQPlatform implements DynamicPlatformPlugin { // We need login credentials or we're not starting. if(!config.email || !config.password) { - this.log('No myQ login credentials specified.'); + this.log("No myQ login credentials specified."); return; } // Capture configuration parameters. if(config.options) { - this.configOptions = config.options; + this.configOptions = config.options; } if(config.longPoll) { @@ -123,7 +123,7 @@ class myQPlatform implements DynamicPlatformPlugin { configureAccessory(accessory: PlatformAccessory): void { // Give this accessory an identity handler. accessory.on(PlatformAccessoryEvent.IDENTIFY, () => { - this.log('%s identified!', accessory.displayName); + this.log("%s identified!", accessory.displayName); }); const gdOpener = accessory.getService(hap.Service.GarageDoorOpener); @@ -155,22 +155,22 @@ class myQPlatform implements DynamicPlatformPlugin { // interruptions to an open or close command that is currently executing - it must be allowed to // complete it's action before accepting a new one. if(myQState === hap.Characteristic.CurrentDoorState.OPENING || myQState === hap.Characteristic.CurrentDoorState.CLOSING) { - const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? 'opening' : 'closing'; - const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? 'close' : 'open'; + const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? "opening" : "closing"; + const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? "close" : "open"; this.log( "%s - unable to %s door while currently trying to finish %s. myQ must complete it's existing action " + - 'before attmepting a new one.', + "before attmepting a new one.", accessory.displayName, actionAttempt, actionExisting, ); - callback(new Error('Unable to accept a new set event while another is completing.')); + callback(new Error("Unable to accept a new set event while another is completing.")); } else if(value === hap.Characteristic.TargetDoorState.CLOSED) { // HomeKit is informing us to close the door. - this.log('%s is closing.', accessory.displayName); - this.doorCommand(accessory, 'close'); + this.log("%s is closing.", accessory.displayName); + this.doorCommand(accessory, "close"); callback(); @@ -183,8 +183,8 @@ class myQPlatform implements DynamicPlatformPlugin { .setCharacteristic(hap.Characteristic.CurrentDoorState, hap.Characteristic.CurrentDoorState.CLOSING); } else if(value === hap.Characteristic.TargetDoorState.OPEN) { // HomeKit is informing us to open the door. - this.log('%s is opening.', accessory.displayName); - this.doorCommand(accessory, 'open'); + this.log("%s is opening.", accessory.displayName); + this.doorCommand(accessory, "open"); callback(); @@ -194,8 +194,8 @@ class myQPlatform implements DynamicPlatformPlugin { .setCharacteristic(hap.Characteristic.CurrentDoorState, hap.Characteristic.CurrentDoorState.OPENING); } else { // HomeKit has told us something that we don't know how to handle. - this.log('Unknown SET event received: %s', value); - callback(new Error('Unknown SET event received: ' + value)); + this.log("Unknown SET event received: %s", value); + callback(new Error("Unknown SET event received: " + value)); } }); @@ -211,7 +211,7 @@ class myQPlatform implements DynamicPlatformPlugin { if(accessory.reachable) { callback(err, this.doorStatus(accessory)); } else { - callback(new Error('NO RESPONSE')); + callback(new Error("NO RESPONSE")); } }); @@ -228,12 +228,12 @@ class myQPlatform implements DynamicPlatformPlugin { const doorState = this.doorStatus(accessory); if(doorState === this.myQOBSTRUCTED) { - this.log('%s has detected an obstruction.', accessory.displayName); + this.log("%s has detected an obstruction.", accessory.displayName); } callback(err, doorState === this.myQOBSTRUCTED); } else { - callback(new Error('NO RESPONSE')); + callback(new Error("NO RESPONSE")); } }); @@ -246,7 +246,7 @@ class myQPlatform implements DynamicPlatformPlugin { // First we check if all the existing accessories we've cached still exist on the myQ API. // Login to myQ and refresh the full device list from the myQ API. if(!(await this.myQ.refreshDevices())) { - this.log('Unable to login to the myQ API. Will continue to retry at regular polling intervals.'); + this.log("Unable to login to the myQ API. Will continue to retry at regular polling intervals."); return 0; } @@ -258,14 +258,14 @@ class myQPlatform implements DynamicPlatformPlugin { } // We are only interested in garage door openers. Perhaps more types in the future. - if(!device.device_type || device.device_type.indexOf('garagedooropener') === -1) { + if(!device.device_type || device.device_type.indexOf("garagedooropener") === -1) { return; } // Exclude or include certain openers based on configuration parameters. - if(!this.myQDeviceVisible(device)) { - return; - } + if(!this.myQDeviceVisible(device)) { + return; + } const uuid = hap.uuid.generate(device.serial_number); let accessory; @@ -274,12 +274,12 @@ class myQPlatform implements DynamicPlatformPlugin { // See if we already know about this accessory or if it's truly new. if((accessory = this.accessories.find((x: PlatformAccessory) => x.UUID === uuid)) === undefined) { isNew = 1; - accessory = new Accessory('myQ ' + device.name, uuid); + accessory = new Accessory("myQ " + device.name, uuid); } // Fun fact: This firmware information is stored on the gateway not the opener. const gwParent: any = this.myQ.Devices.find((x: myQDevice) => x.serial_number === device.parent_device_id); - let fwVersion = '0.0'; + let fwVersion = "0.0"; if(gwParent && gwParent.state && gwParent.state.firmware_version) { fwVersion = gwParent.state.firmware_version; @@ -289,14 +289,14 @@ class myQPlatform implements DynamicPlatformPlugin { accessory .getService(hap.Service.AccessoryInformation)! .setCharacteristic(hap.Characteristic.FirmwareRevision, fwVersion) - .setCharacteristic(hap.Characteristic.Manufacturer, 'Liftmaster') - .setCharacteristic(hap.Characteristic.Model, 'myQ') + .setCharacteristic(hap.Characteristic.Manufacturer, "Liftmaster") + .setCharacteristic(hap.Characteristic.Model, "myQ") .setCharacteristic(hap.Characteristic.SerialNumber, device.serial_number); // Only add this device if we previously haven't added it to HomeKit. if(isNew) { this.log("Adding myQ %s device: %s (serial number: %s%s to HomeKit.", device.device_family, device.name, device.serial_number, - device.parent_device_id ? ", gateway: " + device.parent_device_id + ")" : ")"); + device.parent_device_id ? ", gateway: " + device.parent_device_id + ")" : ")"); this.configureAccessory(accessory); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); @@ -318,11 +318,11 @@ class myQPlatform implements DynamicPlatformPlugin { return; } - // Remove the device and inform the user about it. - this.log("Removing myQ %s device: %s (serial number: %s%s from HomeKit.", device.device_family, device.name, device.serial_number, - device.parent_device_id ? ", gateway: " + device.parent_device_id + ")" : ")"); + // Remove the device and inform the user about it. + this.log("Removing myQ %s device: %s (serial number: %s%s from HomeKit.", device.device_family, device.name, device.serial_number, + device.parent_device_id ? ", gateway: " + device.parent_device_id + ")" : ")"); - this.log('Removing myQ device: %s', oldAccessory.displayName); + this.log("Removing myQ device: %s", oldAccessory.displayName); this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [oldAccessory]); delete this.accessories[this.accessories.indexOf(oldAccessory)]; }); @@ -350,7 +350,7 @@ class myQPlatform implements DynamicPlatformPlugin { // If we can't get our status, we're probably not able to connect to the myQ API. if(myQState === undefined) { - this.log('Unable to retrieve status for device: %s', accessory.displayName); + this.log("Unable to retrieve status for device: %s", accessory.displayName); return; } @@ -358,7 +358,7 @@ class myQPlatform implements DynamicPlatformPlugin { accessory.updateReachability(true); if(oldState !== myQState) { - this.log('%s is %s.', accessory.displayName, this.myQStateMap[myQState as number]); + this.log("%s is %s.", accessory.displayName, this.myQStateMap[myQState as number]); } // Update the state in HomeKit. Thanks to @dxdc for suggesting looking at using updateValue @@ -399,7 +399,7 @@ class myQPlatform implements DynamicPlatformPlugin { this.pollingTimer = setTimeout(async () => { // Refresh our myQ information and gracefully handle myQ errors. if(!self.updateAccessories()) { - self.log('Polling error: unable to connect to the myQ API.'); + self.log("Polling error: unable to connect to the myQ API."); self.configPoll.count = self.configPoll.maxCount - 1; } @@ -432,7 +432,7 @@ class myQPlatform implements DynamicPlatformPlugin { const myQState = doorStates[device.state.door_state]; if(myQState === undefined) { - this.log('Unknown door state encountered on myQ device %s: %s', device.name, device.state.door_state); + this.log("Unknown door state encountered on myQ device %s: %s", device.name, device.state.door_state); return 0; } @@ -458,7 +458,7 @@ class myQPlatform implements DynamicPlatformPlugin { } if(myQCommandPolling[command] === undefined) { - this.log('Unknown door commmand encountered on myQ device %s: %s', device.name, command); + this.log("Unknown door commmand encountered on myQ device %s: %s", device.name, command); return; } @@ -522,44 +522,44 @@ class myQPlatform implements DynamicPlatformPlugin { // 2. Explicitly hiding, or showing an opener device by it's serial number will always override the above. // This means that it's possible to hide a gateway, and all the openers that are attached to it, and then // override that behavior on a single opener device that it's connected to. - // - - // Nothing configured - we show all myQ devices to HomeKit. - if(!this.configOptions) { - return true; - } - - // No device. Sure, we'll show it. - if(!device) { - return true; - } - - // We've explicitly enabled this opener. - if(this.configOptions.indexOf("Show." + (device.serial_number as any)) != -1) { - return true; - } - - // We've explicitly hidden this opener. - if(this.configOptions.indexOf("Hide." + device.serial_number) != -1) { - return false; - } - - // If we don't have a gateway device attached to this opener, we're done here. - if(!device.parent_device_id) { - return true; - } - - // We've explicitly shown the gateway this opener is attached to. - if(this.configOptions.indexOf("Show." + device.parent_device_id) != -1) { - return true; - } - - // We've explicitly hidden the gateway this opener is attached to. - if(this.configOptions.indexOf("Hide." + device.parent_device_id) != -1) { - return false; - } - - // Nothing special to do - make this opener visible. - return true; + // + + // Nothing configured - we show all myQ devices to HomeKit. + if(!this.configOptions) { + return true; + } + + // No device. Sure, we'll show it. + if(!device) { + return true; + } + + // We've explicitly enabled this opener. + if(this.configOptions.indexOf("Show." + (device.serial_number as any)) !== -1) { + return true; + } + + // We've explicitly hidden this opener. + if(this.configOptions.indexOf("Hide." + device.serial_number) !== -1) { + return false; + } + + // If we don't have a gateway device attached to this opener, we're done here. + if(!device.parent_device_id) { + return true; + } + + // We've explicitly shown the gateway this opener is attached to. + if(this.configOptions.indexOf("Show." + device.parent_device_id) !== -1) { + return true; + } + + // We've explicitly hidden the gateway this opener is attached to. + if(this.configOptions.indexOf("Hide." + device.parent_device_id) !== -1) { + return false; + } + + // Nothing special to do - make this opener visible. + return true; } } diff --git a/src/myq.ts b/src/myq.ts index fcf212f..d155fc7 100644 --- a/src/myq.ts +++ b/src/myq.ts @@ -1,9 +1,9 @@ /* Copyright(C) 2020, HJD (https://github.com/hjdhjd). All rights reserved. */ -import { HAP, Logging } from 'homebridge'; +import { HAP, Logging } from "homebridge"; -import fetch from 'node-fetch'; -import util from 'util'; +import fetch from "node-fetch"; +import util from "util"; // An incomplete description of the myQ JSON, but enough for our purposes. export interface myQDevice { @@ -30,17 +30,17 @@ let debug = 0; */ const myqVersionMajor = 5; const myqVersionMinor = 1; -const myqVersion = myqVersionMajor + '.' + myqVersionMinor; +const myqVersion = myqVersionMajor + "." + myqVersionMinor; // myQ API base URL, currently v5. -const myqApi = 'https://api.myqdevice.com/api/v' + myqVersionMajor; +const myqApi = "https://api.myqdevice.com/api/v" + myqVersionMajor; // myQ API devices URL, currently v5.1 -const myqApidev = myqApi + '.' + myqVersionMinor; +const myqApidev = myqApi + "." + myqVersionMinor; // myQ app identifier and user agent used to validate against the myQ API. -const myqAppId = 'JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu'; -const myqAgent = 'okhttp/3.10.0'; +const myqAppId = "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu"; +const myqAgent = "okhttp/3.10.0"; /* * The myQ API is undocumented, non-public, and has been derived largely through @@ -81,13 +81,13 @@ export class myQ { // Headers that the myQ API expects. private myqHeaders = { - 'Content-Type': 'application/json', - 'User-Agent': myqAgent, + "Content-Type": "application/json", + "User-Agent": myqAgent, ApiVersion: myqVersion, - BrandId: '2', - Culture: 'en', + BrandId: "2", + Culture: "en", MyQApplicationId: myqAppId, - SecurityToken: '', + SecurityToken: "", }; // Initialize this instance with our login information. @@ -95,21 +95,21 @@ export class myQ { this.log = log; this.Email = email; this.Password = password; - this.securityToken = ''; - this.accountID = ''; + this.securityToken = ""; + this.accountID = ""; } // Log us into myQ and get a security token. private async myqAuthenticate() { // Login to the myQ API and get a security token for our session. - const response = await this.myqFetch(myqApi + '/Login', { - method: 'POST', + const response = await this.myqFetch(myqApi + "/Login", { + method: "POST", headers: this.myqHeaders, body: JSON.stringify({ UserName: this.Email, Password: this.Password }), }); if(!response) { - this.log('myQ API error: unable to authenticate. Will retry later.'); + this.log("myQ API error: unable to authenticate. Will retry later."); return 0; } @@ -123,16 +123,16 @@ export class myQ { // What we should get back upon successfully calling /Login is a security token for // use in future API calls this session. if(!data || !data.SecurityToken) { - this.log('Unable to get a security token from the myQ API.'); + this.log("Unable to get a security token from the myQ API."); return 0; } this.securityToken = data.SecurityToken; - this.log('Successfully connected to the myQ API.'); + this.log("Successfully connected to the myQ API."); if(debug) { - this.log('Token: %s', this.securityToken); + this.log("Token: %s", this.securityToken); } // Add the token to our headers that we will use for subsequent API calls. @@ -149,15 +149,15 @@ export class myQ { } // Get the account information. - const params = new URLSearchParams({ expand: 'account' }); + const params = new URLSearchParams({ expand: "account" }); - const response = await this.myqFetch(myqApi + '/My?' + params, { - method: 'GET', + const response = await this.myqFetch(myqApi + "/My?" + params, { + method: "GET", headers: this.myqHeaders, }); if(!response) { - this.log('myQ API error: unable to login. Will retry later.'); + this.log("myQ API error: unable to login. Will retry later."); return 0; } @@ -170,7 +170,7 @@ export class myQ { // No account information returned. if(!data || !data.Account) { - this.log('Unable to retrieve account information from myQ servers.'); + this.log("Unable to retrieve account information from myQ servers."); return 0; } @@ -178,7 +178,7 @@ export class myQ { this.accountID = data.Account.Id; if(debug) { - this.log('myQ accountID: ' + this.accountID); + this.log("myQ accountID: " + this.accountID); } return 1; @@ -193,7 +193,7 @@ export class myQ { // to potential account lockouts. The author definitely learned this one the hard way. if(this.lastCall && ((now - this.lastCall) < (5*1000))) { if(debug) { - this.log('Throttling myQ API call.'); + this.log("Throttling myQ API call."); } return this.Devices ? 1 : 0; @@ -208,15 +208,15 @@ export class myQ { } // Get the list of device information. - const params = new URLSearchParams({ filterOn: 'true' }); + const params = new URLSearchParams({ filterOn: "true" }); - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices?' + params, { - method: 'GET', + const response = await this.myqFetch(myqApidev + "/Accounts/" + this.accountID + "/Devices?" + params, { + method: "GET", headers: this.myqHeaders, }); if(!response) { - this.log('myQ API error: unable to refresh. Will retry later.'); + this.log("myQ API error: unable to refresh. Will retry later."); return 0; } @@ -241,7 +241,7 @@ export class myQ { // We've discovered a new device. this.log("myQ %s device discovered: %s (serial number: %s%s.", newDevice.device_family, newDevice.name, newDevice.serial_number, - newDevice.parent_device_id ? ", gateway: " + newDevice.parent_device_id + ")" : ")"); + newDevice.parent_device_id ? ", gateway: " + newDevice.parent_device_id + ")" : ")"); if(debug) { this.log(util.inspect(newDevice, { colors: true, sorted: true, depth: 3 })); @@ -260,7 +260,7 @@ export class myQ { } // We've had a device disappear. - this.log('myQ %s device removed: %s - %s.', existingDevice.device_family, existingDevice.name, existingDevice.serial_number); + this.log("myQ %s device removed: %s - %s.", existingDevice.device_family, existingDevice.name, existingDevice.serial_number); if(debug) { this.log(util.inspect(existingDevice, { colors: true, sorted: true, depth: 3 })); @@ -284,13 +284,13 @@ export class myQ { debug = 1; // Get the list of device information. - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/devices/' + deviceId, { - method: 'GET', + const response = await this.myqFetch(myqApidev + "/Accounts/" + this.accountID + "/devices/" + deviceId, { + method: "GET", headers: this.myqHeaders, }); if(!response) { - this.log('myQ API error: unable to query device. Will retry later.'); + this.log("myQ API error: unable to query device. Will retry later."); return 0; } @@ -309,7 +309,7 @@ export class myQ { this.Devices = data.items; this.Devices.forEach((device: any) => { - this.log('Device:'); + this.log("Device:"); this.log(util.inspect(device, { colors: true, sorted: true, depth: 2 })); }); @@ -325,14 +325,14 @@ export class myQ { return 0; } - const response = await this.myqFetch(myqApidev + '/Accounts/' + this.accountID + '/Devices/' + deviceId + '/actions', { - method: 'PUT', + const response = await this.myqFetch(myqApidev + "/Accounts/" + this.accountID + "/Devices/" + deviceId + "/actions", { + method: "PUT", headers: this.myqHeaders, body: JSON.stringify({ action_type: command }), }); if(!response) { - this.log('myQ API error: unable to execute command.'); + this.log("myQ API error: unable to execute command."); return 0; } @@ -357,7 +357,7 @@ export class myQ { (device = this.Devices.find( (x: any) => x.device_type && - x.device_type.indexOf('garagedooropener') !== -1 && + x.device_type.indexOf("garagedooropener") !== -1 && x.serial_number && hap.uuid.generate(x.serial_number) === uuid, )) !== undefined @@ -377,19 +377,19 @@ export class myQ { // Bad username and password. if(response.status === 401) { - this.log('Invalid username or password given. Check your login and password.'); + this.log("Invalid username or password given. Check your login and password."); return undefined; } // Some other unknown error occurred. if(!response.ok) { - this.log('myQ API error: %s %s', response.status, response.statusText); + this.log("myQ API error: %s %s", response.status, response.statusText); return undefined; } return response; } catch(error) { - this.log.error('Fetch error encountered: ' + error); + this.log.error("Fetch error encountered: " + error); return undefined; } } From 81d4eaa65ae9847e396bf1cc99150fe880aaffd5 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 16:46:55 -0500 Subject: [PATCH 11/14] Fix typo (its) --- src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4c0120c..3b76c31 100644 --- a/src/index.ts +++ b/src/index.ts @@ -153,13 +153,13 @@ class myQPlatform implements DynamicPlatformPlugin { // If we are already opening or closing the garage door, we error out. myQ doesn't appear to allow // interruptions to an open or close command that is currently executing - it must be allowed to - // complete it's action before accepting a new one. + // complete its action before accepting a new one. if(myQState === hap.Characteristic.CurrentDoorState.OPENING || myQState === hap.Characteristic.CurrentDoorState.CLOSING) { const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? "opening" : "closing"; const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? "close" : "open"; this.log( - "%s - unable to %s door while currently trying to finish %s. myQ must complete it's existing action " + + "%s - unable to %s door while currently trying to finish %s. myQ must complete its existing action " + "before attmepting a new one.", accessory.displayName, actionAttempt, @@ -342,7 +342,7 @@ class myQPlatform implements DynamicPlatformPlugin { return 0; } - // Iterate through our accessories and update it's status with the corresponding myQ + // Iterate through our accessories and update its status with the corresponding myQ // status. this.accessories.forEach((accessory: PlatformAccessory) => { const oldState = accessory.context.doorState; @@ -519,7 +519,7 @@ class myQPlatform implements DynamicPlatformPlugin { // into that gateway. So if you have multiple gateways but only want one exposed in this plugin, // you may do so by hiding it. // - // 2. Explicitly hiding, or showing an opener device by it's serial number will always override the above. + // 2. Explicitly hiding, or showing an opener device by its serial number will always override the above. // This means that it's possible to hide a gateway, and all the openers that are attached to it, and then // override that behavior on a single opener device that it's connected to. // From 41f1950731131fa942f8ffe049431e1c4891607d Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 16:51:08 -0500 Subject: [PATCH 12/14] Code review comments --- src/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3b76c31..840555e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -154,17 +154,11 @@ class myQPlatform implements DynamicPlatformPlugin { // If we are already opening or closing the garage door, we error out. myQ doesn't appear to allow // interruptions to an open or close command that is currently executing - it must be allowed to // complete its action before accepting a new one. - if(myQState === hap.Characteristic.CurrentDoorState.OPENING || myQState === hap.Characteristic.CurrentDoorState.CLOSING) { + if((myQState === hap.Characteristic.CurrentDoorState.OPENING) || (myQState === hap.Characteristic.CurrentDoorState.CLOSING)) { const actionExisting = myQState === hap.Characteristic.CurrentDoorState.OPENING ? "opening" : "closing"; const actionAttempt = value === hap.Characteristic.TargetDoorState.CLOSED ? "close" : "open"; - this.log( - "%s - unable to %s door while currently trying to finish %s. myQ must complete its existing action " + - "before attmepting a new one.", - accessory.displayName, - actionAttempt, - actionExisting, - ); + this.log("%s - unable to %s door while currently trying to finish %s. myQ must complete its existing action before attmepting a new one.", accessory.displayName, actionAttempt, actionExisting); callback(new Error("Unable to accept a new set event while another is completing.")); } else if(value === hap.Characteristic.TargetDoorState.CLOSED) { From bc9e71ea909311ecdb4eee14ce1f1b5d0d9c71ce Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 16:52:37 -0500 Subject: [PATCH 13/14] Per previous commits --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 840555e..b2a41fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -272,7 +272,7 @@ class myQPlatform implements DynamicPlatformPlugin { } // Fun fact: This firmware information is stored on the gateway not the opener. - const gwParent: any = this.myQ.Devices.find((x: myQDevice) => x.serial_number === device.parent_device_id); + const gwParent = this.myQ.Devices.find((x: myQDevice) => x.serial_number === device.parent_device_id); let fwVersion = "0.0"; if(gwParent && gwParent.state && gwParent.state.firmware_version) { From fdf49f6094de754e169c41cda4f98f92325f6267 Mon Sep 17 00:00:00 2001 From: dxdc Date: Sun, 5 Jul 2020 17:07:51 -0500 Subject: [PATCH 14/14] Fix @types/node version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3be541..4409678 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "homepage": "https://github.com/hjdhjd/homebridge-myq2#readme", "devDependencies": { - "@types/node": "14.0.14", + "@types/node": "10.17.25", "@typescript-eslint/eslint-plugin": "^3.5.0", "@typescript-eslint/parser": "^3.5.0", "eslint": "^7.4.0",