UNPKG

homebridge-aplvibe

Version:

Homebridge plugin for SleepMe devices using APL as core logic engine

282 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.APLVibePlatform = void 0; const types_1 = require("./types"); const apl_engine_1 = require("./apl-engine"); class APLVibePlatform { log; config; api; Service; Characteristic; accessories = []; aplEngine; pollingInterval; constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.log.debug('Initializing APLVibe Platform'); // Initialize Homebridge services this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; // Initialize APL engine - this is where all business logic resides this.aplEngine = new apl_engine_1.APLEngineImpl(this.log); // Set up APL event handlers for state changes this.aplEngine.onEvent('stateChange', this.handleStateChange.bind(this)); this.api.on('didFinishLaunching', () => { this.log.debug('Executed didFinishLaunching callback'); this.initializePlatform(); }); } /** * Initialize platform by delegating to APL engine * This demonstrates the delegation pattern - TypeScript handles Homebridge integration, * APL handles all business logic */ async initializePlatform() { try { // Initialize APL engine with configuration const configJson = JSON.stringify(this.config); const initResult = await this.aplEngine.init(configJson); if (!initResult.success) { this.log.error('Failed to initialize APL engine:', initResult.error); return; } // Discover devices via APL await this.discoverDevices(); // Start polling for device updates this.startPolling(); this.log.info('APLVibe platform initialized successfully'); } catch (error) { this.log.error('Platform initialization failed:', error); } } /** * Device discovery delegates to APL engine * APL handles device management logic, TypeScript handles Homebridge accessory creation */ async discoverDevices() { try { const devicesResult = await this.aplEngine.getDevices(); if (!devicesResult.success) { this.log.error('Failed to get devices from APL:', devicesResult.error); return; } const devices = JSON.parse(devicesResult.data); for (const device of devices) { await this.addAccessory(device); } } catch (error) { this.log.error('Device discovery failed:', error); } } /** * Add accessory - minimal Homebridge setup, delegates state management to APL */ async addAccessory(device) { const uuid = this.api.hap.uuid.generate(device.id); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { this.log.info('Restoring existing accessory from cache:', device.name); // Restore accessory via APL await this.aplEngine.restoreAccessory(uuid); this.setupAccessoryServices(existingAccessory, device); } else { this.log.info('Adding new accessory:', device.name); const accessory = new this.api.platformAccessory(device.name, uuid); accessory.context.device = device; this.setupAccessoryServices(accessory, device); this.api.registerPlatformAccessories('homebridge-aplvibe', 'APLVibe', [accessory]); this.accessories.push(accessory); } } /** * Setup accessory services - creates Homebridge services but delegates all logic to APL */ setupAccessoryServices(accessory, device) { // Device Information Service const informationService = accessory.getService(this.Service.AccessoryInformation) || accessory.addService(this.Service.AccessoryInformation); informationService .setCharacteristic(this.Characteristic.Manufacturer, 'SleepMe') .setCharacteristic(this.Characteristic.Model, device.model) .setCharacteristic(this.Characteristic.SerialNumber, device.id); // Thermostat Service - all state management delegated to APL const thermostatService = accessory.getService(this.Service.Thermostat) || accessory.addService(this.Service.Thermostat); // Current Temperature - read-only, managed by APL thermostatService.getCharacteristic(this.Characteristic.CurrentTemperature) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.CURRENT_TEMPERATURE); return result.success ? result.data : 20; }); // Target Temperature - read/write, validation and conversion handled by APL thermostatService.getCharacteristic(this.Characteristic.TargetTemperature) .setProps({ minValue: this.config.unit === 'C' ? 13 : 55, maxValue: this.config.unit === 'C' ? 46 : 115, minStep: 0.1, }) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.TARGET_TEMPERATURE); return result.success ? result.data : 20; }) .onSet(async (value) => { // Validate temperature via APL const validationResult = await this.aplEngine.validateTemperature(value, this.config.unit); if (!validationResult.success || !validationResult.data) { throw new this.api.hap.HapStatusError(-70410 /* this.api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST */); } // Queue temperature change request via APL const request = { id: `temp_${Date.now()}`, priority: 'HIGH', timestamp: Date.now(), action: 'setTemperature', device_id: device.id, data: { temperature: value } }; await this.aplEngine.queueRequest(request); await this.aplEngine.setState(device.id, types_1.Characteristics.TARGET_TEMPERATURE, value); }); // Heating/Cooling State - managed by APL thermostatService.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.HEATING_COOLING_STATE); return result.success ? result.data : this.Characteristic.CurrentHeatingCoolingState.OFF; }); thermostatService.getCharacteristic(this.Characteristic.TargetHeatingCoolingState) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.TARGET_HEATING_COOLING_STATE); return result.success ? result.data : this.Characteristic.TargetHeatingCoolingState.AUTO; }) .onSet(async (value) => { await this.aplEngine.setState(device.id, types_1.Characteristics.TARGET_HEATING_COOLING_STATE, value); }); // Temperature Display Units thermostatService.getCharacteristic(this.Characteristic.TemperatureDisplayUnits) .onGet(() => { return this.config.unit === 'C' ? this.Characteristic.TemperatureDisplayUnits.CELSIUS : this.Characteristic.TemperatureDisplayUnits.FAHRENHEIT; }) .onSet((value) => { // Unit changes handled by APL for temperature conversions this.log.debug('Temperature unit changed to:', value); }); // Switch Service for power control - delegates to APL const switchService = accessory.getService(this.Service.Switch) || accessory.addService(this.Service.Switch, 'Power'); switchService.getCharacteristic(this.Characteristic.On) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.POWER_STATE); return result.success ? result.data : false; }) .onSet(async (value) => { const request = { id: `power_${Date.now()}`, priority: 'CRITICAL', timestamp: Date.now(), action: 'setPower', device_id: device.id, data: { power: value } }; await this.aplEngine.queueRequest(request); await this.aplEngine.setState(device.id, types_1.Characteristics.POWER_STATE, value); }); // Battery Service for water level monitoring const batteryService = accessory.getService(this.Service.Battery) || accessory.addService(this.Service.Battery, 'Water Level'); batteryService.getCharacteristic(this.Characteristic.BatteryLevel) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.WATER_LEVEL); return result.success ? result.data : 100; }); batteryService.getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(async () => { const result = await this.aplEngine.getState(device.id, types_1.Characteristics.WATER_LEVEL); const waterLevel = result.success ? result.data : 100; return waterLevel < 20 ? this.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; }); } /** * Handle state changes from APL engine * APL emits events when state changes, TypeScript translates to Homebridge */ handleStateChange(data) { const accessory = this.accessories.find(acc => acc.context.device.id === data.deviceId); if (!accessory) { return; } // Map APL characteristics to Homebridge characteristics const characteristicMap = { [types_1.Characteristics.CURRENT_TEMPERATURE]: this.Characteristic.CurrentTemperature, [types_1.Characteristics.TARGET_TEMPERATURE]: this.Characteristic.TargetTemperature, [types_1.Characteristics.POWER_STATE]: this.Characteristic.On, [types_1.Characteristics.WATER_LEVEL]: this.Characteristic.BatteryLevel, }; const homebridgeChar = characteristicMap[data.characteristic]; if (!homebridgeChar) { return; } // Update appropriate service let service; if (data.characteristic === types_1.Characteristics.POWER_STATE) { service = accessory.getService(this.Service.Switch); } else if (data.characteristic === types_1.Characteristics.WATER_LEVEL) { service = accessory.getService(this.Service.Battery); } else { service = accessory.getService(this.Service.Thermostat); } if (service) { service.updateCharacteristic(homebridgeChar, data.value); } } /** * Start polling for device updates - delegates to APL for scheduling and rate limiting */ startPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); } this.pollingInterval = setInterval(async () => { try { // Process any queued requests via APL await this.aplEngine.processQueue(); // Apply any scheduled temperature changes via APL for (const accessory of this.accessories) { const deviceId = accessory.context.device.id; await this.aplEngine.applySchedule(deviceId); } } catch (error) { this.log.error('Polling error:', error); } }, (this.config.pollingInterval || 90) * 1000); } configureAccessory(accessory) { this.log.info('Loading accessory from cache:', accessory.displayName); this.accessories.push(accessory); } /** * Cleanup - destroy APL engine and clear intervals */ destroy() { if (this.pollingInterval) { clearInterval(this.pollingInterval); } if (this.aplEngine) { this.aplEngine.destroy(); } } } exports.APLVibePlatform = APLVibePlatform; //# sourceMappingURL=platform.js.map