@ubreu/homebridge-eufy-security
Version:
Control Eufy Security from homebridge.
374 lines • 20.7 kB
JavaScript
;
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");
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 || (exports.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 {
constructor(platform, accessory, device) {
super(platform, accessory, device);
this.hasKeyPad = false;
this.guardModeChangeTimeout = { timeout: null, delay: 5000 };
this.platform.log.debug(`${this.accessory.displayName} Constructed Station`);
this.stationConfig = this.getStationConfig();
this.hasKeyPad = this.device.hasDeviceWithType(eufy_security_client_1.DeviceType.KEYPAD);
this.mappingHKEufy();
this.alarm_triggered = false;
this.alarm_delayed = false;
const validValues = [
this.platform.Characteristic.SecuritySystemTargetState.AWAY_ARM,
this.platform.Characteristic.SecuritySystemTargetState.STAY_ARM,
this.platform.Characteristic.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.platform.log.debug(`${this.accessory.displayName} - ${item} : ${JSON.stringify(this.getPropertyValue(item))}`);
}
});
// if (this.stationConfig.hkNight) {
validValues.push(this.platform.Characteristic.SecuritySystemTargetState.NIGHT_ARM);
// }
this.registerCharacteristic({
serviceType: this.platform.Service.SecuritySystem,
characteristicType: this.platform.Characteristic.SecuritySystemCurrentState,
getValue: (data) => this.handleSecuritySystemCurrentStateGet(),
onValue: (service, characteristic) => {
this.device.on('current mode', (station, currentMode) => {
this.onStationCurrentModePushNotification(characteristic, station, currentMode);
});
this.device.on('alarm event', (station, alarmEvent) => this.onStationAlarmEventPushNotification(characteristic, station, alarmEvent));
},
});
this.registerCharacteristic({
serviceType: this.platform.Service.SecuritySystem,
characteristicType: this.platform.Characteristic.SecuritySystemTargetState,
getValue: (data) => this.handleSecuritySystemTargetStateGet(),
setValue: (value) => this.handleSecuritySystemTargetStateSet(value),
onValue: (service, characteristic) => {
// eslint-disable-next-line max-len
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(this.platform.Service.SecuritySystem)
.getCharacteristic(this.platform.Characteristic.SecuritySystemTargetState)
.setProps({ validValues });
this.registerCharacteristic({
serviceType: this.platform.Service.Switch,
characteristicType: this.platform.Characteristic.On,
name: this.accessory.displayName + '_Siren',
getValue: (data) => 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);
}
/**
* Helper function to get a configuration value by checking custom, global, and default settings.
* @param {number} customValue - Value from the custom configuration
* @param {number} globalValue - Value from the global configuration
* @param {number} defaultValue - The default value to use if both custom and global values are undefined
* @returns {number} The chosen value based on priority
*/
getConfigValue(customValue, globalValue, defaultValue) {
return customValue !== undefined ? customValue : (globalValue !== undefined ? globalValue : defaultValue);
}
/**
* 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() {
var _a, _b, _c;
// Find the station configuration based on the serial number, if it exists
const stationConfig = (_a = this.platform.config.stations) === null || _a === void 0 ? void 0 : _a.find((station) => station.serialNumber === this.SN);
// Debug log to show the retrieved station configuration
this.platform.log.debug(`${this.accessory.displayName} Config: ${JSON.stringify(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: this.getConfigValue(stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.hkHome, this.platform.config.hkHome, 0),
hkAway: this.getConfigValue(stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.hkAway, this.platform.config.hkAway, 1),
hkNight: this.getConfigValue(stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.hkNight, this.platform.config.hkNight, 2),
// Default HomeKit mode for 'Off':
// - If a keypad is present, set to 63 (Special value)
// - Otherwise, set to 6 (Default value)
hkOff: this.getConfigValue(stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.hkOff, this.platform.config.hkOff, this.hasKeyPad ? 63 : 6),
// Use optional chaining to safely access manualTriggerModes and manualAlarmSeconds
manualTriggerModes: (_b = stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.manualTriggerModes) !== null && _b !== void 0 ? _b : [],
manualAlarmSeconds: (_c = stationConfig === null || stationConfig === void 0 ? void 0 : stationConfig.manualAlarmSeconds) !== null && _c !== void 0 ? _c : 30,
};
// Log the manual trigger modes for debugging purposes
this.platform.log.debug(`${this.accessory.displayName}
manual alarm will be triggered only in these hk modes:
${config.manualTriggerModes}`);
// Return the final configuration object
return config;
}
onStationGuardModePushNotification(characteristic, station, guardMode) {
this.platform.log.debug(`${this.accessory.displayName} ON SecurityGuardMode: ${guardMode}`);
const homekitCurrentMode = this.convertEufytoHK(guardMode);
characteristic.updateValue(homekitCurrentMode);
}
onStationCurrentModePushNotification(characteristic, station, currentMode) {
if (this.guardModeChangeTimeout.timeout) {
// If there's an existing timeout, clear it
clearTimeout(this.guardModeChangeTimeout.timeout);
}
this.platform.log.debug(`${this.accessory.displayName} ON SecuritySystemCurrentState: ${currentMode}`);
const homekitCurrentMode = this.convertEufytoHK(currentMode);
characteristic.updateValue(homekitCurrentMode);
}
onStationAlarmEventPushNotification(characteristic, station, 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 eufy-security-client)
// this would mean that the alarm is always reset after 30 seconds
// see here: https://github.com/bropat/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
this.platform.log.warn('ON StationAlarmEvent - ALARM TRIGGERED - alarmEvent:', eufy_security_client_1.AlarmEvent[alarmEvent]);
this.alarm_triggered = true;
characteristic.updateValue(this.platform.Characteristic.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
this.platform.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:
this.platform.log.warn('ON StationAlarmEvent - ALARM UNKNOWN - alarmEvent:', eufy_security_client_1.AlarmEvent[alarmEvent]);
characteristic.updateValue(this.platform.Characteristic.StatusFault.GENERAL_FAULT);
break;
}
this.updateManuelTriggerButton(this.alarm_triggered);
}
mappingHKEufy() {
// Initialize the modes array with HomeKit and Eufy mode mappings
this.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.platform.log.debug(`${this.accessory.displayName} Mapping for station modes: ${JSON.stringify(this.modes)}`);
}
/**
* 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 this.platform.Characteristic.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 'Something wrong with this device';
}
this.platform.log.debug(`${this.accessory.displayName} GET StationCurrentMode: ${currentValue}`);
return this.convertEufytoHK(currentValue);
}
catch (_a) {
this.platform.log.error(`${this.accessory.displayName} ${stateCharacteristic}: Wrong return value`);
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.platform.log.debug(`${this.accessory.displayName} 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.platform.log.debug(`${this.accessory.displayName} SET StationGuardMode Eufy: ${eufy_security_client_1.GuardMode[mode]}(${mode})`);
this.platform.log.info(`${this.accessory.displayName} 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.platform.log.warn(`${this.accessory.displayName} 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.platform.log.error(`${this.accessory.displayName} 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.platform.log.error(`${this.accessory.displayName} 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(() => this.platform.log.debug(this.accessory.displayName, 'alarm manually triggered for ' + this.stationConfig.manualAlarmSeconds + ' seconds.'))
.catch(err => this.platform.log.error(`${this.accessory.displayName} alarm could not be manually triggered: ${err}`));
}
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.platform.log.info(`${this.accessory.displayName} ${message}`);
this.updateManuelTriggerButton(false);
}, 1000);
}
}
catch (_a) {
this.platform.log.error(`${this.accessory.displayName} handleSecuritySystemTargetStateGet: ${value}`);
return;
}
}
else { // reset alarm
this.device.resetStationAlarmSound()
.then(() => this.platform.log.debug(`${this.accessory.displayName} alarm manually reset`))
.catch(err => this.platform.log.error(`${this.accessory.displayName} alarm could not be reset: ${err}`));
}
}
onStationAlarmDelayedEvent(station, armDelay) {
this.platform.log.debug(`${this.accessory.displayName} 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.platform.log.debug(`${this.accessory.displayName} alarm for this station is armed now (due to timeout).`);
this.alarm_delayed = false;
}, (armDelay + 1) * 1000);
}
onStationAlarmArmedEvent(station) {
this.platform.log.debug(`${this.accessory.displayName} 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) {
return 'Unknown';
}
}
updateManuelTriggerButton(state) {
this.getService(this.platform.Service.Switch, this.accessory.displayName + '_Siren')
.getCharacteristic(this.platform.Characteristic.On)
.updateValue(state);
}
}
exports.StationAccessory = StationAccessory;
//# sourceMappingURL=StationAccessory.js.map