UNPKG

onvif-nvt

Version:

Wrapper for ONVIF spec to control NVT (Network Video Transitter) devices.

278 lines (240 loc) 8.34 kB
const Util = require('../utils/util'); const Soap = require('../utils/soap'); const dgram = require('dgram'); class Discovery { constructor() { this.soap = new Soap(); this._MULTICAST_ADDRESS = '239.255.255.250'; this._PORT = 3702; this._DISCOVERY_INTERVAL = 150; this._DISCOVERY_RETRY_MAX = 3; this._DISCOVERY_WAIT = 3000; this._udp = null; this._discoveryIntervalTimer = null; this._discoveryWaitTimer = null; } startProbe(callback) { const promise = new Promise((resolve, reject) => { let errMsg = ''; if (typeof callback !== 'undefined' && callback !== null) { if (errMsg = Util.isInvalidValue(callback, 'function')) { reject(new Error('The "callback" argument for startProbe is invalid:' + errMsg)); return; } } this._devices = {}; this._udp = dgram.createSocket('udp4'); this._udp.once('error', error => { reject(error); }); this._udp.on('message', (buf, deviceInfo) => { this.soap.parse(buf.toString()).then(results => { this.parseResult(results, deviceInfo); }).catch(error => { console.error(error); }); }); this._udp.bind(() => { this._udp.removeAllListeners('error'); this._sendProbe().then(() => {}).catch(error => { reject(error); }); this._discoveryWaitTimer = setTimeout(() => { this.stopProbe().then(() => { const deviceList = []; Object.keys(this._devices).forEach(urn => { deviceList.push(this._devices[urn]); }); resolve(deviceList); }).catch(error => { reject(error); }); }, this._DISCOVERY_WAIT); }); }); if (Util.isValidCallback(callback)) { promise.then(deviceList => { callback(null, deviceList); }).catch(error => { callback(error); }); } else { return promise; } } stopProbe(callback) { if (this._discoveryIntervalTimer !== null) { clearTimeout(this._discoveryIntervalTimer); this._discoveryIntervalTimer = null; } if (this._discoveryWaitTimer !== null) { clearTimeout(this._discoveryWaitTimer); this._discoveryWaitTimer = null; } const promise = new Promise((resolve, reject) => { let errMsg = ''; if (typeof callback !== 'undefined' && callback !== null) { if (errMsg = Util.isInvalidValue(callback, 'function')) { reject(new Error('The "callback" argument for stopProbe is invalid:' + errMsg)); return; } } if (this._udp) { this._udp.close(() => { if (this._udp) { this._udp.unref(); } this._udp = null; resolve(); }); } else { resolve(); } }); if (Util.isValidCallback(callback)) { promise.then(() => { callback(null); }).catch(error => { callback(error); }); } else { return promise; } } _sendProbe(callback) { let soapTemplate = ''; soapTemplate += '<?xml version="1.0" encoding="UTF-8"?>'; soapTemplate += '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">'; soapTemplate += ' <s:Header>'; soapTemplate += ' <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>'; soapTemplate += ' <a:MessageID>uuid:__uuid__</a:MessageID>'; soapTemplate += ' <a:ReplyTo>'; soapTemplate += ' <a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>'; soapTemplate += ' </a:ReplyTo>'; soapTemplate += ' <a:To s:mustUnderstand="1">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>'; soapTemplate += ' </s:Header>'; soapTemplate += ' <s:Body>'; soapTemplate += ' <Probe xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery">'; soapTemplate += ' <d:Types xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dp0="http://www.onvif.org/ver10/network/wsdl">dp0:__type__</d:Types>'; soapTemplate += ' </Probe>'; soapTemplate += ' </s:Body>'; soapTemplate += '</s:Envelope>'; soapTemplate = soapTemplate.replace(/\>\s+\</g, '><'); soapTemplate = soapTemplate.replace(/\s+/, ' '); const soapSet = []; ['NetworkVideoTransmitter', 'Device', 'NetworkVideoDisplay'].forEach(type => { let s = soapTemplate; s = s.replace('__type__', type); s = s.replace('__uuid__', Util.createUuidV4()); soapSet.push(s); }); const soapList = []; for (let i = 0; i < this._DISCOVERY_RETRY_MAX; i++) { soapSet.forEach(s => { soapList.push(s); }); } const promise = new Promise((resolve, reject) => { const send = () => { if (this._udp) { const soapEnvelope = soapList.shift(); if (soapEnvelope) { const buf = Buffer.from(soapEnvelope, 'utf8'); this._udp.send(buf, 0, buf.length, this._PORT, this._MULTICAST_ADDRESS, (error, bytes) => { if (error) { console.error(error); } this._discoveryIntervalTimer = setTimeout(() => { send(); }, this._DISCOVERY_INTERVAL); }); } else { resolve(); } } else { reject(new Error('No UDP connection is available. The init() method might not be called yet.')); } }; send(); }); return promise; } parseResult(results, deviceInfo) { const parsed = results.parsed; let urn = ''; const address = deviceInfo.address; let service = ''; let xaddrs = []; let scopes = []; let types = ''; let probe = {}; try { if ('Body' in parsed) { const body = parsed.Body; if ('ProbeMatches' in body) { const probeMatches = body.ProbeMatches; if (probeMatches !== undefined) { if ('ProbeMatch' in probeMatches) { const probeMatch = probeMatches.ProbeMatch; urn = probeMatch.EndpointReference.Address; xaddrs = probeMatch.XAddrs.split(/\s+/); if (xaddrs.length > 1) { xaddrs.forEach(addr => { const index = addr.indexOf(deviceInfo.address); if (index !== -1) { service = addr; } }); } else { service = xaddrs[0]; } if (typeof probeMatch.Scopes === 'string') { scopes = probeMatch.Scopes.split(/\s+/); } else if (typeof probeMatch.Scopes === 'object' && typeof probeMatch.Scopes._ === 'string') { scopes = probeMatch.Scopes._.split(/\s+/); } if (typeof probeMatch.Types === 'string') { types = probeMatch.Types.split(/\s+/); } else if (typeof probeMatch.Types === 'object' && typeof probeMatch.Types._ === 'string') { types = probeMatch.Types._.split(/\s+/); } } } } } } catch (e) { return null; } if (urn && xaddrs.length > 0 && scopes.length > 0) { if (!this._devices[urn]) { let name = ''; let hardware = ''; let location = ''; scopes.forEach(s => { if (s.indexOf('onvif://www.onvif.org/hardware/') === 0) { hardware = s.split('/').pop(); } else if (s.indexOf('onvif://www.onvif.org/location/') === 0) { location = s.split('/').pop(); } else if (s.indexOf('onvif://www.onvif.org/name/') === 0) { name = s.split('/').pop(); name = name.replace(/_/g, ' '); } }); probe = { urn: urn, name: name, address: address, service: service, hardware: hardware, location: location, types: types, xaddrs: xaddrs, scopes: scopes }; this._devices[urn] = probe; } } return probe; } } module.exports = Discovery;