homebridge-eufy-security
Version:
Control Eufy Security from homebridge.
369 lines • 18.5 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");
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
;