homebridge-elero-stick
Version:
Elero funkstick for homebridge: https://github.com/ma-ku/homebridge-elero-stick
482 lines • 23.7 kB
JavaScript
"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