UNPKG

homebridge-eufy-security

Version:
369 lines 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StationAccessory = exports.HKGuardMode = void 0; const BaseAccessory_1 = require("./BaseAccessory"); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const eufy_security_client_1 = require("eufy-security-client"); const utils_1 = require("../utils/utils"); var HKGuardMode; (function (HKGuardMode) { HKGuardMode[HKGuardMode["STAY_ARM"] = 0] = "STAY_ARM"; HKGuardMode[HKGuardMode["AWAY_ARM"] = 1] = "AWAY_ARM"; HKGuardMode[HKGuardMode["NIGHT_ARM"] = 2] = "NIGHT_ARM"; HKGuardMode[HKGuardMode["DISARM"] = 3] = "DISARM"; })(HKGuardMode || (exports.HKGuardMode = HKGuardMode = {})); /** * Platform Accessory * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ class StationAccessory extends BaseAccessory_1.BaseAccessory { stationConfig; hasKeyPad = false; modes; alarm_triggered; alarm_delayed; alarm_delay_timeout; guardModeChangeTimeout = { timeout: null, delay: 5000 }; constructor(platform, accessory, device) { super(platform, accessory, device); this.log.debug(`Constructed Station`); this.hasKeyPad = this.device.hasDeviceWithType(eufy_security_client_1.DeviceType.KEYPAD); this.log.debug(`has keypad:`, this.hasKeyPad); this.stationConfig = this.getStationConfig(); this.modes = this.mappingHKEufy(); this.alarm_triggered = false; this.alarm_delayed = false; const validValues = [ utils_1.CHAR.SecuritySystemTargetState.AWAY_ARM, utils_1.CHAR.SecuritySystemTargetState.STAY_ARM, utils_1.CHAR.SecuritySystemTargetState.DISARM, ]; const SecuritySettings = [ eufy_security_client_1.PropertyName.StationHomeSecuritySettings, eufy_security_client_1.PropertyName.StationAwaySecuritySettings, eufy_security_client_1.PropertyName.StationOffSecuritySettings, eufy_security_client_1.PropertyName.StationCustom1SecuritySettings, eufy_security_client_1.PropertyName.StationCustom2SecuritySettings, eufy_security_client_1.PropertyName.StationCustom3SecuritySettings, ]; SecuritySettings.forEach(item => { if (this.device.hasPropertyValue(item) && this.getPropertyValue(item) !== '') { this.log.debug(`- ${item} :`, this.getPropertyValue(item)); } }); // if (this.stationConfig.hkNight) { validValues.push(utils_1.CHAR.SecuritySystemTargetState.NIGHT_ARM); // } this.registerCharacteristic({ serviceType: utils_1.SERV.SecuritySystem, characteristicType: utils_1.CHAR.SecuritySystemCurrentState, getValue: () => this.handleSecuritySystemCurrentStateGet(), onValue: (service, characteristic) => { this.device.on('current mode', (station, currentMode) => { this.onStationCurrentModePushNotification(characteristic, currentMode); }); this.device.on('alarm event', (station, alarmEvent) => this.onStationAlarmEventPushNotification(characteristic, alarmEvent)); }, }); this.registerCharacteristic({ serviceType: utils_1.SERV.SecuritySystem, characteristicType: utils_1.CHAR.SecuritySystemTargetState, getValue: () => this.handleSecuritySystemTargetStateGet(), setValue: (value) => this.handleSecuritySystemTargetStateSet(value), onValue: (service, characteristic) => { this.device.on('guard mode', (station, guardMode) => { this.onStationGuardModePushNotification(characteristic, station, guardMode); }); this.device.on('alarm arm delay event', this.onStationAlarmDelayedEvent.bind(this)); this.device.on('alarm armed event', this.onStationAlarmArmedEvent.bind(this)); }, }); this.getService(utils_1.SERV.SecuritySystem) .getCharacteristic(utils_1.CHAR.SecuritySystemTargetState) .setProps({ validValues }); this.registerCharacteristic({ serviceType: utils_1.SERV.Switch, characteristicType: utils_1.CHAR.On, name: this.accessory.displayName + ' Siren', getValue: () => this.handleManualTriggerSwitchStateGet(), setValue: (value) => this.handleManualTriggerSwitchStateSet(value), }); this.pruneUnusedServices(); } /** * Get the current value of the "propertyName" characteristic */ getPropertyValue(propertyName) { return this.device.getPropertyValue(propertyName); } async setPropertyValue(propertyName, value) { await this.platform.eufyClient.setStationProperty(this.SN, propertyName, value); } /** * Gets the station configuration based on several possible sources. * Priority is given to custom configurations (if available), then falls back to global configs, * and lastly uses default values if neither custom nor global configs are set. * * @returns {StationConfig} The final configuration settings for the station */ getStationConfig() { // Find the station configuration based on the serial number, if it exists const stationConfig = this.platform.config.stations?.find((station) => station.serialNumber === this.SN); // Debug log to show the retrieved station configuration this.log.debug(`Config:`, stationConfig); // Initialize the config object with prioritized values const config = { // For each setting (e.g., hkHome), check if it is defined in the custom config, // if not, check the global config, and as a last resort, use the default value hkHome: stationConfig?.hkHome ?? this.platform.config.hkHome, hkAway: stationConfig?.hkAway ?? this.platform.config.hkAway, hkNight: stationConfig?.hkNight ?? this.platform.config.hkNight, // Default HomeKit mode for 'Off': // - If a keypad is present, set to 6 (Special value) // - Otherwise, set to 63 (Default value) hkOff: stationConfig?.hkOff ?? this.hasKeyPad ? 6 : this.platform.config.hkOff, // Use optional chaining to safely access manualTriggerModes and manualAlarmSeconds manualTriggerModes: stationConfig?.manualTriggerModes ?? [], manualAlarmSeconds: stationConfig?.manualAlarmSeconds ?? 30, }; // Log the manual trigger modes for debugging purposes this.log.debug(`manual alarm will be triggered only in these hk modes:\r${config.manualTriggerModes}`); // Return the final configuration object return config; } mappingHKEufy() { // Initialize the modes array with HomeKit and Eufy mode mappings const modes = [ { hk: 0, eufy: this.stationConfig.hkHome }, { hk: 1, eufy: this.stationConfig.hkAway }, { hk: 2, eufy: this.stationConfig.hkNight }, { hk: 3, eufy: this.stationConfig.hkOff }, ]; // Log the mapping for station modes for debugging purposes this.log.debug(`Mapping for station modes:`, modes); return modes; } onStationGuardModePushNotification(characteristic, station, guardMode) { this.log.debug(`ON SecurityGuardMode: ${guardMode}`); const homekitCurrentMode = this.convertEufytoHK(guardMode); characteristic.updateValue(homekitCurrentMode); } onStationCurrentModePushNotification(characteristic, currentMode) { if (this.guardModeChangeTimeout.timeout) { // If there's an existing timeout, clear it clearTimeout(this.guardModeChangeTimeout.timeout); } this.log.debug(`ON SecuritySystemCurrentState: ${currentMode}`); const homekitCurrentMode = this.convertEufytoHK(currentMode); characteristic.updateValue(homekitCurrentMode); } onStationAlarmEventPushNotification(characteristic, alarmEvent) { let currentValue = this.device.getPropertyValue(eufy_security_client_1.PropertyName.StationCurrentMode); if (alarmEvent === 0) { // do not resset alarm if alarm was triggered manually // since the alarm can only be triggered for 30 seconds for now (limitation of @homebridge-eufy-security/eufy-security-client) // this would mean that the alarm is always reset after 30 seconds // see here: https://github.com/bropat/@homebridge-eufy-security/eufy-security-client/issues/178 currentValue = -1; } switch (alarmEvent) { case 2: // Alarm triggered by GSENSOR case 3: // Alarm triggered by PIR case 4: // Alarm triggered by EUFY_APP case 6: // Alarm triggered by DOOR case 7: // Alarm triggered by CAMERA_PIR case 8: // Alarm triggered by MOTION_SENSOR case 9: // Alarm triggered by CAMERA_GSENSOR utils_1.log.warn('ON StationAlarmEvent - ALARM TRIGGERED - alarmEvent:', eufy_security_client_1.AlarmEvent[alarmEvent]); this.alarm_triggered = true; characteristic.updateValue(utils_1.CHAR.SecuritySystemCurrentState.ALARM_TRIGGERED); // Alarm !!! break; case 0: // Alarm off by Hub case 15: // Alarm off by Keypad case 16: // Alarm off by Eufy App case 17: // Alarm off by HomeBase button utils_1.log.warn('ON StationAlarmEvent - ALARM OFF - alarmEvent:', eufy_security_client_1.AlarmEvent[alarmEvent]); this.alarm_triggered = false; if (currentValue !== -1) { characteristic.updateValue(this.convertEufytoHK(currentValue)); // reset alarm state } break; default: utils_1.log.warn('ON StationAlarmEvent - ALARM UNKNOWN - alarmEvent:', eufy_security_client_1.AlarmEvent[alarmEvent]); characteristic.updateValue(utils_1.CHAR.StatusFault.GENERAL_FAULT); break; } this.updateManuelTriggerButton(this.alarm_triggered); } /** * Convert a HomeKit mode number to its corresponding Eufy mode number. * Searches the `this.modes` array to find a matching HomeKit mode. * Throws an error if a matching mode is not found. * * @param {number} hkMode - The HomeKit mode to convert * @returns {number} The corresponding Eufy mode * @throws {Error} If a matching mode is not found */ convertHKtoEufy(hkMode) { const modeObj = this.modes.find((m) => m.hk === hkMode); if (!modeObj) { throw new Error(`${this.accessory.displayName} No matching Eufy mode found for HomeKit mode ${hkMode}`); } return modeObj.eufy; } /** * Convert a Eufy mode number to its corresponding HomeKit mode number. * Searches the `this.modes` array to find a matching Eufy mode. * Throws an error if a matching mode is not found. * * @param {number} eufyMode - The Eufy mode to convert * @returns {number} The corresponding HomeKit mode * @throws {Error} If a matching mode is not found */ convertEufytoHK(eufyMode) { const modeObj = this.modes.find((m) => m.eufy === eufyMode); if (!modeObj) { throw new Error(`${this.accessory.displayName} No matching HomeKit mode found for Eufy mode ${eufyMode}`); } return modeObj.hk; } /** * Handle requests to get the current value of the 'Security System Current State' characteristic */ handleSecuritySystemCurrentStateGet() { if (this.alarm_triggered) { return utils_1.CHAR.SecuritySystemCurrentState.ALARM_TRIGGERED; } return this.handleSecuritySystemTargetStateGet('handleSecuritySystemCurrentStateGets'); } /** * Handle requests to get the current value of the 'Security System Target State' characteristic */ handleSecuritySystemTargetStateGet(stateCharacteristic = 'handleSecuritySystemTargetStateGet') { try { const currentValue = this.device.getPropertyValue(eufy_security_client_1.PropertyName.StationCurrentMode); if (currentValue === -1) { throw new Error('Something wrong with this device', currentValue); } this.log.debug(`GET StationCurrentMode: ${currentValue}`); return this.convertEufytoHK(currentValue); } catch (error) { this.log.error(`${stateCharacteristic}: Wrong return value`, error); return false; } } /** * Handle requests to set the 'Security System Target State' characteristic */ handleSecuritySystemTargetStateSet(value) { try { this.alarm_triggered = false; const NameMode = this.getGuardModeName(value); this.log.debug(`SET StationGuardMode HomeKit: ${NameMode}`); const mode = this.convertHKtoEufy(value); if (isNaN(mode)) { throw new Error(`${this.accessory.displayName}: Could not convert guard mode value to valid number. Aborting guard mode change...'`); } this.log.debug(`SET StationGuardMode Eufy: ${eufy_security_client_1.GuardMode[mode]}(${mode})`); this.log.info(`Request to change station guard mode to: ${NameMode}`); // Call the device's setGuardMode method to initiate the action this.device.setGuardMode(mode); // Set a new timeout this.guardModeChangeTimeout.timeout = setTimeout(() => { // This code is executed when the timeout elapses, indicating that the action may not have completed yet. // You can include a message indicating that the action is being retried. this.log.warn(`Changing guard mode to ${NameMode} did not complete. Retry...'`); // Call the device's setGuardMode method to initiate the action this.device.setGuardMode(mode); // Set a secondary timeout for retry, if needed const retryTimeout = setTimeout(() => { // This code is executed if the retry also times out, indicating a failure. this.log.error(`Changing guard mode to ${NameMode} timed out!`); }, this.guardModeChangeTimeout.delay); // Store the retry timeout as part of guardModeChangeTimeout this.guardModeChangeTimeout.timeout = retryTimeout; }, this.guardModeChangeTimeout.delay); this.updateManuelTriggerButton(false); } catch (error) { this.log.error(`Error Setting security mode! ${error}`); } } handleManualTriggerSwitchStateGet() { return this.alarm_triggered; } async handleManualTriggerSwitchStateSet(value) { if (value) { // trigger alarm try { const currentValue = this.device.getPropertyValue(eufy_security_client_1.PropertyName.StationCurrentMode); if (currentValue === -1) { throw 'Something wrong with this device'; } // check if alarm is allowed for this guard mode // and alarm is not delayed if (this.stationConfig.manualTriggerModes.indexOf(this.convertEufytoHK(currentValue)) !== -1 && !this.alarm_delayed) { this.device.triggerStationAlarmSound(this.stationConfig.manualAlarmSeconds) .then(() => utils_1.log.debug(this.accessory.displayName, 'alarm manually triggered for ' + this.stationConfig.manualAlarmSeconds + ' seconds.')) .catch(error => this.log.error(`alarm could not be manually triggered: ${error}`)); } else { const message = this.alarm_delayed ? 'tried to trigger alarm, but the alarm delayed event was triggered beforehand.' : 'tried to trigger alarm, but the current station mode prevents the alarm from being triggered. ' + 'Please look in in the configuration if you want to change this behaviour.'; setTimeout(() => { this.log.info(`${message}`); this.updateManuelTriggerButton(false); }, 1000); } } catch { this.log.error(`handleSecuritySystemTargetStateGet: ${value}`); return; } } else { // reset alarm this.device.resetStationAlarmSound() .then(() => this.log.debug(`alarm manually reset`)) .catch(error => this.log.error(`alarm could not be reset: ${error}`)); } } onStationAlarmDelayedEvent(station, armDelay) { this.log.debug(`alarm for this station will be delayed by ${armDelay} seconds.`); this.alarm_delayed = true; if (this.alarm_delay_timeout) { clearTimeout(this.alarm_delay_timeout); } this.alarm_delay_timeout = setTimeout(() => { this.log.debug(`alarm for this station is armed now (due to timeout).`); this.alarm_delayed = false; }, (armDelay + 1) * 1000); } onStationAlarmArmedEvent() { this.log.debug(`alarm for this station is armed now.`); this.alarm_delayed = false; if (this.alarm_delay_timeout) { clearTimeout(this.alarm_delay_timeout); } } getGuardModeName(value) { try { return `${HKGuardMode[value]}(${value})`; } catch (error) { this.log.error(`Error getting guard mode name! ${error}`); return 'Unknown'; } } updateManuelTriggerButton(state) { this.getService(utils_1.SERV.Switch, this.accessory.displayName + ' Siren') .getCharacteristic(utils_1.CHAR.On) .updateValue(state); } } exports.StationAccessory = StationAccessory; //# sourceMappingURL=StationAccessory.js.map