diff --git a/plugins/hikvision/src/hikvision-camera-api.ts b/plugins/hikvision/src/hikvision-camera-api.ts
index c3a220fd6..b548ef6c5 100644
--- a/plugins/hikvision/src/hikvision-camera-api.ts
+++ b/plugins/hikvision/src/hikvision-camera-api.ts
@@ -1,5 +1,6 @@
import { Readable } from 'stream';
import AxiosDigestAuth from '@koush/axios-digest-auth';
+import { EventEmitter } from "stream";
export enum HikVisionCameraEvent {
MotionDetected = "VMD",
@@ -24,6 +25,8 @@ export interface HikVisionCameraStreamSetup {
export class HikVisionCameraAPI {
digestAuth: AxiosDigestAuth;
+ deviceModel : Promise;
+ listenerPromise : Promise;
constructor(public ip: string, username: string, password: string, public channel: string, public console: Console) {
this.digestAuth = new AxiosDigestAuth({
@@ -32,7 +35,42 @@ export class HikVisionCameraAPI {
});
}
+ async checkDeviceModel() : Promise {
+ if (!this.deviceModel) {
+ this.deviceModel = new Promise(async (resolve, reject) => {
+ try {
+ const response = await this.digestAuth.request({
+ method: "GET",
+ responseType: 'text',
+ url: `http://${this.ip}/ISAPI/System/deviceInfo`,
+ });
+ const deviceModel = response.data.match(/>(.*?)<\/model>/)?.[1];
+ resolve(deviceModel);
+ } catch (e) {
+ this.console.error('error checking NVR model', e);
+ resolve("unknown");
+ }
+ });
+ }
+ return await this.deviceModel;
+ }
+
+ async checkIsOldModel() : Promise {
+ // The old Hikvision DS-7608NI-E2 doesn't support channel capability checks, and the requests cause errors
+ const model = await this.checkDeviceModel();
+ return model.match(/DS-7608NI-E2/) != undefined;
+ }
+
async checkStreamSetup(): Promise {
+ const isOld = await this.checkIsOldModel();
+ if (isOld) {
+ this.console.error('NVR is old version. Defaulting camera capabilities to H.264/AAC');
+ return {
+ videoCodecType: "H.264",
+ audioCodecType: "AAC",
+ }
+ }
+
const response = await this.digestAuth.request({
method: "GET",
responseType: 'text',
@@ -67,30 +105,38 @@ export class HikVisionCameraAPI {
}
async listenEvents() {
- const url = `http://${this.ip}/ISAPI/Event/notification/alertStream`;
- this.console.log('listener url', url);
- const response = await this.digestAuth.request({
- method: "GET",
- url,
- responseType: 'stream',
- });
- const stream = response.data as Readable;
+ // support multiple cameras listening to a single single stream
+ if (!this.listenerPromise) {
+ this.listenerPromise = new Promise(async (resolve, reject) => {
+ const url = `http://${this.ip}/ISAPI/Event/notification/alertStream`;
+ this.console.log('listener url', url);
- stream.on('data', (buffer: Buffer) => {
- const data = buffer.toString();
- // this.console.log(data);
- for (const event of Object.values(HikVisionCameraEvent)) {
- if (data.indexOf(event) !== -1) {
- const cameraNumber = data.match(/(.*?))?.[1] || data.match(/(.*?))?.[1];
- if (this.channel
- && data.indexOf(`${this.channel.substr(0, 1)}`) === -1) {
- continue;
+ const response = await this.digestAuth.request({
+ method: "GET",
+ url,
+ responseType: 'stream',
+ });
+ const stream = response.data as Readable;
+
+ stream.on('data', (buffer: Buffer) => {
+ const data = buffer.toString();
+ // this.console.log(data);
+ for (const event of Object.values(HikVisionCameraEvent)) {
+ if (data.indexOf(event) !== -1) {
+ const cameraNumber = data.match(/(.*?))?.[1] || data.match(/(.*?))?.[1];
+ if (this.channel
+ && data.indexOf(`${this.channel.substr(0, 1)}`) === -1) {
+ continue;
+ }
+ stream.emit('event', event, cameraNumber);
+ }
}
- stream.emit('event', event, cameraNumber);
- }
- }
- });
+ });
+ resolve(stream);
+ });
+ }
- return stream;
+ const eventSource = await this.listenerPromise;
+ return eventSource;
}
}
diff --git a/plugins/hikvision/src/main.ts b/plugins/hikvision/src/main.ts
index f867200f0..a6a7ec5fb 100644
--- a/plugins/hikvision/src/main.ts
+++ b/plugins/hikvision/src/main.ts
@@ -26,13 +26,20 @@ class HikVisionCamera extends RtspSmartCamera implements Camera {
events.on('close', () => ret.emit('error', new Error('close')));
events.on('error', e => ret.emit('error', e));
events.on('event', (event: HikVisionCameraEvent, cameraNumber: string) => {
- // if (this.getRtspChannel() && cameraNumber !== this.getCameraNumber()) {
- // return;
- // }
if (event === HikVisionCameraEvent.MotionDetected
|| event === HikVisionCameraEvent.LineDetection
|| event === HikVisionCameraEvent.FieldDetection) {
- this.motionDetected = true;
+ if (this.getRtspChannel() && cameraNumber !== this.getCameraNumber()) {
+ // this.console.error(`### Skipping motion event ${cameraNumber} != ${this.getCameraNumber()}`);
+ return;
+ }
+
+ // this.console.error('### Detected motion, camera: ', cameraNumber);
+ const prevMotion = this.motionDetected;
+ if (!prevMotion) {
+ this.motionDetected = true;
+ this.console.log(`motionDetected, camera ${this.getCameraNumber()}`);
+ }
clearTimeout(motionTimeout);
motionTimeout = setTimeout(() => this.motionDetected = false, 30000);
}
@@ -57,7 +64,7 @@ class HikVisionCamera extends RtspSmartCamera implements Camera {
this.log.a(`This camera is configured for ${streamSetup.videoCodecType} on the main channel. Configuring it it for H.264 is recommended for optimal performance.`);
}
if (!this.isAudioDisabled() && streamSetup.audioCodecType && streamSetup.audioCodecType !== 'AAC') {
- this.log.a(`This camera is configured for ${streamSetup.audioCodecType} on the main channel. Configuring it it for AAC is recommended for optimal performance.`);
+ this.log.a(`This camera is configured for ${streamSetup.audioCodecType} on the main channel. Configuring it for AAC is recommended for optimal performance.`);
}
})();
@@ -116,8 +123,12 @@ class HikVisionCamera extends RtspSmartCamera implements Camera {
if (!this.channelIds) {
const client = this.createClient();
this.channelIds = new Promise(async (resolve, reject) => {
- try {
-
+ const isOld = await client.checkIsOldModel();
+ if (isOld) {
+ this.console.error('Old NVR. Defaulting to two camera configuration');
+ const camNumber = this.getCameraNumber() || '1';
+ resolve([camNumber + '01', camNumber + '02']);
+ } else try {
const response = await client.digestAuth.request({
url: `http://${this.getHttpAddress()}/ISAPI/Streaming/channels`,
responseType: 'text',