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

146 lines (145 loc) 7.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const plugin_ui_utils_1 = require("@homebridge/plugin-ui-utils"); const values_1 = require("../dist/utils/values"); const commands_1 = require("../dist/utils/commands"); const ble_1 = require("../dist/utils/ble"); class AM43UiServer extends plugin_ui_utils_1.HomebridgePluginUiServer { constructor() { super(); this.currentNotificationCallback = null; this.onRequest("/scan_for_devices", (...args) => this.handleScanRequest(...args)); this.onRequest("/connect_to_device", (...args) => this.handleConnectRequest(...args)); this.onRequest("/disconnect_from_device", (...args) => this.handleDisconnectRequest(...args)); this.onRequest("/rename_device", (...args) => this.handleNameChangeRequest(...args)); this.onRequest("/auth_with_passcode", (...args) => this.handleAuthWithPasscode(...args)); this.onRequest("/move_motor", (...args) => this.handleMotorMove(...args)); this.onRequest("/adjust_limit", (...args) => this.handleAdjustLimit(...args)); // this._discoveredDevices = [] // this._connectedDevice = null // this._controlCharacteristics = {} this.ready(); } // deviceToObject(device): DeviceObject { // let { address, advertisement } = device // const { localName, rssi } = advertisement // const id = this._discoveredDevices.indexOf(device) // const isOnMac = process.platform === "darwin" // if (isOnMac) address = address || device.id // return { address, rssi, localName, id, isOnMac } // } async handleScanRequest({ scan_time }) { const rawDeviceUUIDs = await ble_1.ble.discover(); const devices = []; for (let index = 0; index < rawDeviceUUIDs.length; index++) { try { const { device } = await this.connectWithTimeout(rawDeviceUUIDs[index]); devices.push({ name: await device.getName(), address: await device.getAddress(), }); await device.disconnect(); } catch (err) { this.pushEvent("connection-failed", { uuid: rawDeviceUUIDs[index] }); } } return devices; } async connectWithTimeout(uuid, wait = 5) { return await new Promise((resolve, reject) => { const timeout = setTimeout(reject, wait * 1000); ble_1.ble .connectToDevice(uuid) .then((r) => { clearTimeout(timeout); resolve(r); }) .catch(() => { console.log("failed to connect"); reject(); }); }); } async handleConnectRequest({ device_id }) { const { device } = await this.connectWithTimeout(device_id); await this.subscribeToEvents({ device_id }); return { name: await device.getName(), address: await device.getAddress() }; } async handleDisconnectRequest({ device_id }) { if (this.currentNotificationCallback) { await ble_1.ble.unsubscribe(this.currentNotificationCallback); this.currentNotificationCallback = null; } } async subscribeToEvents({ device_id }) { const { serviceUUID, characteristicUUID } = await ble_1.ble.getServiceAndCharacteristicUUIDs(device_id); if (this.currentNotificationCallback) { await ble_1.ble.unsubscribe(this.currentNotificationCallback); this.currentNotificationCallback = null; } this.currentNotificationCallback = (data) => this.sendNotification({ device_id, data }); await ble_1.ble.subscribe(device_id, serviceUUID, characteristicUUID, this.currentNotificationCallback); } sendNotification({ device_id, data }) { const notificationToEvent = { "9a35015a31": "name-change-success", "9a1701a5ce": "auth-error", "9a17015a31": "auth-success", "9a22015a31": "limit-set-success", "9a22015b31": "limit-save-success", "9a22015c31": "limit-cancel-success", }; const eventName = notificationToEvent[data.toString("hex")]; if (eventName) this.pushEvent(eventName, { device_id }); } async handleNameChangeRequest({ device_id, new_name, }) { const { serviceUUID, characteristicUUID } = await ble_1.ble.getServiceAndCharacteristicUUIDs(device_id); const data = new_name .split("") .map((letter) => letter.charCodeAt(0)) .map((value) => (value > 254 ? "?".charCodeAt(0) : value)); await ble_1.ble.writeValue(device_id, serviceUUID, characteristicUUID, (0, commands_1.buildCommandBuffer)(values_1.DeviceValues.AM43_COMMAND_CHANGE_NAME, data)); return this.handleConnectRequest({ device_id }); } async handleAuthWithPasscode({ device_id, passcode, }) { const { serviceUUID, characteristicUUID } = await ble_1.ble.getServiceAndCharacteristicUUIDs(device_id); const data16Bit = new Uint16Array([parseInt(passcode)]); const data = new Uint8Array(data16Bit.buffer).reverse(); await ble_1.ble.writeValue(device_id, serviceUUID, characteristicUUID, (0, commands_1.buildCommandBuffer)(values_1.DeviceValues.AM43_COMMAND_PASSCODE, data)); return "OK"; } async handleAdjustLimit({ device_id, openOrClose, phase }) { const { serviceUUID, characteristicUUID } = await ble_1.ble.getServiceAndCharacteristicUUIDs(device_id); const COMMANDS = { OPENED: { SET: Uint8Array.from([0x00, 0x01, 0x00]), SAVE: Uint8Array.from([0x20, 0x01, 0x00]), CANCEL: Uint8Array.from([0x40, 0x01, 0x00]), }, CLOSED: { SET: Uint8Array.from([0x00, 0x02, 0x00]), SAVE: Uint8Array.from([0x20, 0x02, 0x00]), CANCEL: Uint8Array.from([0x40, 0x01, 0x00]), }, }; await ble_1.ble.writeValue(device_id, serviceUUID, characteristicUUID, (0, commands_1.buildCommandBuffer)(values_1.DeviceValues.AM43_COMMAND_SET_LIMIT, COMMANDS[openOrClose][phase])); } async handleMotorMove({ device_id, command }) { const { serviceUUID, characteristicUUID } = await ble_1.ble.getServiceAndCharacteristicUUIDs(device_id); const COMMAND_TO_DATA = { OPEN: values_1.DeviceValues.AM43_MOVE_OPEN, CLOSE: values_1.DeviceValues.AM43_MOVE_CLOSE, STOP: values_1.DeviceValues.AM43_MOVE_STOP, }; console.log((0, commands_1.buildCommandBuffer)(values_1.DeviceValues.AM43_COMMAND_ID_SET_MOVE, COMMAND_TO_DATA[command])); await ble_1.ble.writeValue(device_id, serviceUUID, characteristicUUID, (0, commands_1.buildCommandBuffer)(values_1.DeviceValues.AM43_COMMAND_ID_SET_MOVE, [ COMMAND_TO_DATA[command], ])); } } ; (() => { return new AM43UiServer(); })();