UNPKG

homebridge-soma-shades

Version:
409 lines 22.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SOMADevice = void 0; // device information const INFO_SERVICE_UUID = '180a'; const INFO_MANUFACTURER_CHARACTERISTIC_UUID = '2a29'; const INFO_SERIAL_CHARACTERISTIC_UUID = '2a25'; const INFO_HARDWARE_REVISION_CHARACTERISTIC_UUID = '2a27'; const INFO_FIRMWARE_REVISION_CHARACTERISTIC_UUID = '2a26'; const INFO_SOFTWARE_REVISION_CHARACTERISTIC_UUID = '2a28'; // battery const BATTERY_SERVICE_UUID = '180f'; const BATTERY_LEVEL_CHARACTERISTIC_UUID = '2a19'; // motor control const MOTOR_SERVICE_UUID = '00001861b87f490c92cb11ba5ea5167c'; const MOTOR_STATE_CHARACTERISTIC_UUID = '00001525b87f490c92cb11ba5ea5167c'; const MOTOR_TARGET_CHARACTERISTIC_UUID = '00001526b87f490c92cb11ba5ea5167c'; const MOTOR_CONTROL_CHARACTERISTIC_UUID = '00001530b87f490c92cb11ba5ea5167c'; //const MOTOR_MOVE_UP = 0x69; //const MOTOR_STOP = 0x50; //const MOTOR_MOVE_DOWN = 0x96; // timeout const DEFAULT_TIMEOUT = 10000; // 10s class SOMADevice { constructor(log, peripheral) { this.connected = false; this.initialized = false; this.log = log; this.peripheral = peripheral; this.characteristics = { battery: null, motor: { state: null, target: null, control: null, }, }; } initialize() { if (!this.connected) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is not connected`); return this.connect().then(() => this.initialize()); } if (!this.initialized) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is not initialized`); return this.getCharacteristics().then(() => this.initialize()); } this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} finished initialize`); return new Promise((resolve) => resolve(true)); } connect() { // Check the connection state const state = this.peripheral.state; if (state === 'connected') { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is already connected`); this.connected = true; return new Promise((resolve) => resolve()); } this.peripheral.once('disconnect', () => { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} disconnected`); this.peripheral.removeAllListeners(); this.connected = false; this.initialized = false; this.characteristics = { battery: null, motor: { state: null, target: null, control: null, }, }; }); return Promise.race([ this.peripheral.connectAsync(), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then(() => { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is connected`); this.connected = true; return new Promise(resolve => resolve()); }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to connect ${error}`); return new Promise((_, reject) => reject(new Error(error))); }); } async getInfomationCharacteristics() { if (!this.connected) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is not connected to get info characteristics`); return this.initialize().then(() => this.getInfomationCharacteristics()); } const deviceInfo = { manufacturer: 'Default Manufacturer', serial: 'Default Serial', hardwareRevision: 'Default Hardware Revision', firmwareRevision: 'Default Firmware Revision', softwareRevision: 'Default Software Revision', }; this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is getting information services`); const services = await Promise.race([ this.peripheral.discoverServicesAsync([INFO_SERVICE_UUID]), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((ret) => { if (ret && Array.isArray(ret)) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is discovering info services as array`); return ret; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover info services as array`); return []; }).catch((error) => { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover info services: ${error}`); return []; }); if (services.length !== 1 || services[0].uuid !== INFO_SERVICE_UUID) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid info services`); return deviceInfo; } this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is getting information characteristics`); const characteristics = await Promise.race([ services[0].discoverCharacteristicsAsync(), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((ret) => { if (ret && Array.isArray(ret)) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is discovering info characteristics as array`); return ret; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover info characteristics as array`); return []; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover info characteristics: ${error}`); return []; }); if (characteristics.length !== 5) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid info characteristics`); return deviceInfo; } for (const characteristic of characteristics) { if (characteristic.uuid === INFO_MANUFACTURER_CHARACTERISTIC_UUID) { deviceInfo.manufacturer = await characteristic.readAsync() .then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading INFO_MANUFACTURER_CHARACTERISTIC_UUID as buffer`); return buffer.toString(); } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_MANUFACTURER_CHARACTERISTIC_UUID as buffer`); return deviceInfo.manufacturer; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_MANUFACTURER_CHARACTERISTIC_UUID: ${error}`); return deviceInfo.manufacturer; }); continue; } if (characteristic.uuid === INFO_SERIAL_CHARACTERISTIC_UUID) { deviceInfo.serial = await characteristic.readAsync() .then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading INFO_SERIAL_CHARACTERISTIC_UUID as buffer`); return buffer.toString(); } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_SERIAL_CHARACTERISTIC_UUID as buffer`); return deviceInfo.manufacturer; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_SERIAL_CHARACTERISTIC_UUID: ${error}`); return deviceInfo.manufacturer; }); continue; } if (characteristic.uuid === INFO_HARDWARE_REVISION_CHARACTERISTIC_UUID) { deviceInfo.hardwareRevision = await characteristic.readAsync() .then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading INFO_HARDWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return buffer.toString(); } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_HARDWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return deviceInfo.manufacturer; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_HARDWARE_REVISION_CHARACTERISTIC_UUID: ${error}`); return deviceInfo.manufacturer; }); continue; } if (characteristic.uuid === INFO_FIRMWARE_REVISION_CHARACTERISTIC_UUID) { deviceInfo.firmwareRevision = await characteristic.readAsync() .then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading INFO_FIRMWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return buffer.toString(); } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_FIRMWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return deviceInfo.manufacturer; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_FIRMWARE_REVISION_CHARACTERISTIC_UUID: ${error}`); return deviceInfo.manufacturer; }); continue; } if (characteristic.uuid === INFO_SOFTWARE_REVISION_CHARACTERISTIC_UUID) { deviceInfo.softwareRevision = await characteristic.readAsync() .then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading INFO_SOFTWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return buffer.toString(); } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_SOFTWARE_REVISION_CHARACTERISTIC_UUID as buffer`); return deviceInfo.manufacturer; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read INFO_SOFTWARE_REVISION_CHARACTERISTIC_UUID: ${error}`); return deviceInfo.manufacturer; }); continue; } } return deviceInfo; } async getCharacteristics() { if (!this.connected) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is not connected to get characteristics`); return this.connect().then(() => this.getCharacteristics()); } if (this.initialized) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is initialized already`); return new Promise(resolve => resolve()); } this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is getting services`); const services = await Promise.race([ this.peripheral.discoverServicesAsync([BATTERY_SERVICE_UUID, MOTOR_SERVICE_UUID]), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((ret) => { if (ret && Array.isArray(ret)) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is discovering services as array`); return ret; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover services as array`); return []; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover services: ${error}`); return []; }); if (services.length !== 2 || services[0].uuid !== BATTERY_SERVICE_UUID || services[1].uuid !== MOTOR_SERVICE_UUID) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid services`); return new Promise((_, reject) => reject(new Error('invalid services'))); } this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is getting battery characteristics`); const batteryCharacteristics = await Promise.race([ services[0].discoverCharacteristicsAsync([BATTERY_LEVEL_CHARACTERISTIC_UUID]), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((ret) => { if (ret && Array.isArray(ret)) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is discovering battery characteristics as array`); return ret; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover battery characteristics as array`); return []; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover battery characteristics: ${error}`); return []; }); if (batteryCharacteristics.length !== 1 || batteryCharacteristics[0].uuid !== BATTERY_LEVEL_CHARACTERISTIC_UUID) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid battery characteristics`); return new Promise((_, reject) => reject(new Error('invalid battery characteristics'))); } this.characteristics.battery = batteryCharacteristics[0]; this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is getting motor characteristics`); const motorCharacteristics = await Promise.race([ services[1].discoverCharacteristicsAsync([MOTOR_STATE_CHARACTERISTIC_UUID, MOTOR_TARGET_CHARACTERISTIC_UUID, MOTOR_CONTROL_CHARACTERISTIC_UUID]), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((ret) => { if (ret && Array.isArray(ret)) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is discovering motor characteristics as array`); return ret; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover motor characteristics as array`); return []; }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to discover motor characteristics: ${error}`); return []; }); if (motorCharacteristics.length !== 3) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid motor characteristics`); return new Promise((_, reject) => reject(new Error('invalid motor characteristics'))); } for (const characteristic of motorCharacteristics) { switch (characteristic.uuid) { case MOTOR_STATE_CHARACTERISTIC_UUID: this.characteristics.motor.state = characteristic; break; case MOTOR_TARGET_CHARACTERISTIC_UUID: this.characteristics.motor.target = characteristic; break; case MOTOR_CONTROL_CHARACTERISTIC_UUID: this.characteristics.motor.control = characteristic; break; default: break; } } this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} has successfully get all characteristics`); this.initialized = true; return new Promise(resolve => resolve()); } getBatteryLevel() { if (!this.connected) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not connected for getBatteryLevel`); return this.initialize().then(() => this.getBatteryLevel()); } if (!this.initialized) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not initialized for getBatteryLevel`); return this.getCharacteristics().then(() => this.getBatteryLevel()); } if (!this.characteristics.battery) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid battery characteristic`); return new Promise((_, reject) => reject(new Error('invalid battery characteristic'))); } return Promise.race([ this.characteristics.battery.readAsync(), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading battery as buffer`); return buffer[0]; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read battery as buffer`); return new Promise((_, reject) => reject(new Error('failed to read as buffer'))); }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read battery: ${error}`); return new Promise((_, reject) => reject(new Error(error))); }); } getCurrentPosition() { if (!this.connected) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not connected for getCurrentPosition`); return this.initialize().then(() => this.getCurrentPosition()); } if (!this.initialized) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not initialized for getCurrentPosition`); return this.getCharacteristics().then(() => this.getCurrentPosition()); } if (!this.characteristics.motor.state) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid current position characteristic`); return new Promise((_, reject) => reject(new Error('invalid current position characteristic'))); } return Promise.race([ this.characteristics.motor.state.readAsync(), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading current position as buffer`); return buffer[0]; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read current position as buffer`); return new Promise((_, reject) => reject(new Error('failed to read as buffer'))); }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read current position: ${error}`); return new Promise((_, reject) => reject(new Error(error))); }); } async getTargetPosition() { if (!this.connected) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not connected for getTargetPosition`); return this.initialize().then(() => this.getTargetPosition()); } if (!this.initialized) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not initialized for getTargetPosition`); return this.getCharacteristics().then(() => this.getTargetPosition()); } if (!this.characteristics.motor.target) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid target position characteristic`); return new Promise((_, reject) => reject(new Error('invalid target position characteristic'))); } return Promise.race([ this.characteristics.motor.target.readAsync(), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then((buffer) => { if (buffer instanceof Buffer) { this.log.debug(`peripheral ${this.peripheral.id.toLowerCase()} is reading target position as buffer`); return buffer[0]; } this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read target position as buffer`); return new Promise((_, reject) => reject(new Error('failed to read as buffer'))); }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to read target position: ${error}`); return new Promise((_, reject) => reject(new Error(error))); }); } setTargetPosition(position) { if (!this.connected) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not connected for setTargetPosition`); return this.initialize().then(() => this.setTargetPosition(position)); } if (!this.initialized) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} is not initialized for setTargetPosition`); return this.getCharacteristics().then(() => this.setTargetPosition(position)); } if (!this.characteristics.motor.target) { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} has invalid target position characteristic`); return new Promise((_, reject) => reject(new Error('invalid target position characteristic'))); } return Promise.race([ this.characteristics.motor.target.writeAsync(Buffer.from([position]), false), new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), DEFAULT_TIMEOUT)), ]).then(() => { return new Promise(resolve => resolve()); }).catch((error) => { this.log.error(`peripheral ${this.peripheral.id.toLowerCase()} failed to set target position: ${error}`); return new Promise((_, reject) => reject(new Error(error))); }); } } exports.SOMADevice = SOMADevice; //# sourceMappingURL=somaDevice.js.map