UNPKG

homebridge-am43-blinds-bluez

Version:

A homebridge plugin to control AM43 based shade motors in HomeKit. These include the A OK and Zemismart bluetooth based motors. This plugin requires a Homebridge host that supports Bluetooth 4.0

241 lines (240 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AM43Platform = void 0; const values_1 = require("./utils/values"); //@ts-ignore const package_json_1 = require("../package.json"); const settings_1 = require("./settings"); const updateCharacteristicValue_1 = require("./utils/updateCharacteristicValue"); const ble_1 = require("./utils/ble"); const AM43Device_1 = require("./AM43Device"); const StaticVariables = { DEFAULT_HAP_INTERACTION_TIMEOUT: 1.5 * 60, DEFAULT_POLL_INTERVAL: 5 * 60, DEFAULT_SCANNING_TIMEOUT: 8, POSITION_UPDATE_INTERVAL: 2 * 60, MISSING_DEVICES_SCANNING_TIMEOUT: 5, HAP_NO_INTERACTION_GRACE_PERIOD: 5, MINIMUM_POLL_INTERVAL: 5, // The minimum required poll interval. In seconds. }; class AM43Platform { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.configJSON = config; this.log = log; this.api = api; this.packageJSON = package_json_1.default; this.accessories = []; this.log.info("Starting AM43 platform"); this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.discoveredDevices = []; this.allowedDevices = []; this.setupConfigValues(); api.on("didFinishLaunching", () => this.didFinishLaunching()); api.on("shutdown", () => this.shutdown()); } configureAccessory(accessory) { this.accessories.push(accessory); } setupConfigValues() { let configuredAllowedDevicesList = this.configJSON.allowed_devices; this.allowedDevices = configuredAllowedDevicesList; if (this.configJSON.hap_interaction_timeout != undefined && this.configJSON.hap_interaction_timeout <= 0) { this.log.warn("Automatic disconnection of AM43 devices is disabled and the connection will be kept open. This might cause higher power usage of the devices but improve responsiveness."); } if (this.configJSON.poll_interval != undefined && this.configJSON.poll_interval < StaticVariables.MINIMUM_POLL_INTERVAL) { this.log.warn(`Polling for devices is disabled due too a low poll interval. This might cause an incorrect state in HomeKit apps. Polling requires a value of ${StaticVariables.MINIMUM_POLL_INTERVAL} (seconds) or higher.`); } } async didFinishLaunching() { this.log.info("AM43-PI discover..."); // splits the HomeKit init phase from the BLE init phase because BLE // can fail in a way that can't be caught & cause the homekit devices to be lost for (let index = 0; index < this.allowedDevices.length; index++) { const blindConfig = this.allowedDevices[index]; const device = await this.initDevice(blindConfig, false); if (blindConfig.has_tilt) { const tiltDevice = await this.initDevice({ ...blindConfig.tilt_motor, has_tilt: false, }, true); device.addTiltMotor(tiltDevice); } } for (let index = 0; index < this.discoveredDevices.length; index++) { const device = this.discoveredDevices[index]; await device.firstConnect(); await device.updatePosition(); if (device.tiltMotor) await device.updateTilt(); } } async initDevice(perBlindConfig, isTiltMotor) { return new Promise((resolve, reject) => { const HomeKituuid = this.api.hap.uuid.generate(perBlindConfig.address); const existingAccessory = this.accessories.find((accessory) => accessory.UUID == HomeKituuid); const device = new AM43Device_1.AM43Device({ uuid: perBlindConfig.address, config: perBlindConfig, interactionTimeout: this.configJSON.hap_interaction_timeout, log: this.log, }); this.discoveredDevices.push(device); if (isTiltMotor) { this.log.info(`Added Motor as Tilt: ${device.description}`); resolve(device); return; } if (!existingAccessory) { this.log.info(`Found new AM43 Motor: ${device.description}`); let accessory = this.createAccessory(device, perBlindConfig.address); this.setupAccessory(accessory, device, perBlindConfig); this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [ accessory, ]); } else { this.log.info(`Found known AM43 Motor: ${device.description}`); this.setupAccessory(existingAccessory, device, perBlindConfig); this.api.updatePlatformAccessories([existingAccessory]); } resolve(device); //}) }); } async shutdown() { this.log.info("Homebridge is shutting down, disconnecting AM43 motors and saving state..."); for (let index = 0; index < this.discoveredDevices.length; index++) { const device = this.discoveredDevices[index]; await device.disconnect(); } ble_1.ble.destroyBluetooth(); } createAccessory(device, address) { const uuid = this.api.hap.uuid.generate(address); return new this.api.platformAccessory(device.name, uuid); } setupAccessory(accessory, device, deviceConfig) { this.configureWindowCoveringServiceOnAccessory(accessory, device, deviceConfig); this.configureInformationServiceOnAccessory(accessory, device); this.configureBatteryServiceOnAccessory(accessory, device); this.configurePropertiesOnAccessory(accessory); device.on("ble:position", () => { (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, this.Characteristic.PositionState, device.getDirection() || null); (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, this.Characteristic.CurrentPosition, 100 - device.position); if (device.getDirection() === values_1.DIRECTION.STOP) (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, this.Characteristic.TargetPosition, 100 - device.position); }); if (deviceConfig.has_tilt) { device.on("tilt", (tilt) => (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, deviceConfig.tilt_motor.orientation === "vertical" ? this.Characteristic.CurrentVerticalTiltAngle : this.Characteristic.CurrentHorizontalTiltAngle, tilt)); } device.on("ble:battery", (percentage) => { this.log.debug("Notifying of new battery percentage: " + percentage); (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, this.Characteristic.BatteryLevel, percentage); (0, updateCharacteristicValue_1.updateCharacteristicValue)(accessory, this.Service.WindowCovering, this.Characteristic.StatusLowBattery, percentage <= 10); }); } configurePropertiesOnAccessory(accessory) { accessory.context.lastPositionUpdate = null; accessory.context.hapInteractionTimeout = this.configJSON.hap_interaction_timeout ?? StaticVariables.DEFAULT_HAP_INTERACTION_TIMEOUT; } configureInformationServiceOnAccessory(accessory, device) { const service = accessory.getService(this.Service.AccessoryInformation) || accessory.addService(this.Service.AccessoryInformation); service .getCharacteristic(this.Characteristic.Manufacturer) .updateValue(settings_1.PLATFORM_NAME); service .getCharacteristic(this.Characteristic.Model) .updateValue(settings_1.PLUGIN_NAME); service.getCharacteristic(this.Characteristic.Name).updateValue(device.name); service .getCharacteristic(this.Characteristic.SerialNumber) .on("get", (callback) => callback(null, device.id)); service .getCharacteristic(this.Characteristic.FirmwareRevision) .on("get", (callback) => callback(null, this.packageJSON?.version || "1.1.1")); } configureBatteryServiceOnAccessory(accessory, device) { const service = accessory.getService(this.Service.Battery) || accessory.addService(this.Service.Battery); service .getCharacteristic(this.Characteristic.BatteryLevel) .on("get", async (callback) => { const percentage = await device.updateBatteryStatus(); return callback(percentage === false ? -70402 /* SERVICE_COMMUNICATION_FAILURE */ : undefined, percentage); }); service.getCharacteristic(this.Characteristic.ChargingState).updateValue(0); service .getCharacteristic(this.Characteristic.StatusLowBattery) .on("get", (callback) => callback(null, device.batteryPercentage <= 10)); } configureWindowCoveringServiceOnAccessory(accessory, device, deviceConfig) { const service = accessory.getService(this.Service.WindowCovering) || accessory.addService(this.Service.WindowCovering); service .getCharacteristic(this.Characteristic.CurrentPosition) .on("get", async (callback) => { const position = await device.updatePosition(); if (position === false) return callback(-70402 /* SERVICE_COMMUNICATION_FAILURE */); return callback(null, 100 - position); }); service .getCharacteristic(this.Characteristic.TargetPosition) .on("get", (callback) => { let targetPosition = device.targetPosition ?? device.position; targetPosition = 100 - targetPosition; this.log.debug("Reporting target position: " + targetPosition); return callback(null, targetPosition); }) .on("set", async (value, callback) => { const targetPosition = 100 - parseInt(value.toString()); const success = await device.setPosition(targetPosition); callback(success ? undefined : -70402 /* SERVICE_COMMUNICATION_FAILURE */); }); service .getCharacteristic(this.Characteristic.PositionState) .on("get", (callback) => { this.log.debug(`Getting direction... ${device.getDirection()}`); callback(null, device.getDirection()); }); if (!deviceConfig.has_tilt) return; service .getCharacteristic(deviceConfig.tilt_motor.orientation === "vertical" ? this.Characteristic.TargetVerticalTiltAngle : this.Characteristic.TargetHorizontalTiltAngle) .on("get", (callback) => { const tilt = device.targetTilt ?? device.tilt ?? 0; this.log.debug("Reporting target tilt position: " + tilt); return callback(null, tilt); }) .on("set", async (value, callback) => { const success = await device.setTilt(value.valueOf()); callback(success ? undefined : -70402 /* SERVICE_COMMUNICATION_FAILURE */); }); service .getCharacteristic(deviceConfig.tilt_motor.orientation === "vertical" ? this.Characteristic.CurrentVerticalTiltAngle : this.Characteristic.CurrentHorizontalTiltAngle) .on("get", async (callback) => { const tilt = await device.updateTilt(); if (tilt === false) return callback(-70402 /* SERVICE_COMMUNICATION_FAILURE */); return callback(null, tilt); }); } } exports.AM43Platform = AM43Platform;