diff --git a/README.md b/README.md index cd32fed5..51c95955 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ interval (optional) | The amount of time between each send of a hex code in seco disableAutomaticOff (optional) | Prevent the switch from turning off automatically after a given amount of time. | false | true onDurationOpen (optional) | The amount of time before the window covering automatically turns itself off when opening (used in conjunction with disableAutomaticOff). | 5 | 2 onDurationClose (optional) | The amount of time before the window covering automatically turns itself off when closing (used in conjunction with disableAutomaticOff). | 5 | 2 +hold (optional) | Disabling this will force the window-covering to increment in intervals rather than in a fluid motion. | false | true host (optional) | The IP or MAC address of the Broadlink RM device. | 192.168.1.32 | (auto-discovered) #### "data" key-value object diff --git a/accessories/windowCovering.js b/accessories/windowCovering.js index fbe3d95f..d4d44282 100644 --- a/accessories/windowCovering.js +++ b/accessories/windowCovering.js @@ -6,86 +6,149 @@ class WindowCoveringAccessory extends BroadlinkRMAccessory { async setTargetPosition (hexData, previousValue) { const { config, data, log, name } = this; - const { open, close } = data; - let { percentageChangePerSend } = config; + const { open, close, off } = data; + + if (off && this.operationID ) { + log(`${name} setTargetPosition: cancel last operation`) + this.stop(); + } + + this.operationID = Date.now(); + const currentOperationID = this.operationID; - if (!previousValue) previousValue = 0 + if (!this.currentPosition) this.currentPosition = 0; + + let { percentageChangePerSend } = config; if (!percentageChangePerSend) percentageChangePerSend = 10; - let difference = this.targetPosition - previousValue; + let difference = this.targetPosition - this.currentPosition; - const opening = (difference > 0); - if (!opening) difference = -1 * difference; + this.opening = (difference > 0); + if (!this.opening) difference = -1 * difference; // If the target position is not a multiple of the percentageChangePerSend // value then make it so if (this.targetPosition % percentageChangePerSend !== 0) { let roundedTargetPosition; - if (opening) roundedTargetPosition = Math.ceil(this.targetPosition / percentageChangePerSend) * percentageChangePerSend; - if (!opening) roundedTargetPosition = Math.floor(this.targetPosition / percentageChangePerSend) * percentageChangePerSend; + if (this.opening) roundedTargetPosition = Math.ceil(this.targetPosition / percentageChangePerSend) * percentageChangePerSend; + if (!this.opening) roundedTargetPosition = Math.floor(this.targetPosition / percentageChangePerSend) * percentageChangePerSend; this.targetPosition = previousValue; - log(`${name} setTargetPosition: (rounding to multiple of percentageChangePerSend; ${roundedTargetPosition})`); + log(`${name} setTargetPosition: (rounding to multiple of percentageChangePerSend; ${roundedTargetPosition}) ${currentOperationID}`); setTimeout(() => { + if (currentOperationID !== this.operationID) return + this.windowCoveringService.setCharacteristic(Characteristic.TargetPosition, roundedTargetPosition); }, 200); return; } - const sendCount = Math.ceil(difference / percentageChangePerSend); + const increments = Math.ceil(difference / percentageChangePerSend); - hexData = opening ? open : close + hexData = this.opening ? open : close -try { - this.openOrClose({ hexData, opening, sendCount, previousValue }) // Perform this asynchronously i.e. without await - } catch (err) { - console.log(err) - } + this.openOrClose({ hexData, increments, previousValue, currentOperationID }) // Perform this asynchronously i.e. without await } - async openOrClose ({ hexData, opening, sendCount, previousValue }) { + async openOrClose ({ hexData, increments, previousValue, currentOperationID }) { let { config, data, host, name, log } = this; - let { percentageChangePerSend, interval, disableAutomaticOff, onDuration, onDurationOpen, onDurationClose } = config; + let { hold, percentageChangePerSend, interval, disableAutomaticOff, onDuration, onDurationOpen, onDurationClose } = config; const { off } = data; if (!interval) percentageChangePerSend = 0.5; if (!percentageChangePerSend) percentageChangePerSend = 10; if (disableAutomaticOff === undefined) disableAutomaticOff = true; + if (!onDuration) onDuration = this.opening ? onDurationOpen : onDurationClose; + if (!onDuration) onDuration = 2; - let currentValue = previousValue; + if (hold) { + let difference = this.targetPosition - this.currentPosition + if (!this.opening) difference = -1 * difference; - // Itterate through each hex config in the array - for (let index = 0; index < sendCount; index++) { + const durationPerPercentage = onDuration / percentageChangePerSend; + const totalTime = durationPerPercentage * difference; - if (opening) currentValue += percentageChangePerSend - if (!opening) currentValue -= percentageChangePerSend + log(`${name} setTargetPosition: currently ${this.currentPosition}%, moving to ${this.targetPosition}%`); + log(`${name} setTargetPosition: ${totalTime}s (${onDuration} / ${percentageChangePerSend} * ${difference}) until auto-off ${currentOperationID}`); sendData({ host, hexData, log }); - this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, currentValue); - if (!disableAutomaticOff) { - if (!onDuration) onDuration = opening ? onDurationOpen : onDurationClose; - if (!onDuration) onDuration = 2; + this.updateCurrentPositionAtIntervals(currentOperationID) - log(`${name} setTargetPosition: waiting ${onDuration}s for auto-off`); - await delayForDuration(onDuration); + this.autoStopTimeout = setTimeout(() => { + this.stop(); + }, totalTime * 1000) + } else { + let currentValue = this.currentPosition || 0; + // Itterate through each hex config in the array + for (let index = 0; index < increments; index++) { + if (currentOperationID !== this.operationID) return; - if (!off) throw new Error('An "off" hex code must be set if "disableAutomaticOff" is set to false.') + if (this.opening) currentValue += percentageChangePerSend; + if (!this.opening) currentValue -= percentageChangePerSend; - log(`${name} setTargetPosition: auto-off`); - sendData({ host, hexData: off, log }); - } + sendData({ host, hexData, log }); + this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, currentValue); + + if (!disableAutomaticOff) { + log(`${name} setTargetPosition: waiting ${onDuration}s until auto-off ${currentOperationID}`); + await delayForDuration(onDuration); + if (currentOperationID !== this.operationID) return; + + if (!off) throw new Error('An "off" hex code must be set if "disableAutomaticOff" is set to false.') - log(`${name} setTargetPosition: waiting ${interval}s for next send`); + log(`${name} setTargetPosition: auto-off`); + sendData({ host, hexData: off, log }); + } - if (index < sendCount) await delayForDuration(interval); + log(`${name} setTargetPosition: waiting ${interval}s for next send ${currentOperationID}`); + + if (index < sendCount) await delayForDuration(interval); + if (currentOperationID !== this.operationID) return; + } } } + stop () { + const { data, host, log, name } = this; + const { off } = data; + + if (this.autoStopTimeout) clearTimeout(this.autoStopTimeout) + if (this.updateCurrentPositionTimeout) clearTimeout(this.updateCurrentPositionTimeout) + + this.operationID = undefined; + this.opening = undefined; + + log(`${name} setTargetPosition: off`); + if (off) sendData({ host, hexData: off, log }); + } + + updateCurrentPositionAtIntervals (currentOperationID) { + const { config } = this; + let { onDuration, onDurationOpen, onDurationClose, percentageChangePerSend } = config; + + if (!onDuration) onDuration = this.opening ? onDurationOpen : onDurationClose; + if (!onDuration) onDuration = 2; + + const durationPerPercentage = onDuration / percentageChangePerSend; + + this.updateCurrentPositionTimeout = setTimeout(() => { + if (currentOperationID !== this.operationID) return; + + let currentValue = this.currentPosition || 0; + if (this.opening) currentValue++; + if (!this.opening) currentValue--; + + this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, currentValue); + this.updateCurrentPositionAtIntervals(currentOperationID); + }, durationPerPercentage * 1000) + + } + getServices () { const services = super.getServices(); diff --git a/config-sample.json b/config-sample.json index 4a086994..686aa6b5 100644 --- a/config-sample.json +++ b/config-sample.json @@ -178,6 +178,7 @@ "disableAutomaticOff": false, "onDurationOpen": 2, "onDurationClose": 2, + "hold": true, "data":{ "open":"2600500000012...", "close":"2600500000012...", diff --git a/package.json b/package.json index 787a1983..b69f84d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-broadlink-rm", - "version": "1.3.3", + "version": "1.3.4", "description": "Broadlink RM plugin (including the mini and pro) for homebridge: https://github.com/nfarina/homebridge", "license": "ISC", "keywords": [ @@ -21,6 +21,6 @@ "url": "git@github.com:lprhodes/homebridge-broadlink-rm.git" }, "dependencies": { - "broadlinkjs": "git+https://github.com/lprhodes/broadlinkjs.git#0fe3146" + "broadlinkjs": "git+https://github.com/lprhodes/broadlinkjs.git#0fe3146", } }