homebridge-camera-ui
Version:
User Interface for RTSP capable cameras with HSV support.
228 lines (183 loc) • 8.02 kB
JavaScript
'use-strict';
import Logger from '../../services/logger/logger.service.js';
export default class Handler {
constructor(hap, cameraUi) {
this.hap = hap;
this.log = Logger.log;
this.cameraUi = cameraUi;
this.motionTimers = new Map();
//handle motion from mqtt/http/smtp/videoanalysis (passed through camera.ui)
this.cameraUi.on('motion', (cameraName, trigger, state, event) => {
this.handle(trigger, cameraName, state, false, false, event);
});
this.initialized = false;
}
finishLoading(accessories) {
this.accessories = accessories;
this.initialized = true;
}
async handle(target, name, active, manual, muteDoorbell, event) {
if (this.initialized) {
const accessory = this.accessories.find((accessory) => accessory.displayName == name);
if (accessory) {
switch (target) {
case 'motion':
await this.motionHandler(accessory, active, manual, muteDoorbell, event);
break;
case 'doorbell':
await this.doorbellHandler(accessory, active, manual, event);
break;
default:
this.log.error(`Unknown target specified for motion handler (${target})!`, name, 'Homebridge');
}
} else {
this.log.error(`Camera "${name}" not found.`);
}
} else {
this.log.warn('Ignore motion event, Homebridge not yet fully initialized..', name, 'Homebridge');
}
}
async motionHandler(accessory, active, manual, muteDoorbell, event) {
const motionSensor = accessory.getService(this.hap.Service.MotionSensor);
const motionTrigger = accessory.getServiceById(this.hap.Service.Switch, 'MotionTrigger');
if (motionSensor) {
const activeState = Boolean(active);
const sensorState = Boolean(motionSensor.getCharacteristic(this.hap.Characteristic.MotionDetected).value);
if (activeState === sensorState) {
return;
}
const settingsDatabase = await this.cameraUi?.database?.interface.chain.get('settings').cloneDeep().value();
const cameraSettings = settingsDatabase?.cameras.find((cam) => cam.name === accessory.displayName);
const mqttClient = this.cameraUi?.motionController?.mqttClient;
if (mqttClient?.connected && cameraSettings?.mqttTopic) {
mqttClient.publish(
cameraSettings.mqttTopic,
JSON.stringify({
camera: accessory.displayName,
state: active,
type: 'motion',
event: event,
})
);
} else {
this.log.debug('MQTT client not connected, skip MQTT (notification)..');
}
const doorbellSensor = accessory.getService(this.hap.Service.Doorbell);
const timeoutConfig = !accessory.context.config.useInterfaceTimer
? accessory.context.config.motionTimeout
: settingsDatabase?.recordings?.timer || 15;
const timeout = this.motionTimers.get(accessory.UUID);
if (timeout) {
if (active) {
this.log.info('Motion ON - Skip motion event, timeout active!', accessory.displayName);
if (motionTrigger && manual) {
setTimeout(() => motionTrigger.updateCharacteristic(this.hap.Characteristic.On, false), 500);
}
return;
} else {
clearTimeout(timeout);
this.motionTimers.delete(accessory.UUID);
}
}
if (manual) {
const atHome = settingsDatabase?.general?.atHome || false;
const cameraExcluded = (settingsDatabase?.general?.exclude || []).includes(accessory.displayName);
if (active && atHome && !cameraExcluded) {
this.log.info(
`Motion ON - Skip motion trigger. At Home is active and ${accessory.displayName} is not excluded!`,
accessory.displayName
);
if (motionTrigger) {
setTimeout(() => motionTrigger.updateCharacteristic(this.hap.Characteristic.On, false), 500);
}
return;
}
}
this.log.info(`Motion ${active ? 'ON' : 'OFF'}`, accessory.displayName);
motionSensor.updateCharacteristic(this.hap.Characteristic.MotionDetected, active ? true : false);
motionTrigger?.updateCharacteristic(this.hap.Characteristic.On, active ? true : false);
if (!accessory.context.config.hsv && manual) {
this.cameraUi.eventController.triggerEvent('motion', accessory.displayName, active);
}
if (active) {
if (accessory.context.config.motionDoorbell && !muteDoorbell) {
doorbellSensor?.updateCharacteristic(
this.hap.Characteristic.ProgrammableSwitchEvent,
this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS
);
}
if (timeoutConfig > 0) {
const timer = setTimeout(() => {
this.log.info('Motion OFF - Motion handler timeout.', accessory.displayName);
this.motionTimers.delete(accessory.UUID);
motionSensor.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
if (motionTrigger) {
motionTrigger.updateCharacteristic(this.hap.Characteristic.On, false);
}
}, timeoutConfig * 1000);
this.motionTimers.set(accessory.UUID, timer);
}
}
} else {
if (motionTrigger) {
setTimeout(() => motionTrigger.updateCharacteristic(this.hap.Characteristic.On, false), 500);
}
this.log.debug('Motion is not enabled for this camera.');
}
}
async doorbellHandler(accessory, active, manual, event) {
const doorbellSensor = accessory.getService(this.hap.Service.Doorbell);
const doorbellTrigger = accessory.getServiceById(this.hap.Service.Switch, 'DoorbellTrigger');
if (doorbellSensor) {
const settingsDatabase = await this.cameraUi?.database?.interface.chain.get('settings').cloneDeep().value();
const cameraSettings = settingsDatabase.cameras.find((cam) => cam.name === accessory.displayName);
const mqttClient = this.cameraUi?.motionController?.mqttClient;
if (mqttClient?.connected && cameraSettings?.mqttTopic) {
mqttClient.publish(
cameraSettings.mqttTopic,
JSON.stringify({
camera: accessory.displayName,
state: active,
type: 'motion',
event: event,
})
);
} else {
this.log.debug('MQTT client not connected, skip MQTT (notification)..');
}
if (manual) {
const atHome = settingsDatabase?.general?.atHome || false;
const cameraExcluded = (settingsDatabase?.general?.exclude || []).includes(accessory.displayName);
if (active && atHome && !cameraExcluded) {
this.log.info(
`Doorbell ON - Skip doorbell trigger. At Home is active and ${accessory.displayName} is not excluded!`,
accessory.displayName
);
if (doorbellTrigger) {
setTimeout(() => doorbellTrigger.updateCharacteristic(this.hap.Characteristic.On, false), 500);
}
return;
}
}
this.log.info(`Doorbell ${active ? 'ON' : 'OFF'}`, accessory.displayName);
doorbellTrigger?.updateCharacteristic(this.hap.Characteristic.On, active ? true : false);
if (!accessory.context.config.hsv && manual) {
this.cameraUi.eventController.triggerEvent('doorbell', accessory.displayName, active);
}
if (active) {
if (accessory.context.config.hsv) {
this.motionHandler(accessory, active, manual, true);
}
doorbellSensor.updateCharacteristic(
this.hap.Characteristic.ProgrammableSwitchEvent,
this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS
);
}
} else {
this.log.debug('Doorbell is not enabled for this camera.');
}
if (doorbellTrigger) {
setTimeout(() => doorbellTrigger.updateCharacteristic(this.hap.Characteristic.On, false), 500);
}
}
}