UNPKG

homebridge-connector-hub

Version:

A Homebridge plugin to expose Connector+ compatible blinds, curtains, awnings etc to Homekit

165 lines 8.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectorDeviceHandler = void 0; const log_1 = require("../util/log"); const connector_hub_api_1 = require("./connector-hub-api"); const connector_hub_constants_1 = require("./connector-hub-constants"); const connector_hub_helpers_1 = require("./connector-hub-helpers"); /** * This class exposes methods for handling all conversions between Homekit and * Connector co-ordinate systems. Generally, Connector positions are the inverse * of Homekit values, but in certain cases this does not hold true. */ class ConnectorDeviceHandler { constructor(deviceInfo, config) { this.deviceInfo = deviceInfo; this.config = config; // By default, a value of 100 is fully closed for connector blinds. this.kClosedValue = 100; // Map of canonical field names to their (variable) effective field names. For // a TDBU device, these fields will be suffixed with _T or _B. this.fields = { currentPosition: 'currentPosition', currentState: 'currentState', currentAngle: 'currentAngle', targetPosition: 'targetPosition', targetAngle: 'targetAngle', batteryLevel: 'batteryLevel', operation: 'operation', }; // Unlike hub devices, a WiFi curtain's position and target percentages are // the same as Homekit, and the inverse of other Connector devices. This is // also true of the top-down component of a TDBU blind. if (deviceInfo.deviceType === connector_hub_api_1.DeviceType.kWiFiCurtain || deviceInfo.tdbuType === connector_hub_helpers_1.TDBUType.kTopDown) { this.kClosedValue = this.invertPC(this.kClosedValue); } // If the user reversed this device's direction, invert the closed state. const reverseDevice = config.reverseDirection.includes(deviceInfo.mac); if ((0, connector_hub_helpers_1.xor)(reverseDevice, config.invertReverseList)) { this.kClosedValue = this.invertPC(this.kClosedValue); } // Update the field names used in the device data if this is a TDBU blind. if (deviceInfo.tdbuType !== connector_hub_helpers_1.TDBUType.kNone) { const suffix = (deviceInfo.tdbuType === connector_hub_helpers_1.TDBUType.kTopDown ? '_T' : '_B'); for (const field in this.fields) { this.fields[field] = `${this.fields[field]}${suffix}`; } } } // Return an array containing the Hub target corresponding to the input // Homekit value, and a command to implement the targeting request. makeTargetRequest(homekitTarget) { const hubTarget = this.fromHomekitPercent(homekitTarget); if (this.usesBinaryState()) { return [ this.binarizeTargetPosition(hubTarget), this.makeOpenCloseRequest(hubTarget), ]; } return [hubTarget, this.makeTargetPositionRequest(hubTarget)]; } // Given a hub target value, constructs a binary open/close request. makeOpenCloseRequest(hubTarget) { return { [this.fields.operation]: this.positionToOpCode(this.binarizeTargetPosition(hubTarget)), }; } // Given a hub target value, constructs a percentage targeting request. makeTargetPositionRequest(hubTarget) { return { [this.fields.targetPosition]: hubTarget }; } // Convert a percentage position into a binary open / closed state. Note that // the input is a Connector hub position, not an inverted Homekit position. positionToOpCode(hubPos) { return Math.abs(this.kClosedValue - hubPos) < 50 ? connector_hub_api_1.DeviceOpCode.kClose : connector_hub_api_1.DeviceOpCode.kOpen; } // Given a kOpen or kClose opcode, return the equivalent position. opCodeToPosition(opCode) { return opCode === connector_hub_api_1.DeviceOpCode.kClose ? this.kClosedValue : this.invertPC(this.kClosedValue); } // Helper function to convert between Hub and Homekit percentages. invertPC(percent) { return (100 - percent); } toHomekitPercent(hubPC) { return this.kClosedValue === 100 ? this.invertPC(hubPC) : hubPC; } fromHomekitPercent(homekitPC) { return this.kClosedValue === 100 ? this.invertPC(homekitPC) : homekitPC; } // Determine whether this device uses binary open/close commands. usesBinaryState() { var _a; return ((_a = (this.currentState || this.lastState)) === null || _a === void 0 ? void 0 : _a.data.wirelessMode) === connector_hub_api_1.WirelessMode.kUniDirectional; } // Helper function which ensures that the device state received from the hub // is in the format expected by the plugin. Mutates and returns the input. sanitizeDeviceState(deviceState) { // Convert a TDBU reading into a generic device reading. if (this.deviceInfo.tdbuType !== connector_hub_helpers_1.TDBUType.kNone) { deviceState = this.tdbuToGenericState(deviceState); } // Depending on the device type, the hub may return an explicit position or // a simple open / closed state. In the former case, don't change anything. if (deviceState.data.currentPosition !== undefined) { return deviceState; } // Otherwise, convert the open / closed state into a currentPosition. if (deviceState.data.operation <= connector_hub_api_1.DeviceOpCode.kOpen) { deviceState.data.currentPosition = this.opCodeToPosition(deviceState.data.operation); return deviceState; } // If the operation is "stopped" then check if we have a target position. const target = (deviceState.data.targetPosition); if (deviceState.data.operation === connector_hub_api_1.DeviceOpCode.kStopped && target >= 0) { deviceState.data.currentPosition = target; return deviceState; } // If we reach here, then no exact position information can be deduced. log_1.Log.debug('No explicit position data in device state:', deviceState); // The blind is stopped somewhere between closed and open. We approximate // this by setting the position to half-open. The periodic update routine // will assume that the movement has completed and will set the target // position to the same value, so we will end up in a consistent state. deviceState.data.currentPosition = connector_hub_constants_1.kHalfOpenValue; return deviceState; } // Convert a TDBU device state to generic format. If the current state does // not have an entry for a particular field, merge it from the last state; // some devices may report only partial state on each refresh. tdbuToGenericState(deviceState) { var _a; for (const field in this.fields) { if (deviceState.data[this.fields[field]] !== undefined) { deviceState.data[field] = deviceState.data[this.fields[field]]; } else { deviceState.data[field] = (_a = this.lastState) === null || _a === void 0 ? void 0 : _a.data[this.fields[field]]; } } return deviceState; } // Homekit may set a percentage position for a device that only supports // binary open and close. This function is used to handle this scenario. Note // that the input targetPos is a Connector hub position. binarizeTargetPosition(hubTarget) { return hubTarget >= 50 ? 100 : 0; } // Determines the direction in which the window covering is moving, given // current position and target. getDirection(hubPos, hubTarget) { const targetOffset = Math.abs(this.kClosedValue - hubTarget); const posOffset = Math.abs(this.kClosedValue - hubPos); return posOffset < targetOffset ? connector_hub_constants_1.OperationState.OPEN_OPENING : (posOffset > targetOffset ? connector_hub_constants_1.OperationState.CLOSED_CLOSING : connector_hub_constants_1.OperationState.STOPPED); } } exports.ConnectorDeviceHandler = ConnectorDeviceHandler; //# sourceMappingURL=connectorDeviceHandler.js.map