├── .gitignore ├── src ├── index.ts ├── HikvisionApi.ts ├── HikVisionCamera.ts └── HikVisionNVR.ts ├── tsconfig.json ├── .github └── workflows │ └── main.yml ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .homebridge*/ 2 | .vscode*/ 3 | dist/ 4 | node_modules/ 5 | yarn-error.log 6 | **/config.json 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { API } from "homebridge"; 2 | import { HikVisionNVR } from "./HikVisionNVR"; 3 | 4 | export const HIKVISION_PLUGIN_NAME = "homebridge-plugin-hikvision"; 5 | export const HIKVISION_PLATFORM_NAME = "Hikvision"; 6 | 7 | export default function main(api: API) { 8 | api.registerPlatform( 9 | HIKVISION_PLUGIN_NAME, 10 | HIKVISION_PLATFORM_NAME, 11 | HikVisionNVR 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2015", 7 | "es2016", 8 | "es2017", 9 | "es2018", 10 | "ES2019" 11 | ], 12 | "declaration": true, 13 | "declarationMap": true, 14 | "sourceMap": true, 15 | "outDir": "./dist", 16 | "rootDir": "./src", 17 | "strict": true, 18 | "esModuleInterop": true, 19 | "baseUrl": "./src", 20 | "skipLibCheck": true, 21 | "noImplicitAny" : true 22 | }, 23 | "include": [ 24 | "./src/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "./node_modules", 28 | "**/*.spec.ts" 29 | ] 30 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | publish: 18 | runs-on: ubuntu-20.04 19 | steps: 20 | - uses: actions/checkout@v1 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: 10 24 | - run: npm install 25 | - run: npm test 26 | - run: npm run build 27 | - uses: JS-DevTools/npm-publish@v1 28 | with: 29 | token: ${{ secrets.NPM_TOKEN }} 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@evops/homebridge-hikvision", 3 | "version": "1.0.17", 4 | "main": "dist/index.js", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/eu-evops/homebridge-hikvision" 9 | }, 10 | "dependencies": { 11 | "axios-digest": "^0.1.1", 12 | "highland": "^2.13.5", 13 | "homebridge-camera-ffmpeg": "v3.0.3", 14 | "xml2js": "^0.4.23" 15 | }, 16 | "devDependencies": { 17 | "@types/highland": "^2.12.10", 18 | "@types/node": "^14.0.12", 19 | "@types/node-persist": "^3.0.0", 20 | "@types/xml2js": "^0.4.5", 21 | "homebridge": "^1.1.3", 22 | "typescript": "^4.0.2", 23 | "rimraf": "^3.0.2" 24 | }, 25 | "scripts": { 26 | "build": "rimraf ./dist && tsc", 27 | "start": "npx homebridge -D -I -P . -U .homebridge -Q", 28 | "start-ffmpeg": "npx homebridge -D -I -P ./node_modules/homebridge-camera-ffmpeg -U .homebridge-ffmpeg -Q" 29 | }, 30 | "engines": { 31 | "node": ">=10", 32 | "homebridge": ">=1.0.0" 33 | }, 34 | "keywords": [ 35 | "homebridge-plugin" 36 | ] 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Homebridge Hikvision plugin 2 | 3 | THIS REPOSITORY IS NOW BEING ARCHIVED - I HAVE SWITCHED TO USING [SCRY](https://www.scrypted.app) WHICH PROVIDES HOMEKIT SECURE VIDEO AND WORKS A LOT MORE SEAMLESSLY. 4 | 5 | 6 | 7 | \* README written from memory, please raise an issue if things are not working as expected. 8 | 9 | Homebridge plugin that connects to your HikVision NVR and exposes your cameras in Homebridge. The plugin is heavily based on excellent [homebridge-camera-ffmpeg](https://github.com/Sunoo/homebridge-camera-ffmpeg). 10 | 11 | This plugin will automatically discover all cameras connected to your NVR and will expose them to homebridge. You will get access to stream video (including sound) directly from your home app, will get push notification with screenshots of the motion events - this needs to be configured in HikVision NVR and additionally in Home app under home settings - you will need to enable notifications. 12 | 13 | ## Installation 14 | 15 | In order to use this plugin, you need to install `homebridge-camera-ffmpeg` and `homebridge-hikvision` as per below: 16 | 17 | ```bash 18 | sudo npm install -g homebridge-camera-ffmpeg @evops/homebridge-hikvision --unsafe-perm 19 | ``` 20 | 21 | ## Configuration 22 | 23 | To configure, add this to your `config.json` for homebridge under platforms node: 24 | 25 | ```json 26 | { 27 | "platforms": [ 28 | { 29 | "platform": "Hikvision", 30 | "host": "nvr-host", 31 | "port": 443, 32 | "username": "admin", 33 | "password": "very-secure-password" 34 | } 35 | ] 36 | } 37 | ``` 38 | 39 | At this time, these are the only configuration options available. Once you've added the platform to your config, (re)start your homebridge instance, and configure your cameras on your iPhone or iPad, iOS will prompt you for each camera in your system. 40 | 41 | ## Enable notifications in HikVision NVR 42 | 43 | The plugin does not detect motion itself, it responds to events generated by the NVR using HTTP Event API. The relevant code for this lives at https://github.com/eu-evops/homebridge-hikvision/blob/master/src/HikvisionApi.ts#L73. 44 | 45 | You must set up notifications in the NVR configuration as per below screenshot: 46 | 47 | ![image](https://user-images.githubusercontent.com/47468/103291461-af287480-49e3-11eb-9db9-18708b35b3c3.png) 48 | 49 | You might also find this YouTube video useful: 50 | 51 | https://www.youtube.com/watch?v=-R_dtE9o9WI 52 | 53 | [![Setup Notifications](https://img.youtube.com/vi/-R_dtE9o9WI/0.jpg)](https://www.youtube.com/watch?v=-R_dtE9o9WI) 54 | 55 | -------------------------------------------------------------------------------- /src/HikvisionApi.ts: -------------------------------------------------------------------------------- 1 | import https from 'https'; 2 | import Axios, { AxiosResponse, AxiosRequestConfig } from 'axios'; 3 | import { AxiosDigest } from 'axios-digest'; 4 | import xml2js, { Parser } from 'xml2js'; 5 | import highland from 'highland'; 6 | import { PlatformConfig } from 'homebridge'; 7 | 8 | export interface HikVisionNvrApiConfiguration extends PlatformConfig { 9 | host: string 10 | port: Number 11 | secure: boolean 12 | ignoreInsecureTls: boolean 13 | username: string 14 | password: string 15 | debugFfmpeg: boolean 16 | } 17 | 18 | export class HikvisionApi { 19 | private _http?: AxiosDigest 20 | private _parser?: Parser 21 | 22 | constructor(config: HikVisionNvrApiConfiguration) { 23 | const _axios = Axios.create({ 24 | baseURL: `http${config.secure ? 's' : ''}://${config.host}:${config.port}`, 25 | httpsAgent: new https.Agent({ 26 | rejectUnauthorized: !config.ignoreInsecureTls 27 | }) 28 | }); 29 | this._http = new AxiosDigest(config.username, config.password, _axios); 30 | this._parser = new Parser({ explicitArray: false }) 31 | } 32 | 33 | /* 34 | "DeviceInfo": { 35 | "$": { 36 | "version": "2.0", 37 | "xmlns": "http://www.isapi.org/ver20/XMLSchema" 38 | }, 39 | "deviceName": "Network Video Recorder", 40 | "deviceID": "48443030-3637-3534-3837-f84dfcf8ef1c", 41 | "model": "DS-7608NI-I2/8P", 42 | "serialNumber": "DS-7608NI-I2/8P0820190316CCRRD00675487WCVU", 43 | "macAddress": "f8:4d:fc:f8:ef:1c", 44 | "firmwareVersion": "V4.22.005", 45 | "firmwareReleasedDate": "build 191208", 46 | "encoderVersion": "V5.0", 47 | "encoderReleasedDate": "build 191208", 48 | "deviceType": "NVR", 49 | "telecontrolID": "255" 50 | } 51 | */ 52 | public async getSystemInfo() { 53 | return this._getResponse('/ISAPI/System/deviceInfo'); 54 | } 55 | 56 | async getCameras() { 57 | const channels = await this._getResponse('/ISAPI/ContentMgmt/InputProxy/channels'); 58 | const channelStatus = await this._getResponse('/ISAPI/ContentMgmt/InputProxy/channels/status'); 59 | 60 | for (let i = 0; i < channels.InputProxyChannelList.InputProxyChannel.length; i++) { 61 | const channel = channels.InputProxyChannelList.InputProxyChannel[i]; 62 | try { 63 | channel.capabilities = await this._getResponse(`/ISAPI/ContentMgmt/StreamingProxy/channels/${channel.id}01/capabilities`); 64 | } catch { 65 | console.log(`Error getting cabailities for channel ${channel.id}`); 66 | } 67 | } 68 | 69 | return channels.InputProxyChannelList.InputProxyChannel.map((channel: { status: any; id: any; name: string }) => { 70 | channel.status = channelStatus.InputProxyChannelStatusList.InputProxyChannelStatus.find((cs: { id: any; }) => { 71 | return cs.id === channel.id; 72 | }); 73 | return channel; 74 | }).filter((camera: { status: { online: string; }; }) => camera.status.online === 'true'); 75 | } 76 | 77 | async startMonitoringEvents(callback: (value: any) => any) { 78 | 79 | const xmlParser = new xml2js.Parser({ 80 | explicitArray: false, 81 | }); 82 | 83 | 84 | /* 85 | EventNotificationAlert: { 86 | '$': { version: '2.0', xmlns: 'http://www.isapi.org/ver20/XMLSchema' }, 87 | ipAddress: '10.0.1.186', 88 | portNo: '80', 89 | protocolType: 'HTTP', 90 | macAddress: 'f8:4d:fc:f8:ef:1c', 91 | dynChannelID: '1', 92 | channelID: '1', 93 | dateTime: '2020-02-19T18:44:4400:00', 94 | activePostCount: '1', 95 | eventType: 'fielddetection', 96 | eventState: 'active', 97 | eventDescription: 'fielddetection alarm', 98 | channelName: 'Front door', 99 | DetectionRegionList: { DetectionRegionEntry: [Object] } 100 | } 101 | */ 102 | 103 | 104 | const url = `/ISAPI/Event/notification/alertStream` 105 | 106 | // TODO: what do we do if we lose our connection to the NVR? Don't we need to re-connect? 107 | this.get(url, { 108 | responseType: 'stream', 109 | headers: {} 110 | }).then(response => { 111 | highland(response!.data) 112 | .map((chunk: any) => chunk.toString('utf8')) 113 | .filter(text => text.match(/<\?xml/)) 114 | .map(text => text.replace(/[\s\S]*<\?xml/gmi, ' xmlParser.parseStringPromise(xmlText)) 116 | .each(promise => promise.then(callback)); 117 | }); 118 | } 119 | 120 | async get(url: string, config?: AxiosRequestConfig): Promise { 121 | return this._http?.get(url, config); 122 | } 123 | 124 | private async _getResponse(path: string) { 125 | const response = await this._http?.get(path); 126 | const responseJson = await this._parser?.parseStringPromise(response?.data); 127 | return responseJson; 128 | } 129 | } -------------------------------------------------------------------------------- /src/HikVisionCamera.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Service, 3 | WithUUID, 4 | AudioStreamingCodecType, 5 | AudioStreamingSamplerate, 6 | CameraControllerOptions, 7 | API, 8 | PlatformAccessory, 9 | } from "homebridge"; 10 | 11 | // We borrow, rather cheekly from the homebridge-camera-ffmpeg plugin. 12 | // TODO: probably rethink and do something like https://github.com/homebridge/homebridge-examples/tree/master/bridged-camera-example-typescript. 13 | 14 | import { StreamingDelegate } from "homebridge-camera-ffmpeg/dist/streamingDelegate"; 15 | import { Logger } from "homebridge-camera-ffmpeg/dist/logger"; 16 | import { CameraConfig } from "homebridge-camera-ffmpeg/dist/configTypes"; 17 | 18 | export class HikVisionCamera { 19 | log: any; 20 | config: any; 21 | any: any; 22 | camera?: any; 23 | motionDetected: boolean = false; 24 | homebridgeApi: API; 25 | displayName: string; 26 | UUID: string; 27 | accessory: any; 28 | 29 | constructor(log: any, homebridgeApi: API, accessory: PlatformAccessory) { 30 | this.log = log; 31 | this.homebridgeApi = homebridgeApi; 32 | this.accessory = accessory; 33 | this.displayName = this.accessory.displayName; 34 | this.UUID = accessory.UUID; 35 | 36 | this.configure(this.accessory); 37 | } 38 | 39 | getService(...args: any[]) { 40 | return this.accessory.getService(...args); 41 | } 42 | 43 | configureController(...args: any[]) { 44 | return this.accessory.configureController(...args); 45 | } 46 | 47 | addService(...args: any[]) { 48 | return this.accessory.addService(...args); 49 | } 50 | 51 | removeService(...args: any[]) { 52 | return this.accessory.removeService(...args); 53 | } 54 | 55 | on(...args: any[]) { 56 | this.accessory.on(...args); 57 | } 58 | 59 | getServiceByUUIDAndSubType>( 60 | uuid: string | T, 61 | subType: string 62 | ): Service | undefined { 63 | return undefined; 64 | } 65 | 66 | configure(accessory: any) { 67 | this.log.info( 68 | "[HikvisionCamera] Configuring accessory: ", 69 | accessory.displayName 70 | ); 71 | 72 | accessory.on("identify", () => { 73 | this.log(`${accessory.displayName} identified!`); 74 | }); 75 | 76 | let motionSensor: Service | undefined = accessory.getService( 77 | this.homebridgeApi.hap.Service.MotionSensor 78 | ); 79 | if (motionSensor) { 80 | this.log.info("Re-creating motion sensor"); 81 | accessory.removeService(motionSensor); 82 | } else { 83 | this.log.warn("There was no motion sensor set up!"); 84 | } 85 | 86 | motionSensor = new this.homebridgeApi.hap.Service.MotionSensor( 87 | accessory.displayName 88 | ); 89 | accessory.addService(motionSensor!); 90 | 91 | const channelId = accessory.context.channelId; 92 | const cameraConfig = { 93 | name: accessory.displayName, 94 | videoConfig: { 95 | source: `-rtsp_transport tcp -i rtsp://${accessory.context.username}:${accessory.context.password}@${accessory.context.host}/Streaming/Channels/${channelId}01`, 96 | stillImageSource: `-i http${accessory.context.secure ? "s" : ""}://${ 97 | accessory.context.username 98 | }:${accessory.context.password}@${ 99 | accessory.context.host 100 | }/ISAPI/Streaming/channels/${channelId}01/picture?videoResolutionWidth=720`, 101 | maxFPS: 30, // TODO: pull this from the camera to avoid ever upsampling 102 | maxBitrate: 16384, // TODO: pull this from the camera to avoid ever upsampling 103 | maxWidth: 1920, // TODO: pull this from the camera to avoid ever upsampling 104 | vcodec: "libx264", 105 | audio: accessory.context.hasAudio, 106 | debug: Boolean(accessory.context.debugFfmpeg), 107 | }, 108 | }; 109 | 110 | const cameraLogger = new Logger(this.log); 111 | 112 | // Use the homebridge-camera-ffmpeg StreamingDelegate. 113 | const streamingDelegate = new StreamingDelegate( 114 | cameraLogger, 115 | cameraConfig, 116 | this.homebridgeApi, 117 | this.homebridgeApi.hap, 118 | "" 119 | ); 120 | 121 | const cameraControllerOptions: CameraControllerOptions = { 122 | cameraStreamCount: 5, // HomeKit requires at least 2 streams, but 1 is also just fine 123 | delegate: streamingDelegate, 124 | streamingOptions: { 125 | supportedCryptoSuites: [ 126 | this.homebridgeApi.hap.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80, 127 | ], 128 | video: { 129 | resolutions: [ 130 | // TODO: put in the max framerates & resolutions from the camera config. 131 | [320, 180, 30], 132 | [320, 240, 15], // Apple Watch requires this configuration 133 | [320, 240, 30], 134 | [480, 270, 30], 135 | [480, 360, 30], 136 | [640, 360, 30], 137 | [640, 480, 30], 138 | [1280, 720, 30], 139 | [1280, 960, 30], 140 | [1920, 1080, 30], 141 | [1600, 1200, 30], 142 | ], 143 | codec: { 144 | profiles: [ 145 | this.homebridgeApi.hap.H264Profile.BASELINE, 146 | this.homebridgeApi.hap.H264Profile.MAIN, 147 | this.homebridgeApi.hap.H264Profile.HIGH, 148 | ], 149 | levels: [ 150 | this.homebridgeApi.hap.H264Level.LEVEL3_1, 151 | this.homebridgeApi.hap.H264Level.LEVEL3_2, 152 | this.homebridgeApi.hap.H264Level.LEVEL4_0, 153 | ], 154 | }, 155 | }, 156 | audio: { 157 | codecs: [ 158 | { 159 | type: AudioStreamingCodecType.OPUS, 160 | samplerate: AudioStreamingSamplerate.KHZ_24, 161 | }, 162 | { 163 | type: AudioStreamingCodecType.AAC_ELD, 164 | samplerate: AudioStreamingSamplerate.KHZ_16, 165 | }, 166 | ], 167 | }, 168 | }, 169 | }; 170 | 171 | const cameraController = new this.homebridgeApi.hap.CameraController( 172 | cameraControllerOptions 173 | ); 174 | 175 | accessory.configureController(cameraController); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/HikVisionNVR.ts: -------------------------------------------------------------------------------- 1 | import { HikvisionApi, HikVisionNvrApiConfiguration } from "./HikvisionApi"; 2 | import { HikVisionCamera } from "./HikVisionCamera"; 3 | import { HIKVISION_PLATFORM_NAME, HIKVISION_PLUGIN_NAME } from "."; 4 | 5 | import { API, PlatformAccessory, PlatformConfig } from "homebridge"; 6 | 7 | export class HikVisionNVR { 8 | private homebridgeApi: API; 9 | private log: any; 10 | 11 | config: HikVisionNvrApiConfiguration; 12 | hikVisionApi: HikvisionApi; 13 | cameras: HikVisionCamera[]; 14 | 15 | constructor(logger: any, config: PlatformConfig, api: API) { 16 | this.hikVisionApi = new HikvisionApi(config as HikVisionNvrApiConfiguration); 17 | this.homebridgeApi = api; 18 | this.log = logger; 19 | this.config = config as HikVisionNvrApiConfiguration; 20 | this.cameras = []; 21 | 22 | this.log("Initialising accessories for HikVision..."); 23 | 24 | this.homebridgeApi.on( 25 | "didFinishLaunching", 26 | this.startMonitoring.bind(this) 27 | ); 28 | 29 | this.loadAccessories(); 30 | setInterval(this.loadAccessories.bind(this), 60 * 1000); 31 | } 32 | 33 | async loadAccessories() { 34 | const systemInformation = await this.hikVisionApi.getSystemInfo(); 35 | this.log.info("Connected to NVR system: %O", systemInformation) 36 | 37 | this.log.info("Loading cameras..."); 38 | const apiCameras = await this.hikVisionApi.getCameras(); 39 | this.log.debug("Found cameras: %s", JSON.stringify(apiCameras, null, 4)); 40 | 41 | const newAccessories = apiCameras.map((channel: { 42 | id: string; 43 | name: string; 44 | capabilities: any; 45 | }) => { 46 | 47 | const cameraConfig = { 48 | accessory: "camera", 49 | name: channel.name, 50 | channelId: channel.id, 51 | hasAudio: channel.capabilities ? !!channel.capabilities.StreamingChannel.Audio : false, 52 | }; 53 | 54 | const cameraUUID = this.homebridgeApi.hap.uuid.generate( 55 | HIKVISION_PLUGIN_NAME + systemInformation.deviceID + cameraConfig.channelId 56 | ); 57 | const accessory: PlatformAccessory = new this.homebridgeApi.platformAccessory( 58 | cameraConfig.name, 59 | cameraUUID 60 | ); 61 | accessory.context = cameraConfig; 62 | 63 | // Only add new cameras that are not cached 64 | if (!this.cameras.find((x) => x.UUID === accessory.UUID)) { 65 | this.configureAccessory(accessory); // abusing the configureAccessory here 66 | this.homebridgeApi.registerPlatformAccessories( 67 | HIKVISION_PLUGIN_NAME, 68 | HIKVISION_PLATFORM_NAME, 69 | [accessory] 70 | ); 71 | } 72 | 73 | return accessory; 74 | }); 75 | 76 | 77 | this.log.info("Registering cameras with homebridge"); 78 | 79 | // var camerasToRemove: any[] = []; 80 | // // Remove cameras that were not in previous call 81 | // this.cameras.forEach((camera: any) => { 82 | // if (!newAccessories.find((x: PlatformAccessory) => x.UUID === camera.UUID)) { 83 | // this.log(`Unregistering missing camera: ${camera.UUID}`) 84 | // camerasToRemove.push(camera.accessory); 85 | // } 86 | // }); 87 | 88 | // this.homebridgeApi.unregisterPlatformAccessories(HIKVISION_PLUGIN_NAME, HIKVISION_PLATFORM_NAME, camerasToRemove); 89 | } 90 | 91 | async configureAccessory(accessory: PlatformAccessory) { 92 | this.log(`Configuring accessory ${accessory.displayName}`); 93 | 94 | accessory.context = Object.assign(accessory.context, this.config); 95 | const camera = new HikVisionCamera(this.log, this.homebridgeApi, accessory); 96 | 97 | const cameraAccessoryInfo = camera.getService( 98 | this.homebridgeApi.hap.Service.AccessoryInformation 99 | ); 100 | cameraAccessoryInfo!.setCharacteristic(this.homebridgeApi.hap.Characteristic.Manufacturer, 'HikVision'); 101 | // cameraAccessoryInfo!.setCharacteristic(this.homebridgeApi.hap.Characteristic.Model, systemInformation.DeviceInfo.model); 102 | // cameraAccessoryInfo!.setCharacteristic(this.homebridgeApi.hap.Characteristic.SerialNumber, systemInformation.DeviceInfo.serialNumber); 103 | // cameraAccessoryInfo!.setCharacteristic(this.homebridgeApi.hap.Characteristic.FirmwareRevision, systemInformation.DeviceInfo.firmwareVersion); 104 | 105 | this.cameras.push(camera); 106 | } 107 | 108 | private processHikVisionEvent(event: any) { 109 | switch (event.EventNotificationAlert.eventType) { 110 | case "videoloss": 111 | this.log.debug("videoloss, nothing to do..."); 112 | break; 113 | case "fielddetection": 114 | case "linedetection": 115 | case "shelteralarm": 116 | case "VMD": 117 | const motionDetected = 118 | event.EventNotificationAlert.eventState === "active"; 119 | const channelId = event.EventNotificationAlert.channelID; 120 | 121 | const camera = this.cameras.find( 122 | (camera) => camera.accessory.context.channelId === channelId 123 | ); 124 | if (!camera) { 125 | return this.log.warn("Could not find camera for event", event); 126 | } 127 | 128 | this.log.info( 129 | "Motion detected on camera, triggering motion for ", 130 | camera.displayName 131 | ); 132 | 133 | if (motionDetected !== camera.motionDetected) { 134 | camera.motionDetected = motionDetected; 135 | const motionService = camera.getService( 136 | this.homebridgeApi.hap.Service.MotionSensor 137 | ); 138 | motionService?.setCharacteristic( 139 | this.homebridgeApi.hap.Characteristic.MotionDetected, 140 | motionDetected 141 | ); 142 | 143 | setTimeout(() => { 144 | this.log.info("Disabling motion detection on camera", camera.displayName); 145 | camera.motionDetected = !motionDetected; 146 | camera 147 | .getService(this.homebridgeApi.hap.Service.MotionSensor) 148 | ?.setCharacteristic( 149 | this.homebridgeApi.hap.Characteristic.MotionDetected, 150 | !motionDetected 151 | ); 152 | }, 10000); 153 | } 154 | 155 | default: 156 | this.log.info("event", event); 157 | } 158 | } 159 | 160 | startMonitoring() { 161 | this.hikVisionApi.startMonitoringEvents( 162 | this.processHikVisionEvent.bind(this) 163 | ); 164 | } 165 | } 166 | --------------------------------------------------------------------------------