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
JavaScript
;
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();
})();