homebridge-dahua-alerts
Version:
Routes motion alerts for Dahua, Alhua and Lorex camera streams to `homebridge-camera-ffmpeg`
160 lines (145 loc) • 6.1 kB
text/typescript
import {
API,
IndependentPlatformPlugin,
Logging,
PlatformConfig
} from 'homebridge'
import { DahuaCameraConfig, CameraConfig, CameraCredentials } from './configTypes'
import axios, { AxiosError } from 'axios'
import { DahuaError, DahuaEvents, DahuaAlarm } from './dahua'
const PLUGIN_NAME = 'homebridge-dahua-alerts'
const PLATFORM_NAME = 'dahua-alerts'
export = (api: API) => {
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, DahuaMotionAlertsPlatform)
}
class DahuaMotionAlertsPlatform implements IndependentPlatformPlugin {
private readonly log: Logging
private readonly config: DahuaCameraConfig
constructor(log: Logging, config: PlatformConfig) {
this.log = log
this.config = config as unknown as DahuaCameraConfig
if(this.isInvalidConfig(this.config)) {
this.log.error('Errors above, shutting plugin down')
return
} else {
//find all uniqueHosts in config in order to only setup one "DahuaEvents" (socket) connection per unique host
let uniqueHosts = new Map<string, CameraCredentials>()
if(config.host) {
uniqueHosts.set(config.host, {host: config.host, user: config.user, pass: config.pass, useHttp: config.useHttp} as CameraCredentials)
}
this.config.cameras.forEach(camera => {
if(camera.cameraCredentials) {
uniqueHosts.set(camera.cameraCredentials.host, camera.cameraCredentials)
}
})
uniqueHosts.forEach(host => {
let eventTypes: string[] = [];
for (const eventType in this.config.eventTypes) {
if (this.config.eventTypes[eventType]) {
eventTypes.push(eventType);
}
}
let events: DahuaEvents = new DahuaEvents(host.host, host.user, host.pass, host.useHttp, eventTypes)
events.getEventEmitter().on(events.ALARM_EVENT_NAME, this.alertMotion)
events.getEventEmitter().on(events.ERROR_EVENT_NAME, (data: DahuaError) => {
this.log.error(`${data.error} (for more info enable debug logging)`)
this.log.debug(`${data.errorDetails}`)
})
events.getEventEmitter().on(events.DEBUG_EVENT_NAME, (data) => this.log.debug(data))
events.getEventEmitter().on(events.RECONNECTING_EVENT_NAME, (data) => this.log.debug(data))
})
}
}
private alertMotion = (dahuaAlarm: DahuaAlarm) => {
let cameraName = this.getCameraName(dahuaAlarm)
if(cameraName) {
if (dahuaAlarm.action === 'Start') {
this.log.debug(`Video Motion Detected on index: ${dahuaAlarm.index}, mapped to camera ${cameraName}`)
axios.post(this.motionUrl(cameraName)).then(res => {
this.log.info(`Motion for ${cameraName} posted to homebridge-camera-ffmpeg, received`, res.data)
}).catch((err: AxiosError) => {
let msg = 'Error when posting video motion to homebridge-camera-ffmpeg'
if(err.response) {
this.log.error(`${msg} - Status Code: ${err.response.status} Response: ${err.response.data.statusMessage}`)
} else if(err.request) {
this.log.error(`${msg} - didn't get a response from homebridge-camera-ffmpeg - ${err.message}`)
} else {
this.log.error(`${msg}`)
}
})
} else if (dahuaAlarm.action === 'Stop') {
this.log.debug(`Video Motion Ended on index: ${dahuaAlarm.index}, mapped to camera ${cameraName}`)
axios.post(this.resetMotionUrl(cameraName)).then(res => {
this.log.info(`Reset motion for ${cameraName} posted to homebridge-camera-ffmpeg, received`, res.data)
}).catch((err: AxiosError) => {
let msg = 'Error when posting reset video motion to homebridge-camera-ffmpeg'
if(err.response) {
this.log.error(`${msg} - Status Code: ${err.response.status} Response: ${err.response.data.statusMessage}`)
} else if(err.request) {
this.log.error(`${msg} - didn't get a response from homebridge-camera-ffmpeg - ${err.message}`)
} else {
this.log.error(`${msg}`)
}
})
}
}
}
private motionUrl = (cameraName: string): string => {
return encodeURI(`http://localhost:${this.config.homebridgeCameraFfmpegHttpPort}/motion?${cameraName}`)
}
private resetMotionUrl = (cameraName: string): string => {
return encodeURI(`http://localhost:${this.config.homebridgeCameraFfmpegHttpPort}/motion/reset?${cameraName}`)
}
private getCameraName = (alarm: DahuaAlarm): (string | null) => {
for(let i = 0; i < this.config.cameras.length; i++) {
let camera = this.config.cameras[i]
if(camera.index === Number(alarm.index)) {
if((camera.cameraCredentials && camera.cameraCredentials.host === alarm.host) ||
(!camera.cameraCredentials && this.config.host && this.config.host === alarm.host)) {
return camera.cameraName
}
}
}
return null
}
private isInvalidConfig = (config: DahuaCameraConfig): boolean => {
let error = false
if(!config.homebridgeCameraFfmpegHttpPort) {
this.log.error('homebridge-camera-ffmpeg http port not set in config!')
error = true
} else if(!config.cameras || config.cameras.length === 0) {
this.log.error('no cameras configured!')
error = true
} else if((config.host || config.user || config.pass) && this.invalidCameraCredentials({host: config.host, user: config.user, pass: config.pass} as CameraCredentials)) {
error = true
} else {
config.cameras.forEach((camera: CameraConfig) => {
if(!camera.cameraName || (!camera.index && camera.index !== 0)) {
this.log.error('no camera name or index set!')
error = true
return
}
/*if it has camera credentials and it's invalid */
else if(camera.cameraCredentials && this.invalidCameraCredentials(camera.cameraCredentials)) {
error = true
return error
}
})
}
return error
}
private invalidCameraCredentials(config: CameraCredentials) {
let error = false
if(!config.host) {
this.log.error('host not set!')
error = true
} else if(!config.user) {
this.log.error('user not set!')
error = true
} else if(!config.pass) {
this.log.error('pass not set!')
error = true
}
return error
}
}