homebridge-hikvision-local
Version:
Homebridge plugin that connects to your HikVision DVR via a local connection and exposes your cameras in Homebridge. The plugin is heavily based on excellent [homebridge-camera-ffmpeg](https://github.com/Sunoo/homebridge-camera-ffmpeg) and the various hom
224 lines • 10.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HikvisionApi = void 0;
// require('axios-debug-log');
const https_1 = __importDefault(require("https"));
const ts_axios_digest_auth_1 = require("@lukesthl/ts-axios-digest-auth");
const axios_1 = __importDefault(require("axios"));
const xml2js_1 = __importStar(require("xml2js"));
class HikvisionApi {
constructor(config, log) {
this.connected = false;
this._baseURL = `http${config.secure ? 's' : ''}://${config.host}`;
this._http = new ts_axios_digest_auth_1.AxiosDigestAuth({
username: config.username,
password: config.password,
axios: axios_1.default.create({
httpsAgent: new https_1.default.Agent({
rejectUnauthorized: !config.ignoreInsecureTls,
}),
timeout: 8000,
}),
});
this._httpStream = new ts_axios_digest_auth_1.AxiosDigestAuth({
username: config.username,
password: config.password,
axios: axios_1.default.create({
httpsAgent: new https_1.default.Agent({
rejectUnauthorized: !config.ignoreInsecureTls,
}),
}),
});
this._parser = new xml2js_1.Parser({ explicitArray: false });
this.log = log;
}
/*
"DeviceInfo": {
"$": {
"version": "2.0",
"xmlns": "http://www.isapi.org/ver20/XMLSchema"
},
"deviceName": "Network Video Recorder",
"deviceID": "48443030-3637-3534-3837-f84dfcf8ef1c",
"model": "DS-7608NI-I2/8P",
"serialNumber": "DS-7608NI-I2/8P0820190316CCRRD00675487WCVU",
"macAddress": "f8:4d:fc:f8:ef:1c",
"firmwareVersion": "V4.22.005",
"firmwareReleasedDate": "build 191208",
"encoderVersion": "V5.0",
"encoderReleasedDate": "build 191208",
"deviceType": "NVR",
"telecontrolID": "255"
}
*/
getSystemInfo() {
return __awaiter(this, void 0, void 0, function* () {
return this._getResponse('/ISAPI/System/deviceInfo');
});
}
getCameras() {
return __awaiter(this, void 0, void 0, function* () {
const channels = yield this._getResponse('/ISAPI/System/Video/inputs/channels');
if (channels.VideoInputChannelList) {
for (let i = 0; i < channels.VideoInputChannelList.VideoInputChannel.length; i++) {
const channel = channels.VideoInputChannelList.VideoInputChannel[i];
if (channel.resDesc !== 'NO VIDEO') {
channel.capabilities = yield this._getResponse(`/ISAPI/ContentMgmt/StreamingProxy/channels/${channel.id}01/capabilities`);
}
channel.status = { online: channel.resDesc !== 'NO VIDEO' };
}
return channels.VideoInputChannelList.VideoInputChannel.filter((camera) => camera.status.online);
}
else {
const channels2 = yield this._getResponse('/ISAPI/ContentMgmt/InputProxy/channels');
for (let i = 0; i < channels2.InputProxyChannelList.InputProxyChannel.length; i++) {
const channel = channels2.InputProxyChannelList.InputProxyChannel[i];
if (channel.resDesc !== 'NO VIDEO') {
channel.capabilities = yield this._getResponse(`/ISAPI/ContentMgmt/StreamingProxy/channels/${channel.id}01/capabilities`);
}
channel.status = { online: channel.resDesc !== 'NO VIDEO' };
}
return channels2.InputProxyChannelList.InputProxyChannel.filter((camera) => camera.status.online);
}
});
}
startMonitoringEvents(callback) {
return __awaiter(this, void 0, void 0, function* () {
this.log.info('Starting event monitoring...');
const url = '/ISAPI/Event/notification/alertStream';
const xmlParser = new xml2js_1.default.Parser({
explicitArray: false,
});
const startStream = () => __awaiter(this, void 0, void 0, function* () {
try {
const response = yield this.getStream(url, {
responseType: 'stream',
headers: { 'Content-Type': 'multipart/form-data' },
});
this.log.info(`Event Monitoring Connection Status ${response === null || response === void 0 ? void 0 : response.status} -> ${response === null || response === void 0 ? void 0 : response.statusText}`);
if ((response === null || response === void 0 ? void 0 : response.status) !== 200) {
throw new Error(`Failed to start stream, retrying... ${response === null || response === void 0 ? void 0 : response.status} -> ${response === null || response === void 0 ? void 0 : response.statusText}`);
}
else {
const stream = response === null || response === void 0 ? void 0 : response.data;
// eslint-disable-next-line @typescript-eslint/no-use-before-define
handleStream(stream);
}
}
catch (error) {
this.log.error(`Failed to start stream, retrying... ${error}`);
setTimeout(startStream, 5000); // Retry after 5 seconds
}
});
const handleStream = (stream) => __awaiter(this, void 0, void 0, function* () {
stream.on('error', (error) => {
this.log.error(`Stream error, restarting connection...${error}`);
startStream();
});
// stream.on('end', () => {
// this.log.info('Stream ended, restarting connection...');
// startStream();
// });
stream.on('close', () => {
this.log.info('Stream closed, restarting connection...');
startStream();
});
stream.on('finish', () => {
this.log.info('Stream finished, restarting connection...');
startStream();
});
stream.on('pause', () => {
this.log.debug('stream PAUSE');
});
stream.on('resume', () => {
this.log.debug('stream RESUME');
});
stream.on('unpipe', () => {
this.log.debug('stream UNPIPE');
});
stream.on('data', (data) => __awaiter(this, void 0, void 0, function* () {
data = data.toString();
// console.log('DATA', data);
if (data.includes('<EventNotificationAlert')) {
const message = data.slice(data.indexOf('<EventNotificationAlert'));
const eventMsg = yield xmlParser.parseStringPromise(message);
callback(eventMsg);
}
}));
});
startStream();
});
}
getStream(url, config) {
return __awaiter(this, void 0, void 0, function* () {
try {
// this.log.debug('GET', this._baseURL + url, config);
return yield this._httpStream.get(this._baseURL + url, config);
}
catch (e) {
this.log.error(`ERROR: getStream ${this._baseURL + url} -> ${config} ${e}`);
}
});
}
_getResponse(path) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
try {
// this.log.debug(`_getResponse ${this._baseURL + path}`);
const response = yield ((_a = this._http) === null || _a === void 0 ? void 0 : _a.get(this._baseURL + path, {
validateStatus: function (status) {
if (status !== 401) {
return true; // Resolve only if the status code is less than 500
}
else {
return false;
}
},
}));
const responseJson = yield ((_b = this._parser) === null || _b === void 0 ? void 0 : _b.parseStringPromise(response === null || response === void 0 ? void 0 : response.data));
this.connected = true;
return responseJson;
}
catch (e) {
this.log.error(`ERROR: _getResponse ${this._baseURL + path} -> ${e.message}`);
}
});
}
}
exports.HikvisionApi = HikvisionApi;
//# sourceMappingURL=HikvisionApi.js.map