UNPKG

homebridge-elero-stick

Version:

Elero funkstick for homebridge: https://github.com/ma-ku/homebridge-elero-stick

482 lines 23.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EleroShutterAccessory = void 0; const elero_accessory_1 = require("./elero-accessory"); const perf_hooks_1 = require("perf_hooks"); const elero_stick_1 = require("./usb/elero-stick"); var PositionStates; (function (PositionStates) { PositionStates[PositionStates["DECREASING"] = 0] = "DECREASING"; PositionStates[PositionStates["INCREASING"] = 1] = "INCREASING"; PositionStates[PositionStates["STOPPED"] = 2] = "STOPPED"; })(PositionStates || (PositionStates = {})); class EleroShutterAccessory extends elero_accessory_1.EleroAccessory { constructor(hap, log, platformConfig, motorConfig, uuid, stick, channel) { super(hap, log, platformConfig, motorConfig, uuid, stick, channel); // Time before the shutter actually moves at full speed this._startDelay = 0; // Is the cover blocked/jammed? this._jammed = false; this._positionHeld = 0; this._reverse = false; // last known position of the blinds, down by default this._lastPosition = 0; // down by default this._currentTargetPosition = 0; // Stopped by default this._currentPositionState = 0; this.positionState = 0; this._lastStatusTimestamp = perf_hooks_1.performance.now(); // Needed to avooid duplicate log outputs if nothing changes this._lastInfo = ''; this._duration = motorConfig.duration || 20000; this._startDelay = motorConfig.startDelay || 0; this._currentPositionState = hap.Characteristic.PositionState.STOPPED; this._reverse = motorConfig.reverse || false; let service = new hap.Service.WindowCovering(this.displayName); // the current position (0-100%) // https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L493 service .getCharacteristic(hap.Characteristic.CurrentPosition) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getCurrentPosition.bind(this)); // the position state // 0 = DECREASING; 1 = INCREASING; 2 = STOPPED; // https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1138 service .getCharacteristic(hap.Characteristic.PositionState) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getPositionState.bind(this)); // the target position (0-100%) // https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L1564 service .getCharacteristic(hap.Characteristic.TargetPosition) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getTargetPosition.bind(this)) .on("set" /* this.hap.CharacteristicEventTypes.SET */, this.setTargetPosition.bind(this)); // Hold Position stopps the service // https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js#L855 service .getCharacteristic(hap.Characteristic.HoldPosition) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getHoldPosition.bind(this)) .on("set" /* this.hap.CharacteristicEventTypes.SET */, this.setHoldPosition.bind(this)); service .getCharacteristic(hap.Characteristic.ObstructionDetected) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getObstructionDetected.bind(this)); service .getCharacteristic(hap.Characteristic.Name) .on("get" /* this.hap.CharacteristicEventTypes.GET */, this.getName.bind(this)); this.windowCoveringService = service; this.services.push(service); if (motorConfig.stopButton == true) { this.stopButtonService = this.createStopButton(hap, log, service); service.linkedServices.push(this.stopButtonService); } if (motorConfig.ventilationPositionButton == true) { this.ventilationPositionButtonService = this.createVentilationPositionButton(hap, log, service); service.linkedServices.push(this.ventilationPositionButtonService); } if (motorConfig.intermediatePositionButton == true) { this.intermediatePositionButtonService = this.createIntermediatePositionButton(hap, log, service); service.linkedServices.push(this.intermediatePositionButtonService); } log.info("Elero shutter accessory for channel '%s' created!", channel); } createStopButton(hap, log, shutterService) { let switchService = new hap.Service.Switch(this.name + " - Stop", "stop-button"); log.info("Creating stop button for channel '%s'", this.channel); switchService.getCharacteristic(hap.Characteristic.On) .on("get" /* this.hap.CharacteristicEventTypes.GET */, (callback) => { // Switch will always flip back to false callback(undefined, false); }) .on("set" /* this.hap.CharacteristicEventTypes.SET */, (value, callback) => { let state = value; if (state) { log.info("Switch pressed"); this.stick.commandStop([this.channel]); } callback(); }); switchService.linkedServices = [shutterService]; this.services.push(switchService); return switchService; } createVentilationPositionButton(hap, log, shutterService) { let switchService = new hap.Service.Switch(this.name + " - Ventilation", "ventilation-position-button"); log.info("Creating ventilation position button for channel '%s'", this.channel); switchService.getCharacteristic(hap.Characteristic.On) .on("get" /* this.hap.CharacteristicEventTypes.GET */, (callback) => { // Switch will always flip back to false callback(undefined, false); }) .on("set" /* this.hap.CharacteristicEventTypes.SET */, (value, callback) => { let state = value; if (state) { log.info("Switch pressed"); this.stick.commandVentilationPosition([this.channel]); } callback(); }); switchService.linkedServices = [shutterService]; this.services.push(switchService); return switchService; } createIntermediatePositionButton(hap, log, shutterService) { let switchService = new hap.Service.Switch(this.name + " - Intermediate", "intermediate-position-button"); log.info("Creating intermediate position button for channel '%s'", this.channel); switchService.getCharacteristic(hap.Characteristic.On) .on("get" /* this.hap.CharacteristicEventTypes.GET */, (callback) => { // Switch will always flip back to false callback(undefined, false); }) .on("set" /* this.hap.CharacteristicEventTypes.SET */, (value, callback) => { let state = value; if (state) { log.info("Switch pressed"); this.stick.commandIntermediatePosition([this.channel]); } callback(); }); switchService.linkedServices = [shutterService]; this.services.push(switchService); return switchService; } /** * This method is optional to implement. It is called when HomeKit ask to identify the accessory. * Typical this only ever happens at the pairing process. */ identify() { let sequence = [ (stick, channel) => { stick.commandStop([channel]); return 250; }, (stick, channel) => { stick.commandUp([channel]); return 750; }, (stick, channel) => { stick.commandDown([channel]); return 750; }, (stick, channel) => { stick.commandUp([channel]); return 750; }, (stick, channel) => { stick.commandDown([channel]); return 750; }, (stick, channel) => { stick.commandStop([channel]); return 250; } ]; this.log.debug('[%d][%s] Identify', this.channel, this.name); this.runCallback(sequence); } runCallback(sequence) { let callback = sequence.shift(); if (callback) { let stick = this.stick; let channel = this.channel; let timeout = callback(stick, channel); setTimeout(() => { this.runCallback(sequence); }, timeout); } } getName(callback) { this.log.debug('[%d] Get Name: %s', this.channel, this.name); callback(null, this.name); } // Provide the outside position value depending on the reverse direction flag calculatePosition(value) { return (this._reverse ? 100 - value : value); } // Provide the outside state info depending on the reverse direction flag calculateState(value) { if (this._reverse) { switch (value) { case this.hap.Characteristic.PositionState.DECREASING: return this.hap.Characteristic.PositionState.INCREASING; case this.hap.Characteristic.PositionState.INCREASING: return this.hap.Characteristic.PositionState.DECREASING; } } return value; } get isJammed() { return this._jammed; } // HomeKit callback: GET ObstructionDetected async getObstructionDetected(callback) { this.log.debug('[%d][%s] Get ObstructionDetected: %s', this.channel, this.name, this.isJammed); callback(null, this.isJammed); } get holdPosition() { return this._positionHeld; } get currentPositionState() { return this._currentPositionState; } // HomeKit callback: GET PositionState getPositionState(callback) { var state = this.currentPositionState; if (this._reverse) { if (state == this.hap.Characteristic.PositionState.STOPPED) { // Nothing to do } else if (state == this.hap.Characteristic.PositionState.DECREASING) { state = this.hap.Characteristic.PositionState.INCREASING; } else if (state == this.hap.Characteristic.PositionState.INCREASING) { state = this.hap.Characteristic.PositionState.DECREASING; } } this.log.debug('[%d][%s] Requested PositionState: %s', this.channel, this.name, PositionStates[state]); callback(null, state); } get lastPosition() { return this._lastPosition; } updateLastPosition(value) { if (value >= 0 && value <= 100) { this._lastPosition = value; let hkValue = this.calculatePosition(this._lastPosition); this.log.debug("[%d][%s] Updating lastPosition: %d", this.channel, this.name, hkValue); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.CurrentPosition) .updateValue(hkValue); } else { this.log.error("[%d][%s] Updating lastPosition with illegal value: %d: ", this.channel, this.name, value); } } get currentTargetPosition() { return this._currentTargetPosition; } updateTargetPosition(value) { if (value >= 0 && value <= 100) { this._currentTargetPosition = value; let hkValue = this.calculatePosition(this._currentTargetPosition); this.log.debug("[%d][%s] Updating currentTargetPosition: %d", this.channel, this.name, hkValue); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.TargetPosition) .updateValue(hkValue); } else { this.log.error("[%d][%s] Updating currentTargetPosition with illegal value: %d: ", this.channel, this.name, value); } } // HomeKit callback: GET TargetPosition async getTargetPosition(callback) { let hkValue = this.calculatePosition(this._currentTargetPosition); this.log.debug('[%d][%s] Requested TargetPosition: %s', this.channel, this.name, hkValue); callback(null, hkValue); } // HomeKit callback: SET TargetPosition async setTargetPosition(pos, callback) { this.log.debug('[%d][%s] Set TargetPosition: %d', this.channel, this.name, pos); this._currentTargetPosition = this.calculatePosition(pos); this._positionHeld = 0; var moving = false; if ((this._currentTargetPosition < this._lastPosition) || (this._currentTargetPosition < 5)) { this.stick.commandDown([this.channel]); moving = true; } // else if (pos <= 50) { // this.stick.serialConnection.commandIntermediatePosition([this.channel]) // } // else if (pos <= 75) { // this.stick.serialConnection.commandVentilationPosition([this.channel]) // } else if ((this._currentTargetPosition > this._lastPosition) || (this._currentTargetPosition > 5)) { this.stick.commandUp([this.channel]); moving = true; } if (moving) { this.reportingInterval = this.platformConfig.movingUpdateInterval || 1500; this.isMonitoring = true; } callback(null); } updatePositionState(value) { this._currentPositionState = value; let hkValue = this.calculateState(this._currentPositionState); this.log.debug("[%d][%s] Updating currentPositionState: %s", this.channel, this.name, PositionStates[hkValue]); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.PositionState) .updateValue(hkValue); } set lastPosition(value) { this._lastPosition = value; let hkValue = this.calculatePosition(this._lastPosition); this.log.debug("[%d][%s] Setting lastPosition: ", this.channel, this.name, hkValue); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.CurrentPosition) .setValue(hkValue); } set currentPositionState(value) { this._currentPositionState = value; let hkValue = this.calculateState(this._currentPositionState); this.log.debug("[%d][%s] Setting currentPositionState: %d", this.channel, this.name, PositionStates[hkValue]); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.PositionState) .setValue(hkValue); } // HomeKit callback: GET CurrentPosition async getCurrentPosition(callback) { let hkValue = this.calculatePosition(this.lastPosition); this.log.debug('[%d][%s] Requested CurrentPosition: %s', this.channel, this.name, hkValue); callback(null, hkValue); } set currentTargetPosition(value) { this._currentTargetPosition = value; let hkValue = this.calculatePosition(this._currentTargetPosition); this.log.debug("[%d][%s] Setting currentTargetPosition: ", this.channel, this.name, hkValue); this.windowCoveringService .getCharacteristic(this.hap.Characteristic.TargetPosition) .setValue(hkValue); } set holdPosition(value) { this._positionHeld = value; this.windowCoveringService .getCharacteristic(this.hap.Characteristic.HoldPosition) .setValue(this._positionHeld); } // HomeKit callback: GET HoldPosition async getHoldPosition(callback) { this.log.debug('[%d][%s] Requested HoldPosition: %s', this.channel, this.name, this._positionHeld); callback(null, this._positionHeld); } // HomeKit callback: SET HoldPosition async setHoldPosition(value, callback) { this.log.debug('[%d][%s] Set HoldPosition: %d', this.channel, this.name, value); if (value == 1) { this.stick.commandStop([this.channel]); } this._positionHeld = value; callback(null, this._positionHeld); } processState(state, currentTimestamp) { var newState = this.hap.Characteristic.PositionState.STOPPED; var newPosition = this._lastPosition; var newTargetPosition = this._currentTargetPosition; var newInterval = this.reportingInterval || this.platformConfig.defaultUpdateInterval; this._jammed = false; var actualMotorState = this.hap.Characteristic.PositionState.STOPPED; this.log.debug('[%d][%s] Reported motor state %s', this.channel, this.name, elero_stick_1.ELERO_STATES[state]); switch (state) { case elero_stick_1.ELERO_STATES.MOVING_DOWN: case elero_stick_1.ELERO_STATES.START_MOVE_DOWN: actualMotorState = this.hap.Characteristic.PositionState.DECREASING; break; case elero_stick_1.ELERO_STATES.MOVING_UP: case elero_stick_1.ELERO_STATES.START_MOVE_UP: actualMotorState = this.hap.Characteristic.PositionState.INCREASING; break; default: actualMotorState = this.hap.Characteristic.PositionState.STOPPED; } if (actualMotorState != this.hap.Characteristic.PositionState.STOPPED) { // We are moving so figure out the elapsed time // and adjust the lastPosition accordingly. var direction = 1; var check = (position, targetPosition) => { return (position >= targetPosition); }; if (actualMotorState == this.hap.Characteristic.PositionState.DECREASING) { direction = -1; check = (position, targetPosition) => { return (position <= targetPosition); }; } if (!this.isMonitoring) { // We will not interrupt check = (position, targetPosition) => { return false; }; } var elapsed = (currentTimestamp - this._lastStatusTimestamp); this.updatePositionState(actualMotorState); if (elapsed > 0) { if (this._duration > 0) { var delta = Math.max(0, 100 * elapsed / this._duration); var newPosition = Math.min(100, Math.max(0, this._lastPosition + direction * delta)); this.updateLastPosition(newPosition); // If we are driving to an intermediate position, we need to stop // ourselves. For fully opened or closed, we will wait until TOP_POS // or BOTTOM_POS is reported. if ((this._currentTargetPosition > 0) && (this._currentTargetPosition < 100)) { let result = check(newPosition, this._currentTargetPosition); this.log.debug('Checking channel %s every %s ms. Now at %d, moving to %d. Check result is %s', this.channel, this.reportingInterval, newPosition, this._currentTargetPosition, result); if (result) { this.stick.commandStop([this.channel]); } } } this._lastStatusTimestamp = currentTimestamp; } } if (state == elero_stick_1.ELERO_STATES.BOTTOM_POS_STOP) { newState = this.hap.Characteristic.PositionState.STOPPED; newPosition = 0; newTargetPosition = 0; newInterval = this.platformConfig.defaultUpdateInterval; this.isMonitoring = false; } else if (state == elero_stick_1.ELERO_STATES.TOP_POS_STOP) { newState = this.hap.Characteristic.PositionState.STOPPED; newPosition = 100; newTargetPosition = 100; newInterval = this.platformConfig.defaultUpdateInterval; this.isMonitoring = false; } // Not supported/tested for now, might be verified later with // the right motor available?! // else if (state == ELERO_STATES.TOP_VENT_POS_STOP) { // newState = Characteristic.PositionState.STOPPED // newPosition = 33 // newTargetPosition = 33 // newInterval = this.stick.defaultUpdateInterval // } // else if (state == ELERO_STATES.BOTTOM_INTERM_POS_STOP) { // newState = Characteristic.PositionState.STOPPED // newPosition = 67 // newTargetPosition = 67 // newInterval = this.stick.defaultUpdateInterval // } else if (state == elero_stick_1.ELERO_STATES.MOVING_DOWN) { newState = this.hap.Characteristic.PositionState.DECREASING; newInterval = this.platformConfig.movingUpdateInterval; } else if (state == elero_stick_1.ELERO_STATES.MOVING_UP) { newState = this.hap.Characteristic.PositionState.INCREASING; newInterval = this.platformConfig.movingUpdateInterval; } else if (state == elero_stick_1.ELERO_STATES.START_MOVE_DOWN) { newState = this.hap.Characteristic.PositionState.DECREASING; newInterval = this.platformConfig.movingUpdateInterval; this._lastStatusTimestamp = currentTimestamp + this._startDelay; } else if (state == elero_stick_1.ELERO_STATES.START_MOVE_UP) { newState = this.hap.Characteristic.PositionState.INCREASING; newInterval = this.platformConfig.movingUpdateInterval; this._lastStatusTimestamp = currentTimestamp + this._startDelay; } else if (state == elero_stick_1.ELERO_STATES.BLOCKING) { newState = this.hap.Characteristic.PositionState.STOPPED; newInterval = this.platformConfig.defaultUpdateInterval; this._jammed = true; this.isMonitoring = false; } else if (state == elero_stick_1.ELERO_STATES.OVERHEATED) { newState = this.hap.Characteristic.PositionState.STOPPED; newInterval = this.platformConfig.defaultUpdateInterval; this._jammed = true; this.isMonitoring = false; } else if (state == elero_stick_1.ELERO_STATES.STOP_UNDEFINED_POS) { newState = this.hap.Characteristic.PositionState.STOPPED; newInterval = this.platformConfig.defaultUpdateInterval; newTargetPosition = this.lastPosition; this.isMonitoring = false; } this._lastStatusTimestamp = currentTimestamp; this.updateTargetPosition(newTargetPosition); this.updateLastPosition(newPosition); this.updatePositionState(newState); this.lastPosition = newPosition; this.positionState = newState; this.log.debug('Updating reportingInterval for channel %s to %s', this.channel, this.reportingInterval); this.reportingInterval = newInterval; this.logInfo(); } logInfo() { var info = " STOPPED ["; if (this.calculateState(this.currentPositionState) == this.hap.Characteristic.PositionState.DECREASING) { info = " CLOSING ["; } else if (this.calculateState(this.currentPositionState) == this.hap.Characteristic.PositionState.INCREASING) { info = " OPENING ["; } if (this.isJammed) { info = " JAMMED ["; } // We build the string first and only emit a log entry if that entry has changed info = "[" + this.channel + "] " + this.name + info + this.calculatePosition(this._lastPosition) + "]"; if (info != this._lastInfo) { this.log.info(info); this._lastInfo = info; } } } exports.EleroShutterAccessory = EleroShutterAccessory; //# sourceMappingURL=elero-shutter-accessory.js.map