UNPKG

homebridge-ir-amplifier

Version:

Homebridge plugin for IR amplifier control with OCR volume detection and CEC support for Apple TV integration

761 lines 39.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IRAmplifierAccessory = void 0; // import { CECController } from './cecController'; // Désactivé - utilise le service CEC externe class IRAmplifierAccessory { constructor(platform, accessory, broadlinkController, tplinkController, ocrController, cecController) { this.platform = platform; this.accessory = accessory; this.broadlinkController = broadlinkController; this.tplinkController = tplinkController; this.ocrController = ocrController; this.cecController = cecController; this.currentVolume = 50; this.targetVolume = 50; this.isOn = false; this.isSourceCorrect = false; this.lastOCRCheck = 0; this.volumeSyncInProgress = false; // Gestion de l'état temporaire pour TP-Link this.pendingStateChange = false; this.lastStateChangeTime = 0; this.stateChangeTimeout = null; this.externalCECWatcher = null; this.log = platform.log; this.api = platform.api; this.Service = platform.Service; this.Characteristic = platform.Characteristic; this.accessory.getService(this.Service.AccessoryInformation) .setCharacteristic(this.Characteristic.Manufacturer, 'IR Amplifier') .setCharacteristic(this.Characteristic.Model, 'IR Amplifier') .setCharacteristic(this.Characteristic.SerialNumber, 'IR-AMP-001'); // Main switch service this.service = this.accessory.getService(this.Service.Switch) || this.accessory.addService(this.Service.Switch); this.service.setCharacteristic(this.Characteristic.Name, 'Amplifier Power'); this.service.getCharacteristic(this.Characteristic.On) .onSet(this.setPowerState.bind(this)) .onGet(this.getPowerState.bind(this)); // Speaker service for volume control this.speakerService = this.accessory.getService(this.Service.Speaker) || this.accessory.addService(this.Service.Speaker); this.speakerService.setCharacteristic(this.Characteristic.Name, 'Amplifier Volume'); this.speakerService.getCharacteristic(this.Characteristic.Volume) .onSet(this.setVolume.bind(this)) .onGet(this.getVolume.bind(this)); // Volume service for fine control this.volumeService = this.accessory.getService(this.Service.Lightbulb) || this.accessory.addService(this.Service.Lightbulb); this.volumeService.setCharacteristic(this.Characteristic.Name, 'Volume Control'); this.volumeService.setCharacteristic(this.Characteristic.Brightness, this.currentVolume); this.volumeService.getCharacteristic(this.Characteristic.Brightness) .onSet(this.setVolumeBrightness.bind(this)) .onGet(this.getVolumeBrightness.bind(this)); this.initializeMonitoring(); this.initializeCECCallbacks(); } async setPowerState(value) { const boolValue = value; this.log.info('=== SET POWER STATE CALLED ==='); this.log.info('Requested state:', boolValue); this.log.info('Current accessory state:', this.isOn); this.log.info('Pending state change:', this.pendingStateChange); // Vérifier l'état actuel de TP-Link const currentTpLinkState = await this.tplinkController.getInUseState(); this.log.info('Current TP-Link state (inUse):', currentTpLinkState); if (boolValue !== currentTpLinkState) { this.log.info('State change needed - TP-Link:', currentTpLinkState, '→ Requested:', boolValue); // Marquer qu'un changement d'état est en cours this.pendingStateChange = true; this.lastStateChangeTime = Date.now(); this.log.info('Pending state change flag set to TRUE'); // Mettre à jour immédiatement l'état dans HomeKit (état temporaire) this.isOn = boolValue; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('HomeKit state set to temporary state:', this.isOn); // Envoyer la commande IR via Broadlink this.log.info('Sending IR command to change amplifier state'); const success = boolValue ? await this.handlePowerOnWithEnhancements() : await this.broadlinkController.powerOff(); if (success) { this.log.info('IR command sent successfully, scheduling verification'); // Synchroniser l'état CEC avec le nouvel état this.syncCECState(boolValue); // Programmer une vérification différée de l'état TP-Link this.scheduleStateVerification(boolValue); // Si on allume l'amplificateur, initialiser le volume if (boolValue) { this.log.info('Amplifier power ON - starting volume initialization...'); // Attendre un peu que l'amplificateur s'allume avant d'initialiser le volume setTimeout(() => { this.initializeVolumeAfterPowerOn(); }, 3000); // 3 secondes après l'allumage } } else { this.log.error('Failed to send IR command'); // Remettre l'état précédent en cas d'échec this.isOn = currentTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.pendingStateChange = false; this.log.info('State reverted due to IR command failure'); } } else { this.log.info('No state change needed - already matches'); // S'assurer que HomeKit reflète l'état réel this.isOn = currentTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); } this.log.info('=== SET POWER STATE COMPLETED ==='); } scheduleStateVerification(expectedState) { // Annuler la vérification précédente si elle existe if (this.stateChangeTimeout) { clearTimeout(this.stateChangeTimeout); } this.log.info('Scheduling state verification in 15 seconds for expected state:', expectedState); // Programmer la vérification après 15 secondes (délai pour que TP-Link se mette à jour) this.stateChangeTimeout = setTimeout(async () => { await this.verifyStateChange(expectedState); }, 15000); } async verifyStateChange(expectedState) { try { this.log.info('=== VERIFYING STATE CHANGE ==='); this.log.info('Expected state:', expectedState); this.log.info('Current accessory state:', this.isOn); this.log.info('Pending state change:', this.pendingStateChange); // Vérifier l'état actuel de TP-Link const actualTpLinkState = await this.tplinkController.getInUseState(); this.log.info('TP-Link state after delay:', actualTpLinkState, 'Expected:', expectedState); if (actualTpLinkState === expectedState) { // L'état correspond, confirmer this.log.info('State change confirmed - TP-Link matches expected state'); this.isOn = actualTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('HomeKit state confirmed to:', this.isOn); // Synchroniser l'état CEC avec l'état confirmé this.syncCECState(this.isOn); } else { // L'état ne correspond pas, corriger this.log.warn('State mismatch detected - TP-Link:', actualTpLinkState, 'Expected:', expectedState); this.log.info('Correcting HomeKit state to match TP-Link reality'); this.isOn = actualTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('HomeKit state corrected to:', this.isOn); // Synchroniser l'état CEC avec l'état corrigé this.syncCECState(this.isOn); } // Marquer que le changement d'état est terminé this.pendingStateChange = false; this.stateChangeTimeout = null; this.log.info('State verification completed, pending flag cleared'); } catch (error) { this.log.error('Error during state verification:', error); this.pendingStateChange = false; this.stateChangeTimeout = null; } } async getPowerState() { this.log.debug('=== GET POWER STATE CALLED ==='); this.log.debug('Pending state change:', this.pendingStateChange); // Check TP-Link power consumption to determine if amplifier is on const inUse = await this.tplinkController.getInUseState(); this.log.debug('TP-Link inUse:', inUse, 'Current accessory state:', this.isOn); // Ne pas mettre à jour l'état si un changement est en cours if (!this.pendingStateChange) { this.isOn = inUse; this.log.debug('Accessory state updated to:', this.isOn); } else { this.log.debug('Skipping state update - pending state change in progress'); } this.log.debug('Returning power state:', this.isOn); return this.isOn; } async setVolume(value) { const numValue = value; this.log.info('Setting volume to:', value); this.targetVolume = numValue; if (this.volumeSyncInProgress) { this.log.debug('Volume sync in progress, skipping'); return; } await this.syncVolumeToTarget(); // CEC géré par le service externe - pas d'action nécessaire this.log.debug('Volume changed - CEC handled by external service'); } async getVolume() { // Check OCR for current volume if enough time has passed const now = Date.now(); if (now - this.lastOCRCheck > 10000) { // Check every 10 seconds await this.checkOCRVolume(); } return this.currentVolume; } async setVolumeBrightness(value) { const numValue = value; // Map brightness (0-100) to volume (0-100) await this.setVolume(numValue); } async getVolumeBrightness() { return this.currentVolume; } async syncVolumeToTarget() { if (this.volumeSyncInProgress) return; this.volumeSyncInProgress = true; this.log.info('Syncing volume from', this.currentVolume, 'to', this.targetVolume); try { const difference = this.targetVolume - this.currentVolume; const steps = Math.abs(difference); for (let i = 0; i < steps; i++) { if (difference > 0) { await this.broadlinkController.volumeUp(); this.currentVolume++; } else { await this.broadlinkController.volumeDown(); this.currentVolume--; } // Update services this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); // Small delay between commands await new Promise(resolve => setTimeout(resolve, 200)); } this.log.info('Volume sync completed. Current volume:', this.currentVolume); } catch (error) { this.log.error('Error during volume sync:', error); } finally { this.volumeSyncInProgress = false; } } async checkOCRVolume() { try { const result = await this.ocrController.getVolumeAndSource(); this.lastOCRCheck = Date.now(); if (result.volume !== null && result.confidence > 0.7) { const ocrVolume = result.volume; const difference = Math.abs(ocrVolume - this.currentVolume); if (difference > 5) { // Significant difference this.log.info('OCR detected volume mismatch. OCR:', ocrVolume, 'Current:', this.currentVolume); this.currentVolume = ocrVolume; // Update services this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); } } if (result.source !== null) { const isVideo2 = result.source.toLowerCase().includes('video 2') || result.source.toLowerCase().includes('video2'); this.isSourceCorrect = isVideo2; if (!isVideo2) { this.log.warn('Source is not VIDEO 2. Current source:', result.source); // Optionally send source toggle command // await this.broadlinkController.sourceToggle(); } } } catch (error) { this.log.error('Error checking OCR:', error); } } initializeMonitoring() { // Monitor TP-Link power state this.tplinkController.startPowerMonitoring(async (inUse) => { this.log.debug('=== TP-LINK MONITORING CALLBACK ==='); this.log.debug('TP-Link inUse:', inUse, 'Accessory state:', this.isOn); this.log.debug('Pending state change:', this.pendingStateChange); if (inUse !== this.isOn) { this.log.info('TP-Link: Power state changed:', this.isOn, '→', inUse); // Ne pas interférer si un changement d'état est en cours if (this.pendingStateChange) { this.log.info('TP-Link: Ignoring state change - pending state change in progress'); return; } this.isOn = inUse; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('TP-Link: Updated HomeKit power state to:', this.isOn); // Délai avant de synchroniser CEC pour éviter les conflits await new Promise(resolve => setTimeout(resolve, 1000)); // Synchroniser l'état CEC avec TP-Link (local seulement) this.log.debug('TP-Link: CEC state synchronized locally with TP-Link:', this.isOn); } else { this.log.debug('TP-Link: No state change needed'); // Même si pas de changement d'état, s'assurer que CEC est synchronisé localement this.log.debug('TP-Link: CEC state synchronized locally with current state:', this.isOn); } }); // Start periodic OCR checking this.ocrController.startPeriodicCheck((result) => { this.checkOCRVolume(); }); // Start periodic state verification this.startPeriodicStateVerification(); // Initial state check and synchronization this.initializeStateSynchronization(); } startPeriodicStateVerification() { // Vérifier l'état toutes les 15 secondes pour s'assurer de la cohérence setInterval(async () => { try { this.log.debug('=== PERIODIC VERIFICATION ==='); this.log.debug('Pending state change:', this.pendingStateChange); this.log.debug('Current accessory state:', this.isOn); // Ne pas vérifier si un changement d'état est en cours if (this.pendingStateChange) { this.log.debug('Skipping periodic verification - state change in progress'); return; } const tpLinkState = await this.tplinkController.getInUseState(); this.log.debug('TP-Link state:', tpLinkState, 'Accessory state:', this.isOn); if (tpLinkState !== this.isOn) { this.log.info('Periodic check - State mismatch detected - TP-Link:', tpLinkState, 'Accessory:', this.isOn); this.log.info('Correcting accessory state to match TP-Link'); // Corriger l'état de l'accessoire this.isOn = tpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('Accessory state corrected to:', this.isOn); // CEC synchronisé localement avec l'état corrigé this.log.debug('CEC: State corrected locally:', this.isOn); } else { this.log.debug('Periodic check - States match, no correction needed'); } } catch (error) { this.log.error('Error during periodic state verification:', error); } }, 15000); // Vérifier toutes les 15 secondes } async initializeStateSynchronization() { this.log.info('Initializing state synchronization between TP-Link and CEC...'); try { // Récupérer l'état actuel de TP-Link (source de vérité) const tpLinkState = await this.tplinkController.getInUseState(); this.log.info('Initial TP-Link state (inUse):', tpLinkState); // Mettre à jour l'état local de l'accessoire avec l'état réel de TP-Link this.isOn = tpLinkState; this.log.info('Accessory state synchronized with TP-Link:', this.isOn); // Mettre à jour HomeKit avec l'état réel (ignorer le cache) this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('HomeKit state updated to real TP-Link state:', this.isOn); // CEC géré par le service externe this.log.info('CEC handled by external service - no internal CEC state'); // Délai avant la synchronisation initiale CEC await new Promise(resolve => setTimeout(resolve, 2000)); // Synchroniser l'état CEC avec l'état initial this.syncCECState(this.isOn); this.log.info('CEC: Initial state synchronized with CEC bus'); // Récupérer le volume initial await this.getVolume(); this.log.info('State synchronization completed - Accessory:', this.isOn, 'TP-Link:', tpLinkState, 'CEC: external service'); } catch (error) { this.log.error('Error during state synchronization:', error); } } initializeCECCallbacks() { // CEC géré par le service externe - pas de callbacks internes this.log.info('CEC callbacks handled by external service'); // Démarrer le watcher pour le service CEC externe this.startExternalCECWatcher(); } startExternalCECWatcher() { // Surveiller le fichier de communication avec le service CEC externe const fs = require('fs'); const path = '/var/lib/homebridge/cec-to-homebridge.json'; this.log.info('Starting external CEC service watcher...'); const watcher = setInterval(() => { try { if (fs.existsSync(path)) { const data = fs.readFileSync(path, 'utf8').trim(); // Vérifier que le fichier n'est pas vide if (!data) { return; // Fichier vide, ignorer } // Vérifier que c'est du JSON valide let command; try { command = JSON.parse(data); } catch (jsonError) { this.log.warn('CEC: Invalid JSON in communication file:', jsonError.message); this.log.debug('CEC: Raw data:', data); return; // JSON invalide, ignorer } // Vérifier que la commande a la structure attendue if (!command.action || !command.value) { this.log.warn('CEC: Invalid command structure:', command); return; // Structure invalide, ignorer } this.log.info('CEC: Command received from external CEC service:', command); switch (command.action) { case 'power': if (command.value === 'on') { this.log.info('CEC: Power ON from external service'); this.handleCECPowerOn(); } else if (command.value === 'off' || command.value === 'standby') { this.log.info('CEC: Power OFF/STANDBY from external service'); this.handleCECPowerOff(); } break; case 'volume': if (command.value === 'up') { this.log.info('CEC: Volume UP from external service'); this.handleCECVolumeUp(); } else if (command.value === 'down') { this.log.info('CEC: Volume DOWN from external service'); this.handleCECVolumeDown(); } break; case 'mute': this.log.info('CEC: Mute toggle from external service'); this.handleCECMuteToggle(); break; } // Vider le fichier après traitement (plus fiable que la suppression) try { fs.writeFileSync(path, ''); this.log.debug('CEC: Communication file cleared'); } catch (clearError) { this.log.warn('CEC: Could not clear communication file:', clearError.message); // Essayer de supprimer en dernier recours try { fs.unlinkSync(path); this.log.debug('CEC: Communication file deleted'); } catch (unlinkError) { this.log.warn('CEC: Could not delete communication file (will be overwritten on next command):', unlinkError.message); } } } } catch (error) { this.log.error('CEC: Error reading external CEC service file:', error); } }, 100); // Vérifier toutes les 100ms // Stocker le watcher pour le nettoyage this.externalCECWatcher = watcher; } async handleCECPowerOn() { // Vérifier l'état actuel de l'amplificateur avant d'envoyer la commande const currentTpLinkState = await this.tplinkController.getInUseState(); this.log.info('CEC: Apple TV requested amplifier ON - current TP-Link state:', currentTpLinkState); if (currentTpLinkState) { this.log.info('CEC: Amplifier is already ON - skipping IR command to avoid unnecessary power toggle'); // Même si l'amplificateur est déjà allumé, envoyer la commande HDMI1 si activée if (this.broadlinkController.isAutoHDMI1Enabled()) { this.log.info('CEC: Amplifier already ON - sending HDMI1 command via CEC...'); const hdmiSuccess = await this.sendCECHdmi1Command(); if (hdmiSuccess) { this.log.info('CEC: HDMI1 CEC command sent successfully'); } else { this.log.warn('CEC: HDMI1 CEC command failed'); } } return; } this.log.info('CEC: Amplifier is OFF - preparing to send IR power command'); // Debug: Vérifier la configuration des améliorations this.log.info('CEC: Power enhancements config - autoHDMI1:', this.broadlinkController.isAutoHDMI1Enabled()); this.log.info('CEC: Power enhancements config - tplinkPowerCheck:', this.broadlinkController.isTPLinkPowerCheckEnabled()); this.log.info('CEC: Power enhancements config - tplinkPowerOnDelay:', this.broadlinkController.getTPLinkPowerOnDelay()); // Utiliser la méthode helper pour gérer l'allumage avec les améliorations const success = await this.handlePowerOnWithEnhancements(); if (success) { this.log.info('CEC: Power ON command sent successfully'); // Envoyer immédiatement la commande HDMI1 en parallèle (la TV gère les commandes CEC en arrière-plan pendant qu'elle s'allume) if (this.broadlinkController.isAutoHDMI1Enabled()) { this.log.info('CEC: Sending HDMI1 command immediately via CEC...'); // Ne pas attendre (fire and forget) pour ne pas bloquer l'initialisation du volume this.sendCECHdmi1Command().then(hdmiSuccess => { if (hdmiSuccess) { this.log.info('CEC: HDMI1 CEC command sent successfully'); } else { this.log.warn('CEC: HDMI1 CEC command failed'); } }); } // 4. Attendre un peu pour que la commande IR prenne effet await new Promise(resolve => setTimeout(resolve, 2000)); // 5. Vérifier le nouvel état TP-Link const newTpLinkState = await this.tplinkController.getInUseState(); this.log.info('CEC: After IR command - TP-Link state:', newTpLinkState); // 6. Mettre à jour l'état local et HomeKit this.isOn = newTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('CEC: Updated HomeKit power state to:', this.isOn); // 7. Initialiser le volume si l'amplificateur est maintenant allumé if (this.isOn) { this.log.info('CEC: Amplifier is now ON - starting volume initialization...'); await this.initializeVolumeAfterPowerOn(); } } else { this.log.error('CEC: Failed to send power ON command'); } } async handleCECPowerOff() { // Vérifier l'état actuel de l'amplificateur avant d'envoyer la commande const currentTpLinkState = await this.tplinkController.getInUseState(); this.log.info('CEC: Apple TV requested amplifier OFF - current TP-Link state:', currentTpLinkState); if (!currentTpLinkState) { this.log.info('CEC: Amplifier is already OFF - skipping IR command to avoid unnecessary power toggle'); return; } this.log.info('CEC: Amplifier is ON - sending IR power command to turn OFF'); // Envoyer directement la commande IR (les callbacks onSet ne sont pas déclenchés depuis le code) const success = await this.broadlinkController.powerOff(); if (success) { this.log.info('CEC: Power OFF command sent successfully'); // Attendre un peu pour que la commande IR prenne effet await new Promise(resolve => setTimeout(resolve, 2000)); // Vérifier le nouvel état TP-Link const newTpLinkState = await this.tplinkController.getInUseState(); this.log.info('CEC: After IR command - TP-Link state:', newTpLinkState); // Mettre à jour l'état local et HomeKit this.isOn = newTpLinkState; this.service.updateCharacteristic(this.Characteristic.On, this.isOn); this.log.info('CEC: Updated HomeKit power state to:', this.isOn); } else { this.log.error('CEC: Failed to send power OFF command'); } } async handleCECVolumeUp() { this.log.info('CEC: Volume UP requested - sending IR volume up command'); // Envoyer directement la commande IR (les callbacks onSet ne sont pas déclenchés depuis le code) const success = await this.broadlinkController.volumeUp(); if (success) { // Mettre à jour le volume local seulement si la commande IR a réussi this.currentVolume = Math.min(100, this.currentVolume + 1); this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); this.log.info('CEC: Volume UP command sent successfully, volume now:', this.currentVolume); } else { this.log.error('CEC: Failed to send volume UP command'); } } async handleCECVolumeDown() { this.log.info('CEC: Volume DOWN requested - sending IR volume down command'); // Envoyer directement la commande IR (les callbacks onSet ne sont pas déclenchés depuis le code) const success = await this.broadlinkController.volumeDown(); if (success) { // Mettre à jour le volume local seulement si la commande IR a réussi this.currentVolume = Math.max(0, this.currentVolume - 1); this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); this.log.info('CEC: Volume DOWN command sent successfully, volume now:', this.currentVolume); } else { this.log.error('CEC: Failed to send volume DOWN command'); } } async handleCECMuteToggle() { this.log.info('CEC: Mute toggle requested - sending IR mute command'); // Envoyer directement la commande IR (les callbacks onSet ne sont pas déclenchés depuis le code) const success = await this.broadlinkController.mute(); if (success) { // Basculer l'état mute this.currentVolume = this.currentVolume === 0 ? 50 : 0; // Toggle entre 0 et 50 this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); this.log.info('CEC: Mute command sent successfully, volume now:', this.currentVolume); } else { this.log.error('CEC: Failed to send mute command'); } } syncCECState(powerState) { // Synchroniser l'état CEC avec l'état HomeKit // Écrire l'état dans un fichier que le script CEC peut lire try { const fs = require('fs'); const path = '/var/lib/homebridge/homebridge-to-cec.json'; const stateData = { power: powerState ? 'on' : 'off', timestamp: Date.now(), source: 'homebridge' }; // Écrire de manière atomique pour éviter la corruption const tempPath = path + '.tmp'; fs.writeFileSync(tempPath, JSON.stringify(stateData, null, 2)); // Définir les permissions pour que le script CEC (root) puisse lire fs.chmodSync(tempPath, 0o644); // rw-r--r-- // Déplacer le fichier de manière atomique fs.renameSync(tempPath, path); this.log.info('CEC: Amplifier state written to CEC communication file:', powerState ? 'ON' : 'OFF'); this.log.debug('CEC: State data:', stateData); } catch (error) { this.log.error('CEC: Error writing state to CEC communication file:', error); } } /** * Initialize volume after amplifier power on * This ensures the virtual volume matches the physical amplifier volume */ async initializeVolumeAfterPowerOn() { try { this.log.info('Starting volume initialization after power on...'); // Appeler la méthode d'initialisation du volume du BroadlinkController const success = await this.broadlinkController.initializeVolume(); if (success) { this.log.info('Volume initialization completed successfully'); // Mettre à jour le volume virtuel HomeKit avec le volume de démarrage configuré const startupVolume = this.broadlinkController.getStartupVolume(); this.currentVolume = startupVolume; this.speakerService.updateCharacteristic(this.Characteristic.Volume, this.currentVolume); this.volumeService.updateCharacteristic(this.Characteristic.Brightness, this.currentVolume); this.log.info(`HomeKit volume updated to startup volume: ${this.currentVolume}%`); } else { this.log.error('Volume initialization failed'); } } catch (error) { this.log.error('Error during volume initialization:', error); } } /** * Handle power on with enhancements (TP-Link check and HDMI1) * Used by both HomeKit and CEC power on events */ async handlePowerOnWithEnhancements() { this.log.info('handlePowerOnWithEnhancements: Starting enhanced power on sequence'); // 1. Vérifier et allumer la prise TP-Link si nécessaire if (this.broadlinkController.isTPLinkPowerCheckEnabled()) { this.log.info('handlePowerOnWithEnhancements: TP-Link power check is ENABLED'); this.log.info('Checking TP-Link plug power state...'); // Vérifier l'état actuel avant d'appeler ensurePlugIsOn const currentState = await this.tplinkController.getInUseState(); const plugReady = await this.tplinkController.ensurePlugIsOn(); if (!plugReady) { this.log.error('Failed to ensure TP-Link plug is ON - aborting power on'); return false; } // Attendre le délai configuré SEULEMENT si la prise était OFF et a été allumée if (!currentState) { const delay = this.broadlinkController.getTPLinkPowerOnDelay(); this.log.info(`TP-Link plug was OFF and turned ON - waiting ${delay} seconds for stabilization...`); await new Promise(resolve => setTimeout(resolve, delay * 1000)); } else { this.log.info('TP-Link plug was already ON - no delay needed, proceeding immediately'); } } else { this.log.info('handlePowerOnWithEnhancements: TP-Link power check is DISABLED'); } // 2. Envoyer la commande IR d'allumage this.log.info('handlePowerOnWithEnhancements: Sending IR power command to turn ON amplifier'); const success = await this.broadlinkController.powerOn(); if (success) { this.log.info('handlePowerOnWithEnhancements: IR power command sent successfully'); // 3. Envoyer la commande HDMI1 via CEC si activée if (this.broadlinkController.isAutoHDMI1Enabled()) { this.log.info('handlePowerOnWithEnhancements: Auto HDMI1 is ENABLED'); this.log.info('Sending HDMI1 command via CEC to switch TV to HDMI1...'); const hdmiSuccess = await this.sendCECHdmi1Command(); if (hdmiSuccess) { this.log.info('HDMI1 CEC command sent successfully'); } else { this.log.warn('HDMI1 CEC command failed'); } } else { this.log.info('handlePowerOnWithEnhancements: Auto HDMI1 is DISABLED'); } } else { this.log.error('handlePowerOnWithEnhancements: IR power command failed'); } this.log.info('handlePowerOnWithEnhancements: Enhanced power on sequence completed, success:', success); return success; } /** * Send HDMI1 command via CEC to switch TV to HDMI1 input */ async sendCECHdmi1Command() { try { this.log.info('Sending HDMI1 command via external CEC service...'); // Utiliser le service CEC externe via le fichier de communication const cecCommand = { action: 'hdmi1', timestamp: Math.floor(Date.now() / 1000) }; const fs = require('fs'); const path = '/var/lib/homebridge/homebridge-to-cec.json'; // Écrire la commande HDMI1 dans le fichier de communication fs.writeFileSync(path, JSON.stringify(cecCommand)); this.log.info('HDMI1 command written to CEC communication file'); // Attendre un peu pour que le service CEC traite la commande await new Promise(resolve => setTimeout(resolve, 1000)); this.log.info('HDMI1 command sent via external CEC service'); return true; } catch (error) { this.log.error('Error sending HDMI1 command via external CEC service:', error); return false; } } /** * Try a specific CEC command */ async tryCECCommand(command) { return new Promise((resolve) => { const { spawn } = require('child_process'); const cecProcess = spawn('cec-ctl', command); let output = ''; let errorOutput = ''; cecProcess.stdout.on('data', (data) => { output += data.toString(); }); cecProcess.stderr.on('data', (data) => { errorOutput += data.toString(); }); cecProcess.on('close', (code) => { if (code === 0) { this.log.info('CEC command succeeded'); this.log.debug('CEC output:', output); resolve(true); } else { this.log.warn(`CEC command failed with code: ${code}`); this.log.debug('CEC error:', errorOutput); resolve(false); } }); cecProcess.on('error', (error) => { this.log.warn('Failed to execute CEC command:', error); resolve(false); }); }); } cleanup() { // Nettoyer le watcher CEC externe if (this.externalCECWatcher) { clearInterval(this.externalCECWatcher); this.externalCECWatcher = null; this.log.info('CEC: Stopped external CEC service watcher'); } } } exports.IRAmplifierAccessory = IRAmplifierAccessory; //# sourceMappingURL=platformAccessory.js.map