mirror of
https://github.com/koush/scrypted.git
synced 2026-07-09 08:30:39 +01:00
Support for old Hikvision NVR by suppressing unsupported requests and limiting number of alert streams
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Readable } from 'stream';
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { EventEmitter } from "stream";
|
||||
|
||||
export enum HikVisionCameraEvent {
|
||||
MotionDetected = "<eventType>VMD</eventType>",
|
||||
@@ -24,6 +25,8 @@ export interface HikVisionCameraStreamSetup {
|
||||
|
||||
export class HikVisionCameraAPI {
|
||||
digestAuth: AxiosDigestAuth;
|
||||
deviceModel : Promise<string>;
|
||||
listenerPromise : Promise<EventEmitter>;
|
||||
|
||||
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<string> {
|
||||
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<boolean> {
|
||||
// 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<HikVisionCameraStreamSetup> {
|
||||
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(/<channelID>(.*?)</)?.[1] || data.match(/<dynChannelID>(.*?)</)?.[1];
|
||||
if (this.channel
|
||||
&& data.indexOf(`<channelID>${this.channel.substr(0, 1)}</channelID>`) === -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(/<channelID>(.*?)</)?.[1] || data.match(/<dynChannelID>(.*?)</)?.[1];
|
||||
if (this.channel
|
||||
&& data.indexOf(`<channelID>${this.channel.substr(0, 1)}</channelID>`) === -1) {
|
||||
continue;
|
||||
}
|
||||
stream.emit('event', event, cameraNumber);
|
||||
}
|
||||
}
|
||||
stream.emit('event', event, cameraNumber);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
resolve(stream);
|
||||
});
|
||||
}
|
||||
|
||||
return stream;
|
||||
const eventSource = await this.listenerPromise;
|
||||
return eventSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user