From a470895568c4177c7949aeb58231ad9994a6d1da Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 12 Feb 2019 14:26:15 -0500 Subject: [PATCH 01/44] Change code such that one can specify a NIC on which to bind - rather than promiscously using all NICs or an aribtrary NIC --- lib/node-onvif.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/node-onvif.js b/lib/node-onvif.js index 48b593d..f87906d 100644 --- a/lib/node-onvif.js +++ b/lib/node-onvif.js @@ -54,7 +54,7 @@ Onvif.prototype.startDiscovery = function(callback) { /* ------------------------------------------------------------------ * Method: startProbe([callback]) * ---------------------------------------------------------------- */ -Onvif.prototype.startProbe = function(callback) { +Onvif.prototype.startProbe = function(options, callback) { let promise = new Promise((resolve, reject) => { this._devices = {}; this._udp = mDgram.createSocket('udp4'); @@ -125,7 +125,26 @@ Onvif.prototype.startProbe = function(callback) { }); }); - this._udp.bind(() => { + // J. UDALL. CHANGE FROM ORIGINAL CODE TO SPECIFY NIC + let bindAddress = '0.0.0.0' + if (options == null || options.device == null){ + throw("Mandatory options.device is missing") + } else { + const os = require('os') + const interfaces = os.networkInterfaces() + // Try to find the interface based on the device name + if (options.device in interfaces) { + interfaces[options.device].some(function(address) { + // Only use IPv4 addresses + if (address.family === 'IPv4') { + bindAddress = address.address + return true + } + }) + } else + throw("NIC '" + options.device + "' not found") + } + this._udp.bind(null,bindAddress,() => { this._udp.removeAllListeners('error'); this._sendProbe().then(() => { // Do nothing. @@ -252,6 +271,7 @@ Onvif.prototype.stopDiscovery = function(callback) { /* ------------------------------------------------------------------ * Method: stopProbe([callback]) * ---------------------------------------------------------------- */ + Onvif.prototype.stopProbe = function(callback) { if(this._discovery_interval_timer !== null) { clearTimeout(this._discovery_interval_timer); From 3088cef7ef713c0a847470551637135f9275162a Mon Sep 17 00:00:00 2001 From: "judall@airvm.com" Date: Wed, 27 Feb 2019 13:39:24 +0000 Subject: [PATCH 02/44] Implement setNetworkInterfaces method --- lib/modules/service-device.js | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index c1c50b4..a06916a 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -612,6 +612,52 @@ OnvifServiceDevice.prototype.getNetworkInterfaces = function(callback) { } }; + +/* ------------------------------------------------------------------ +* Method: setNetworkInterfaces([callback]) +* ---------------------------------------------------------------- */ +OnvifServiceDevice.prototype.setNetworkInterfaces = function(info,callback) { + //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.$.token + //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.IPv4.enabled = true + //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.IPv4.Config.DHCP = false + //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.IPv4.Config.Manual.Address = req.body.newip + //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.IPv4.Config.Manual.PrefixLength = "24" + let promise = new Promise((resolve, reject) => { + let soap_body = ''; + soap_body += ''; + soap_body += '' + info.interface.data.GetNetworkInterfacesResponse.NetworkInterfaces.$.token + '' + soap_body += '' + soap_body += 'true' + soap_body += '1500' + soap_body += '' + soap_body += 'true' + soap_body += '' + soap_body += '' + info.ip + '' + soap_body += '24' + soap_body += '' + soap_body += 'false' + soap_body += '' + soap_body += '' + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + mOnvifSoap.requestCommand(this.oxaddr, 'SetNetworkInterfaces', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + + /* ------------------------------------------------------------------ * Method: getNetworkProtocols([callback]) * ---------------------------------------------------------------- */ From d45ee869168a2db86bea085321ff139add3a4342 Mon Sep 17 00:00:00 2001 From: "judall@airvm.com" Date: Wed, 27 Feb 2019 21:54:28 +0000 Subject: [PATCH 03/44] Some cameras don't like the top-level namespace. So change to grotty method --- lib/modules/service-device.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index a06916a..3b65a3d 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -624,20 +624,20 @@ OnvifServiceDevice.prototype.setNetworkInterfaces = function(info,callback) { //info.data.GetNetworkInterfacesResponse.NetworkInterfaces.IPv4.Config.Manual.PrefixLength = "24" let promise = new Promise((resolve, reject) => { let soap_body = ''; - soap_body += ''; - soap_body += '' + info.interface.data.GetNetworkInterfacesResponse.NetworkInterfaces.$.token + '' - soap_body += '' - soap_body += 'true' - soap_body += '1500' - soap_body += '' - soap_body += 'true' - soap_body += '' - soap_body += '' + info.ip + '' - soap_body += '24' - soap_body += '' - soap_body += 'false' - soap_body += '' - soap_body += '' + soap_body += ''; + soap_body += '' + info.interface.data.GetNetworkInterfacesResponse.NetworkInterfaces.$.token + '' + soap_body += '' + soap_body += 'true' + //soap_body += '1500' + soap_body += '' + soap_body += 'true' + soap_body += '' + soap_body += '
' + info.ip + '
' + soap_body += '24' + soap_body += '
' + soap_body += 'false' + soap_body += '
' + soap_body += '
' soap_body += '
'; let soap = this._createRequestSoap(soap_body); mOnvifSoap.requestCommand(this.oxaddr, 'SetNetworkInterfaces', soap).then((result) => { From 9ad0609ad0a45b9d423e4b9fd645c1f5b9775b6e Mon Sep 17 00:00:00 2001 From: "judall@airvm.com" Date: Thu, 28 Feb 2019 21:39:14 +0000 Subject: [PATCH 04/44] Implement some ONVIF methods currently unavailable in public version of this library --- lib/modules/device.js | 3 +- lib/modules/service-device.js | 35 ++++++ lib/modules/service-imaging.js | 203 +++++++++++++++++++++++++++++++++ lib/modules/service-media.js | 85 ++++++++++++++ 4 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 lib/modules/service-imaging.js diff --git a/lib/modules/device.js b/lib/modules/device.js index 29c0522..ec4726c 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -14,6 +14,7 @@ const mEventEmitter = require('events').EventEmitter; const mOnvifServiceDevice = require('./service-device.js'); const mOnvifServiceMedia = require('./service-media.js'); const mOnvifServicePtz = require('./service-ptz.js'); +const mOnvifServiceImaging = require('./service-imaging.js'); const mOnvifServiceEvents = require('./service-events.js'); const mOnvifHttpAuth = require('./http-auth.js'); @@ -412,14 +413,12 @@ OnvifDevice.prototype._getCapabilities = function () { } let imaging = c['Imaging']; if (imaging && imaging['XAddr']) { - /* this.services.imaging = new mOnvifServiceImaging({ 'xaddr' : imaging['XAddr'], 'time_diff': this.time_diff, 'user' : this.user, 'pass' : this.pass }); - */ } let media = c['Media']; if (media && media['XAddr']) { diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index 3b65a3d..e612af8 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -172,6 +172,41 @@ OnvifServiceDevice.prototype.getDiscoveryMode = function(callback) { } }; +/* ------------------------------------------------------------------ +* Method: setDiscoveryMode(mode, callback) +* - mode | String | required | 'Discoverable' or 'NonDiscoverable' +* +* }* ---------------------------------------------------------------- */ +OnvifServiceDevice.prototype.setDiscoveryMode = function(mode, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(mode, 'string') + || (mode != 'Discoverable' && mode != 'NonDiscoverable')) { + reject(new Error('The value of "mode" was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += '' + mode + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + mOnvifSoap.requestCommand(this.oxaddr, 'SetDiscoveryMode', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getScopes([callback]) * ---------------------------------------------------------------- */ diff --git a/lib/modules/service-imaging.js b/lib/modules/service-imaging.js new file mode 100644 index 0000000..6e3a494 --- /dev/null +++ b/lib/modules/service-imaging.js @@ -0,0 +1,203 @@ +/* ------------------------------------------------------------------ +* node-onvif - service-imaging.js +* +* Copyright (c) 2016 - 2017, Futomi Hatano, All rights reserved. +* Released under the MIT license +* Date: 2017-08-30 +* ---------------------------------------------------------------- */ +'use strict'; +const mUrl = require('url'); +const mOnvifSoap = require('./soap.js'); + +/* ------------------------------------------------------------------ +* Constructor: OnvifServiceImaging(params) +* - params: +* - xaddr : URL of the entry point for the imaging service +* (Required) +* - user : User name (Optional) +* - pass : Password (Optional) +* - time_diff: ms +* ---------------------------------------------------------------- */ +function OnvifServiceImaging(params) { + this.xaddr = ''; + this.user = ''; + this.pass = ''; + + let err_msg = ''; + + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + throw new Error('The value of "params" was invalid: ' + err_msg); + } + + if('xaddr' in params) { + if(err_msg = mOnvifSoap.isInvalidValue(params['xaddr'], 'string')) { + throw new Error('The "xaddr" property was invalid: ' + err_msg); + } else { + this.xaddr = params['xaddr']; + } + } else { + throw new Error('The "xaddr" property is required.'); + } + + if('user' in params) { + if(err_msg = mOnvifSoap.isInvalidValue(params['user'], 'string', true)) { + throw new Error('The "user" property was invalid: ' + err_msg); + } else { + this.user = params['user'] || ''; + } + } + + if('pass' in params) { + if(err_msg = mOnvifSoap.isInvalidValue(params['pass'], 'string', true)) { + throw new Error('The "pass" property was invalid: ' + err_msg); + } else { + this.pass = params['pass'] || ''; + } + } + + this.oxaddr = mUrl.parse(this.xaddr); + if(this.user) { + this.oxaddr.auth = this.user + ':' + this.pass; + } + + this.time_diff = params['time_diff']; + this.name_space_attr_list = [ + 'xmlns:ter="http://www.onvif.org/ver10/error"', + 'xmlns:xs="http://www.w3.org/2001/XMLSchema"', + 'xmlns:tt="http://www.onvif.org/ver10/schema"', + 'xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"' + ]; +} + +OnvifServiceImaging.prototype._createRequestSoap = function(body) { + let soap = mOnvifSoap.createRequestSoap({ + 'body': body, + 'xmlns': this.name_space_attr_list, + 'diff': this.time_diff, + 'user': this.user, + 'pass': this.pass + }); + return soap; +}; + +/* ------------------------------------------------------------------ +* Method: getImagingSettings(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a video token of the configuration +* +* { +* 'ConfigurationToken': 'Configuration1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceImaging.prototype.getImagingSettings = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetImagingSettings', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + +/* ------------------------------------------------------------------ +* Method: getImagingSettings(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a video token of the configuration +* - ImagingSettings | object | required | an ImagingSettings object +* { +* 'ConfigurationToken': 'Configuration1' +* 'ImagingSettings': '{object}' +* } +* ---------------------------------------------------------------- */ +OnvifServiceImaging.prototype.setImagingSettings = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['ImagingSettings'], 'object')) { + reject(new Error('The "ImagingSettings" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += '' + soap_body += '' + soap_body += '' + params.ImagingSettings.BacklightCompensation.Mode + '' + soap_body += '' + soap_body += '' + params.ImagingSettings.Brightness + '' + soap_body += '' + params.ImagingSettings.ColorSaturation + '' + soap_body += '' + params.ImagingSettings.Contrast + '' + soap_body += '' + soap_body += '' + params.ImagingSettings.Exposure.Mode + '' + soap_body += '' + params.ImagingSettings.Exposure.MinExposureTime + '' + soap_body += '' + params.ImagingSettings.Exposure.MaxExposureTime + '' + soap_body += '' + params.ImagingSettings.Exposure.MinGain + '' + soap_body += '' + params.ImagingSettings.Exposure.MaxGain + '' + soap_body += '' + soap_body += '' + params.ImagingSettings.IrCutFilter + '' + soap_body += '' + params.ImagingSettings.Sharpness + '' + soap_body += '' + soap_body += '' + params.ImagingSettings.WideDynamicRange.Mode + '' + soap_body += ''; + soap_body += '' + soap_body += '' + params.ImagingSettings.WhiteBalance.Mode + '' + soap_body += ''; + soap_body += 'true'; + soap_body += ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'SetImagingSettings', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + + +module.exports = OnvifServiceImaging; \ No newline at end of file diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index de1a316..8180efc 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -222,6 +222,91 @@ OnvifServiceMedia.prototype.getVideoEncoderConfiguration = function(params, call } }; +/* ------------------------------------------------------------------ +* Method: setVideoEncoderConfiguration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a token of the configuration +* - Configuration | Object | required | The new configuration object +* +* { +* 'ConfigurationToken': 'Configuration1' +* 'Configuration' : '{}' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['Configuration'], 'object')) { + reject(new Error('The "Configuration" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += ''; + soap_body += '' + params.Configuration.Name + ''; + soap_body += '' + params.Configuration.UseCount + ''; + soap_body += '' + params.Configuration.Encoding + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Resolution.Width + ''; + soap_body += '' + params.Configuration.Resolution.Height + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Quality + ''; + soap_body += ''; + soap_body += '' + params.Configuration.RateControl.FrameRateLimit + ''; + soap_body += '' + params.Configuration.RateControl.EncodingInterval + ''; + soap_body += '' + params.Configuration.RateControl.BitrateLimit + ''; + soap_body += ''; + soap_body += ''; + soap_body += '' + params.Configuration.H264.GovLength + ''; + soap_body += '' + params.Configuration.H264.H264Profile + ''; + soap_body += ''; + soap_body += ''; + soap_body += '
'; + soap_body += ''+ params.Configuration.Multicast.Address.Type + ''; + soap_body += '' + params.Configuration.Multicast.Address.IPv4Address + ''; + soap_body += '
'; + soap_body += '' + params.Configuration.Multicast.Port + ''; + soap_body += '' + params.Configuration.Multicast.TTL + ''; + soap_body += '' + params.Configuration.Multicast.AutoStart + ''; + soap_body += '
'; + soap_body += '' + params.Configuration.SessionTimeout + ''; + soap_body += '
'; + soap_body += 'true'; + soap_body += '
'; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'SetVideoEncoderConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getCompatibleVideoEncoderConfigurations(params[, callback]) * - params: From ba8191862383b4c04a5c8ea187153d56d64c8c15 Mon Sep 17 00:00:00 2001 From: "judall@airvm.com" Date: Fri, 1 Mar 2019 18:23:49 +0000 Subject: [PATCH 05/44] Change documentation for new methods I've added --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 3a7c328..4357349 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ This package includes a sample application "[ONVIF Network Camera Manager](https * [`getCapabilities([callback])` method](#OnvifServiceDevice-getCapabilities-method) * [`getWsdlUrl([callback])` method](#OnvifServiceDevice-getWsdlUrl-method) * [`getDiscoveryMode([callback])` method](#OnvifServiceDevice-getDiscoveryMode-method) + * [`setDiscoveryMode(mode,[callback])` method](#OnvifServiceDevice-setDiscoveryMode-method) * [`getScopes([callback])` method](#OnvifServiceDevice-getScopes-method) * [`setScopes(params[, callback])` method](#OnvifServiceDevice-setScopes-method) * [`addScopes(params[, callback])` method](#OnvifServiceDevice-addScopes-method) @@ -137,6 +138,7 @@ This package includes a sample application "[ONVIF Network Camera Manager](https * [`getPresets(params[, callback])` method](#OnvifServicePtz-getPresets-method) * [`gotoPreset(params[, callback])` method](#OnvifServicePtz-gotoPreset-method) * [`removePreset(params[, callback])` method](#OnvifServicePtz-removePreset-method) +* [`OnvifServiceImaging` object](#OnvifServiceImaging-object) * [References](#References) * [Release Note](#Release-Note) * [License](#License) @@ -600,6 +602,7 @@ Property | | Type | Description `services` | | Object | +- | `device` | Object | [`OnvifServiceDevice`](#OnvifServiceDevice-object) object +- | `media` | Object | [`OnvifServiceMedia`](#OnvifServiceMedia-object) object ++- | `imaging`| Object | [`OnvifServiceImaging`](#OnvifServiceImaging-object) object +- | `ptz` | Object | [`OnvifServicePtz`](#OnvifServicePtz-object) object These objects will be set when the initialization process is completed calling the [`init()`](#OnvifDevice-init-method) method. See the section "[ONVIF commands](#ONVIF-commands)" for details. @@ -1052,6 +1055,10 @@ This method sends a `GetWsdlUrl` command. This method sends a `GetDiscoveryMode` command. +### setDiscoveryMode(*[callback]*) method + +This method sends a `setDiscoveryMode` command. The 1st argument `mode` MUST be either 'Discoverable' or 'NonDiscoverable' + ### getScopes(*[callback]*) method This method sends a `GetScopes` command. @@ -1201,6 +1208,10 @@ return device.services.device.setDNS(params).then((result) => { This method sends a `GetNetworkProtocols` command. +### setNetworkInterfaces(*params[,callback]*) method + +This method sends a `GetNetworkProtocols` command. + ### getNetworkProtocols(*[callback]*) method This method sends a `GetNetworkProtocols` command. From 495dbe2d99fc8b09d65fd5e15d6bbc694ca3f057 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Fri, 8 Mar 2019 13:56:59 -0500 Subject: [PATCH 06/44] Add methods to add VideoSourceConfiguration to profile --- README.md | 8 ++ lib/modules/service-media.js | 141 +++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/README.md b/README.md index 4357349..09201d1 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ This package includes a sample application "[ONVIF Network Camera Manager](https * [`getCurrentProfile()` method](#OnvifDevice-getCurrentProfile-method) * [`getProfileList()` method](#OnvifDevice-getProfileList-method) * [`changeProfile(index|token)` method](#OnvifDevice-changeProfile-method) + * [`addVideoSourceConfiguration(index|token)` method](#OnvifDevice-addVideoSource-method) + * [`addVideoEncoderConfiguration(index|token)` method](#OnvifDevice-addVideoEncoder-method) * [`getUdpStreamUrl()` method](#OnvifDevice-getUdpStreamUrl-method) * [`fetchSnapshot(callback)` method](#OnvifDevice-fetchSnapshot-method) * [`ptzMove(params[, callback])` method](#OnvifDevice-ptzMove-method) @@ -815,7 +817,13 @@ This sample code will output the result like this: - Before: 1280 x 720 - After: 320 x 180 ``` +#### addVideoSourceConfiguration(*index|token*) +TBD + +#### addVideoEncoderConfiguration(*index|token*) + +TBD #### getUdpStreamUrl() This method returns the UDP Stream URL. Though the URL can be obtained from the result of the [`getCurrentProfile()`](#OnvifDevice-getCurrentProfile-method) method as well, this method makes that easy. diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 8180efc..2550c51 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -307,6 +307,147 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call } }; +/* ------------------------------------------------------------------ +* Method: addVideoEncoderConfiguration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a token of the configuration +* - Configuration | Object | required | The new configuration object +* +* { +* 'ConfigurationToken': 'Configuration1' +* 'Configuration' : '{}' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.addVideoEncoderConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['Configuration'], 'object')) { + reject(new Error('The "Configuration" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += ''; + soap_body += '' + params.Configuration.Name + ''; + soap_body += '' + params.Configuration.UseCount + ''; + soap_body += '' + params.Configuration.Encoding + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Resolution.Width + ''; + soap_body += '' + params.Configuration.Resolution.Height + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Quality + ''; + soap_body += ''; + soap_body += '' + params.Configuration.RateControl.FrameRateLimit + ''; + soap_body += '' + params.Configuration.RateControl.EncodingInterval + ''; + soap_body += '' + params.Configuration.RateControl.BitrateLimit + ''; + soap_body += ''; + soap_body += ''; + soap_body += '' + params.Configuration.H264.GovLength + ''; + soap_body += '' + params.Configuration.H264.H264Profile + ''; + soap_body += ''; + soap_body += ''; + soap_body += '
'; + soap_body += ''+ params.Configuration.Multicast.Address.Type + ''; + soap_body += '' + params.Configuration.Multicast.Address.IPv4Address + ''; + soap_body += '
'; + soap_body += '' + params.Configuration.Multicast.Port + ''; + soap_body += '' + params.Configuration.Multicast.TTL + ''; + soap_body += '' + params.Configuration.Multicast.AutoStart + ''; + soap_body += '
'; + soap_body += '' + params.Configuration.SessionTimeout + ''; + soap_body += '
'; + soap_body += 'true'; + soap_body += '
'; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'SetVideoEncoderConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; +/* ------------------------------------------------------------------ +* Method: addVideoSourceConfiguration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a token of the configuration +* - ProfileToken | String | required | The associate profile token +* +* { +* 'ConfigurationToken': 'Configuration1' +* 'ProfileToken' : 'Profile_1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.addVideoSourceConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += '' + params.ProfileToken + ''; + soap_body += '' + params.ConfigurationToken + ''; + soap_body += ''; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'AddVideoSourceConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getCompatibleVideoEncoderConfigurations(params[, callback]) * - params: From 85178055f07985a1d50a4c352723ee320b900e8a Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 20 Mar 2019 14:14:32 -0400 Subject: [PATCH 07/44] Remove deprecated new Buffer() call --- lib/modules/http-auth.js | 2 +- lib/modules/soap.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/http-auth.js b/lib/modules/http-auth.js index 779551a..6525e4e 100644 --- a/lib/modules/http-auth.js +++ b/lib/modules/http-auth.js @@ -90,7 +90,7 @@ OnvifHttpAuth.prototype._createAuthReqHeaderValue = function(o) { }; OnvifHttpAuth.prototype._createCnonce = function(digit) { - let nonce = new Buffer(digit); + let nonce = Buffer.alloc(digit); for(let i=0; i Date: Wed, 27 Mar 2019 16:03:51 -0400 Subject: [PATCH 08/44] When a startProbe is cancelled, there could still be a pending Timeout. Check before running the timeout function --- lib/node-onvif.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node-onvif.js b/lib/node-onvif.js index f87906d..01cdb64 100644 --- a/lib/node-onvif.js +++ b/lib/node-onvif.js @@ -228,7 +228,7 @@ Onvif.prototype._sendProbe = function(callback) { } let send = () => { let soap = soap_list.shift(); - if(soap) { + if(soap && this._udp) { let buf = Buffer.from(soap, 'utf8'); this._udp.send(buf, 0, buf.length, this._PORT, this._MULTICAST_ADDRESS, (error, bytes) => { this._discovery_interval_timer = setTimeout(() => { From 12df6d301a3ddaab586341597d74560c01c4f3c5 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Mon, 8 Apr 2019 16:24:45 -0400 Subject: [PATCH 09/44] edit addVideoEncoderConfiguration method --- lib/modules/service-media.js | 46 +++++++----------------------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 2550c51..7d5cb7b 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -311,11 +311,11 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call * Method: addVideoEncoderConfiguration(params[, callback]) * - params: * - ConfigurationToken | String | required | a token of the configuration -* - Configuration | Object | required | The new configuration object +* - ProfileToken | String | required | a token of the video encoder configuration * * { * 'ConfigurationToken': 'Configuration1' -* 'Configuration' : '{}' +* 'ProfileToken' : 'Encoder1' * } * ---------------------------------------------------------------- */ OnvifServiceMedia.prototype.addVideoEncoderConfiguration = function(params, callback) { @@ -330,52 +330,24 @@ OnvifServiceMedia.prototype.addVideoEncoderConfiguration = function(params, call reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['Configuration'], 'object')) { - reject(new Error('The "Configuration" property was invalid: ' + err_msg)); + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); return; } let soap_body = ''; try { - soap_body += ''; - soap_body += ''; - soap_body += '' + params.Configuration.Name + ''; - soap_body += '' + params.Configuration.UseCount + ''; - soap_body += '' + params.Configuration.Encoding + ''; - soap_body += ''; - soap_body += '' + params.Configuration.Resolution.Width + ''; - soap_body += '' + params.Configuration.Resolution.Height + ''; - soap_body += ''; - soap_body += '' + params.Configuration.Quality + ''; - soap_body += ''; - soap_body += '' + params.Configuration.RateControl.FrameRateLimit + ''; - soap_body += '' + params.Configuration.RateControl.EncodingInterval + ''; - soap_body += '' + params.Configuration.RateControl.BitrateLimit + ''; - soap_body += ''; - soap_body += ''; - soap_body += '' + params.Configuration.H264.GovLength + ''; - soap_body += '' + params.Configuration.H264.H264Profile + ''; - soap_body += ''; - soap_body += ''; - soap_body += '
'; - soap_body += ''+ params.Configuration.Multicast.Address.Type + ''; - soap_body += '' + params.Configuration.Multicast.Address.IPv4Address + ''; - soap_body += '
'; - soap_body += '' + params.Configuration.Multicast.Port + ''; - soap_body += '' + params.Configuration.Multicast.TTL + ''; - soap_body += '' + params.Configuration.Multicast.AutoStart + ''; - soap_body += '
'; - soap_body += '' + params.Configuration.SessionTimeout + ''; - soap_body += '
'; - soap_body += 'true'; - soap_body += '
'; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += '' + params['ProfileToken'] + ''; + soap_body += ''; } catch (err){ reject(new Error('Missing required configuration parammeters')); return; } let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'SetVideoEncoderConfiguration', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'AddVideoEncoderConfiguration', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); From a4f495f9b202a994d2d4afa2c96a835ee2b63082 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 17 Apr 2019 10:09:13 -0400 Subject: [PATCH 10/44] We were not returning the 'fixed' attribute on the profiles --- lib/modules/device.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/device.js b/lib/modules/device.js index ec4726c..be3f7f8 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -477,6 +477,7 @@ OnvifDevice.prototype._mediaGetProfiles = function () { profiles.forEach((p) => { let profile = { 'token': p['$']['token'], + 'fixed': p['$']['fixed'], 'name': p['Name'], 'snapshot': '', 'stream': { From 608fa1a7eeae1090ef9a8c4e0cfc467b0d1b6468 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 17 Apr 2019 10:30:14 -0400 Subject: [PATCH 11/44] Was ot converting string to boolean --- lib/modules/device.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index be3f7f8..51fa51c 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -477,7 +477,7 @@ OnvifDevice.prototype._mediaGetProfiles = function () { profiles.forEach((p) => { let profile = { 'token': p['$']['token'], - 'fixed': p['$']['fixed'], + 'fixed': p['$']['fixed'].toLowerCase() == 'true', 'name': p['Name'], 'snapshot': '', 'stream': { From f311174a1df534ac8e362cf7da165cfc4db25993 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Fri, 10 May 2019 08:43:23 -0400 Subject: [PATCH 12/44] Add new methods to remove video source and video encoder configurations --- lib/modules/service-media.js | 103 ++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 7d5cb7b..446635d 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -255,7 +255,7 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call soap_body += ''; soap_body += ''; soap_body += '' + params.Configuration.Name + ''; - soap_body += '' + params.Configuration.UseCount + ''; + //soap_body += '' + params.Configuration.UseCount + ''; soap_body += '' + params.Configuration.Encoding + ''; soap_body += ''; soap_body += '' + params.Configuration.Resolution.Width + ''; @@ -307,6 +307,57 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call } }; + +/* ------------------------------------------------------------------ +* Method: removeVideoEncoderConfiguration(params[, callback]) +* - params: +* - ProfileToken | Object | required | a token of the profile +* +* { +* 'ProfileToken' : 'Profile_1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.removeVideoEncoderConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += '' + params.ProfileToken + ''; + soap_body += ''; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'RemoveVideoEncoderConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: addVideoEncoderConfiguration(params[, callback]) * - params: @@ -420,6 +471,56 @@ OnvifServiceMedia.prototype.addVideoSourceConfiguration = function(params, callb } }; +/* ------------------------------------------------------------------ +* Method: removeVideoSourceConfiguration(params[, callback]) +* - params: +* - ProfileToken | String | required | The associate profile token +* +* { +* 'ProfileToken' : 'Profile_1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.removeVideoSourceConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += '' + params.ProfileToken + ''; + soap_body += ''; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'RemoveVideoSourceConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getCompatibleVideoEncoderConfigurations(params[, callback]) * - params: From 96a1736a515bacd63bbfdc20942d4dcc3fa86418 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 15 May 2019 09:47:29 -0400 Subject: [PATCH 13/44] SetVideoEncoderConfiguration: Some cameras don't like implicit namespaces. So add explicit namespace qualifiers --- lib/modules/service-media.js | 60 ++++++++++++++++++------------------ lib/modules/soap.js | 3 ++ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 446635d..763e139 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -253,36 +253,36 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call let soap_body = ''; try { soap_body += ''; - soap_body += ''; - soap_body += '' + params.Configuration.Name + ''; - //soap_body += '' + params.Configuration.UseCount + ''; - soap_body += '' + params.Configuration.Encoding + ''; - soap_body += ''; - soap_body += '' + params.Configuration.Resolution.Width + ''; - soap_body += '' + params.Configuration.Resolution.Height + ''; - soap_body += ''; - soap_body += '' + params.Configuration.Quality + ''; - soap_body += ''; - soap_body += '' + params.Configuration.RateControl.FrameRateLimit + ''; - soap_body += '' + params.Configuration.RateControl.EncodingInterval + ''; - soap_body += '' + params.Configuration.RateControl.BitrateLimit + ''; - soap_body += ''; - soap_body += ''; - soap_body += '' + params.Configuration.H264.GovLength + ''; - soap_body += '' + params.Configuration.H264.H264Profile + ''; - soap_body += ''; - soap_body += ''; - soap_body += '
'; - soap_body += ''+ params.Configuration.Multicast.Address.Type + ''; - soap_body += '' + params.Configuration.Multicast.Address.IPv4Address + ''; - soap_body += '
'; - soap_body += '' + params.Configuration.Multicast.Port + ''; - soap_body += '' + params.Configuration.Multicast.TTL + ''; - soap_body += '' + params.Configuration.Multicast.AutoStart + ''; - soap_body += '
'; - soap_body += '' + params.Configuration.SessionTimeout + ''; - soap_body += '
'; - soap_body += 'true'; + soap_body += ''; + soap_body += '' + params.Configuration.Name + ''; + soap_body += '' + params.Configuration.UseCount + ''; + soap_body += '' + params.Configuration.Encoding + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Resolution.Width + ''; + soap_body += '' + params.Configuration.Resolution.Height + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Quality + ''; + soap_body += ''; + soap_body += '' + params.Configuration.RateControl.FrameRateLimit + ''; + soap_body += '' + params.Configuration.RateControl.EncodingInterval + ''; + soap_body += '' + params.Configuration.RateControl.BitrateLimit + ''; + soap_body += ''; + soap_body += ''; + soap_body += '' + params.Configuration.H264.GovLength + ''; + soap_body += '' + params.Configuration.H264.H264Profile + ''; + soap_body += ''; + soap_body += ''; + soap_body += ''; + soap_body += ''+ params.Configuration.Multicast.Address.Type + ''; + soap_body += '' + params.Configuration.Multicast.Address.IPv4Address + ''; + soap_body += ''; + soap_body += '' + params.Configuration.Multicast.Port + ''; + soap_body += '' + params.Configuration.Multicast.TTL + ''; + soap_body += '' + params.Configuration.Multicast.AutoStart + ''; + soap_body += ''; + soap_body += '' + params.Configuration.SessionTimeout + ''; + soap_body += ''; + soap_body += 'true'; soap_body += '
'; } catch (err){ reject(new Error('Missing required configuration parammeters')); diff --git a/lib/modules/soap.js b/lib/modules/soap.js index acde366..cb1ae47 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -138,6 +138,9 @@ OnvifSoap.prototype._request = function(oxaddr, soap) { if(typeof(msg) === 'object') { msg = msg['_']; } + if (parsed['Body']['Fault']['Detail']){ + msg += ` (${parsed['Body']['Fault']['Detail']['Text']})` + } } catch(e) {} if(msg) { reject(new Error(code + ' ' + text + ' - ' + msg)); From d08697d3f22e74732dfdae4974d191cd8e7437ff Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 15 May 2019 14:45:37 -0400 Subject: [PATCH 14/44] GAG!!! One camera returns the wrong response. In the bowels of this code, I look for that response and try to ameliorate the issue --- lib/modules/soap.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/modules/soap.js b/lib/modules/soap.js index cb1ae47..fefec48 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -71,8 +71,28 @@ OnvifSoap.prototype.requestCommand = function(oxaddr, method_name, soap) { }; resolve(res); } else { - let err = new Error('The device seems to not support the ' + method_name + '() method.'); - reject(err); + + // JU: This sucks! The Arecont Vision camera provides a RemoveVideoEncoderConfigurationResponse + // rather than a RemoveVideoSourceConfigurationResponse + // So look for this abberation + if (method_name == 'RemoveVideoSourceConfiguration'){ + parsed = this._parseResponseResult('RemoveVideoEncoderConfiguration', result) + if (parsed){ + let res = { + 'soap' : xml, + 'formatted': mHtml ? mHtml.prettyPrint(xml, {indent_size: 2}) : '', + 'converted': result, + 'data': parsed + }; + resolve(res); + } else { + let err = new Error('The device seems to not support the ' + method_name + '() method.'); + reject(err); + } + } else { + let err = new Error('The device seems to not support the ' + method_name + '() method.'); + reject(err); + } } } }).catch((error) => { From 2d2718cfbfedca3c0aae5e019dd6be354ef2db4e Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 22 May 2019 07:29:10 -0400 Subject: [PATCH 15/44] Clean up error object --- lib/modules/soap.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/modules/soap.js b/lib/modules/soap.js index fefec48..9eed334 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -58,7 +58,9 @@ OnvifSoap.prototype.requestCommand = function(oxaddr, method_name, soap) { }).then((result) => { let fault = this._getFaultReason(result); if(fault) { - let err = new Error(fault); + let err = new Error(fault) + if (typeof fault == 'object' && fault._) + err = new Error(fault._) reject(err); } else { let parsed = this._parseResponseResult(method_name, result); From 0d026d86bd9ebd4db5b594631ed6d29a8e4d7ee5 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Fri, 14 Jun 2019 15:23:16 -0400 Subject: [PATCH 16/44] Change error message. The NIC could NOT exist, OR it could be unplugged --- lib/node-onvif.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node-onvif.js b/lib/node-onvif.js index 01cdb64..0de8e2b 100644 --- a/lib/node-onvif.js +++ b/lib/node-onvif.js @@ -142,7 +142,7 @@ Onvif.prototype.startProbe = function(options, callback) { } }) } else - throw("NIC '" + options.device + "' not found") + throw("NIC '" + options.device + "' not found or unplugged") } this._udp.bind(null,bindAddress,() => { this._udp.removeAllListeners('error'); From 6a67d86f1ff5625da9908813ebf874258c6c723e Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 7 Sep 2021 09:06:45 -0300 Subject: [PATCH 17/44] when sending GetSystemDateAndTime, omit username/password --- lib/modules/service-device.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index e612af8..b1bf418 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -66,15 +66,23 @@ function OnvifServiceDevice(params) { ]; } -OnvifServiceDevice.prototype._createRequestSoap = function(body) { - let soap = mOnvifSoap.createRequestSoap({ - 'body': body, - 'xmlns': this.name_space_attr_list, - 'diff': this.time_diff, - 'user': this.user, - 'pass': this.pass - }); - return soap; +OnvifServiceDevice.prototype._createRequestSoap = function(body,omitAuthorization) { + if (omitAuthorization){ + let soap = mOnvifSoap.createRequestSoap({ + 'body': body, + 'xmlns': this.name_space_attr_list + }); + return soap; + } else { + let soap = mOnvifSoap.createRequestSoap({ + 'body': body, + 'xmlns': this.name_space_attr_list, + 'diff': this.time_diff, + 'user': this.user, + 'pass': this.pass + }); + return soap; + } }; /* ------------------------------------------------------------------ @@ -944,7 +952,7 @@ OnvifServiceDevice.prototype.getDeviceInformation = function(callback) { OnvifServiceDevice.prototype.getSystemDateAndTime = function(callback) { let promise = new Promise((resolve, reject) => { let soap_body = ''; - let soap = this._createRequestSoap(soap_body); + let soap = this._createRequestSoap(soap_body,true); mOnvifSoap.requestCommand(this.oxaddr, 'GetSystemDateAndTime', soap).then((result) => { let parsed = this._parseGetSystemDateAndTime(result['converted']); if(parsed && parsed['date']) { From d8843164460c2385a0533c2a9105b48a9f62374c Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 7 Sep 2021 09:47:13 -0300 Subject: [PATCH 18/44] Don't send authorization credentials on GetCapabilities SOAP request --- lib/modules/service-device.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index b1bf418..2d2b5f1 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -114,7 +114,7 @@ OnvifServiceDevice.prototype.getCapabilities = function(callback) { soap_body += ''; soap_body += ' All'; soap_body += ''; - let soap = this._createRequestSoap(soap_body); + let soap = this._createRequestSoap(soap_body,true); mOnvifSoap.requestCommand(this.oxaddr, 'GetCapabilities', soap).then((result) => { resolve(result); }).catch((error) => { From f7e1aaa2b5bfdc43d80ee18a3acbbda551fecc2a Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Fri, 10 Sep 2021 09:12:13 -0300 Subject: [PATCH 19/44] If we can't getSystemDateAndTime from device, then abort --- lib/modules/device.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index 51fa51c..4afa7ee 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -39,7 +39,10 @@ function OnvifDevice(params) { this.user = ''; this.pass = ''; this.keepAddr = false; + this.onvifCompliant = 'no'; + this.isAddressable = true; this.lastResponse = null; // for debug + this.lastError = null; if (('xaddr' in params) && typeof (params['xaddr']) === 'string') { this.xaddr = params['xaddr']; @@ -358,6 +361,11 @@ OnvifDevice.prototype.init = function (callback) { let info = this.getInformation(); resolve(info); }).catch((error) => { + + // If we can't even talk to this guy, then abort - but mark the guy as unaddressable + if (this.lastError.toString().search(/Network Error/i) >= 0){ + this.isAddressable = false; + } reject(error); }); }); @@ -376,13 +384,17 @@ OnvifDevice.prototype.init = function (callback) { OnvifDevice.prototype._getSystemDateAndTime = function () { let promise = new Promise((resolve, reject) => { this.services.device.getSystemDateAndTime((error, result) => { - // Ignore the error becase some devices do not support - // the GetSystemDateAndTime command and the error does - // not cause any trouble. - if (!error) { + this.lastError = error; + this.lastResponse = result; + + // If no error, then use result to calculate the time difference between us + // That delta will be used in NONCE creation for subsequent authorized calls + if (!error){ this.time_diff = this.services.device.getTimeDiff(); - } - resolve(); + this.onvifCompliant = 'yes' + resolve() + } else + reject(error) }); }); return promise; @@ -392,11 +404,13 @@ OnvifDevice.prototype._getSystemDateAndTime = function () { OnvifDevice.prototype._getCapabilities = function () { let promise = new Promise((resolve, reject) => { this.services.device.getCapabilities((error, result) => { + this.lastError = error; this.lastResponse = result; if (error) { reject(new Error('Failed to initialize the device: ' + error.toString())); return; } + this.onvifCompliant = 'yes' let c = result['data']['GetCapabilitiesResponse']['Capabilities']; if (!c) { reject(new Error('Failed to initialize the device: No capabilities were found.')); @@ -448,6 +462,8 @@ OnvifDevice.prototype._getCapabilities = function () { OnvifDevice.prototype._getDeviceInformation = function () { let promise = new Promise((resolve, reject) => { this.services.device.getDeviceInformation((error, result) => { + this.lastError = error; + this.lastResponse = result; if (error) { reject(new Error('Failed to initialize the device: ' + error.toString())); } else { @@ -463,6 +479,7 @@ OnvifDevice.prototype._getDeviceInformation = function () { OnvifDevice.prototype._mediaGetProfiles = function () { let promise = new Promise((resolve, reject) => { this.services.media.getProfiles((error, result) => { + this.lastError = error; this.lastResponse = result; if (error) { reject(new Error('Failed to initialize the device: ' + error.toString())); @@ -604,6 +621,7 @@ OnvifDevice.prototype._mediaGetStreamURI = function () { 'Protocol': protocol }; this.services.media.getStreamUri(params, (error, result) => { + this.lastError = error; this.lastResponse = result; if (!error) { let uri = result['data']['GetStreamUriResponse']['MediaUri']['Uri']; @@ -637,6 +655,7 @@ OnvifDevice.prototype._mediaGetSnapshotUri = function () { if (profile) { let params = { 'ProfileToken': profile['token'] }; this.services.media.getSnapshotUri(params, (error, result) => { + this.lastError = error; this.lastResponse = result; if (!error) { try { From ef40584e7aa0f12d0a837e8485daea95e52cd7da Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Mon, 20 Sep 2021 14:20:54 -0300 Subject: [PATCH 20/44] add media 2 ONVIF methods --- lib/modules/service-media.js | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 763e139..ac42102 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -222,6 +222,50 @@ OnvifServiceMedia.prototype.getVideoEncoderConfiguration = function(params, call } }; +/* ------------------------------------------------------------------ +* Method: getVideoEncoder2Configuration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | a token of the configuration +* +* { +* 'ConfigurationToken': 'Configuration1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getVideoEncoder2Configuration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetVideoEncoder2Configuration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; /* ------------------------------------------------------------------ * Method: setVideoEncoderConfiguration(params[, callback]) * - params: @@ -626,6 +670,66 @@ OnvifServiceMedia.prototype.getVideoEncoderConfigurationOptions = function(param } }; +/* ------------------------------------------------------------------ +* Method: getVideoEncoder2ConfigurationOptions(params[, callback]) +* - params: +* - ProfileToken | String | optional | a token of the profile +* - ConfigurationToken | String | optional | a token of the configuration +* +* { +* 'ProfileToken': 'Profile1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getVideoEncoder2ConfigurationOptions = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if('ProfileToken' in params) { + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + } + + if('ConfigurationToken' in params) { + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + } + + let soap_body = ''; + soap_body += ''; + if(params['ProfileToken']) { + soap_body += '' + params['ProfileToken'] + ''; + } + if(params['ConfigurationToken']) { + soap_body += '' + params['ConfigurationToken'] + ''; + } + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetVideoEncoder2ConfigurationOptions', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getGuaranteedNumberOfVideoEncoderInstances(params[, callback]) * - params: From 83342300723faafe629fa7f11558500d4aea0779 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 23 Sep 2021 10:29:49 -0300 Subject: [PATCH 21/44] Store the date/time from the camera on successful GetSystemDateAndTime --- lib/modules/device.js | 1 + lib/modules/service-device.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/modules/device.js b/lib/modules/device.js index 4afa7ee..eeeeb27 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -391,6 +391,7 @@ OnvifDevice.prototype._getSystemDateAndTime = function () { // That delta will be used in NONCE creation for subsequent authorized calls if (!error){ this.time_diff = this.services.device.getTimeDiff(); + this.date_time = this.services.device.getDateTime() this.onvifCompliant = 'yes' resolve() } else diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index 2d2b5f1..a118331 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -92,6 +92,12 @@ OnvifServiceDevice.prototype.getTimeDiff = function() { return this.time_diff; }; +/* ------------------------------------------------------------------ +* Method: getDateTime() +* ---------------------------------------------------------------- */ +OnvifServiceDevice.prototype.getDateTime = function() { + return this.date_time; +}; /* ------------------------------------------------------------------ * Method: setAuth(user, pass) * ---------------------------------------------------------------- */ @@ -959,6 +965,7 @@ OnvifServiceDevice.prototype.getSystemDateAndTime = function(callback) { let device_time = parsed['date'].getTime(); let my_time = (new Date()).getTime(); this.time_diff = device_time - my_time; + this.date_time = parsed.date } resolve(result); }).catch((error) => { From 69ca1f3fb0c09e513797555a236935b5e0156776 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 9 Nov 2021 14:32:44 -0400 Subject: [PATCH 22/44] Wasn't returning govlength on H264 encoders --- lib/modules/device.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index eeeeb27..c333f19 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -543,6 +543,7 @@ OnvifDevice.prototype._mediaGetProfiles = function () { } if (p['VideoEncoderConfiguration']) { profile['video']['encoder'] = { + 'encoding': p['VideoEncoderConfiguration']['Encoding'], 'token': p['VideoEncoderConfiguration']['$']['token'], 'name': p['VideoEncoderConfiguration']['Name'], 'resolution': { @@ -552,7 +553,8 @@ OnvifDevice.prototype._mediaGetProfiles = function () { 'quality': parseInt(p['VideoEncoderConfiguration']['Quality'], 10), 'framerate': parseInt(p['VideoEncoderConfiguration']['RateControl']['FrameRateLimit'], 10), 'bitrate': parseInt(p['VideoEncoderConfiguration']['RateControl']['BitrateLimit'], 10), - 'encoding': p['VideoEncoderConfiguration']['Encoding'] + 'encoding': p['VideoEncoderConfiguration']['Encoding'], + 'govlength': p['VideoEncoderConfiguration']['H264'] ? p['VideoEncoderConfiguration']['H264']['GovLength'] : null }; } if (p['AudioSourceConfiguration']) { From bed71be6d5a45d5b048a06337ecd32c39e9abc54 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Fri, 12 Nov 2021 10:10:30 -0400 Subject: [PATCH 23/44] Wasn't converting GOVlength to integer from string --- lib/modules/device.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index c333f19..05c02cf 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -554,7 +554,7 @@ OnvifDevice.prototype._mediaGetProfiles = function () { 'framerate': parseInt(p['VideoEncoderConfiguration']['RateControl']['FrameRateLimit'], 10), 'bitrate': parseInt(p['VideoEncoderConfiguration']['RateControl']['BitrateLimit'], 10), 'encoding': p['VideoEncoderConfiguration']['Encoding'], - 'govlength': p['VideoEncoderConfiguration']['H264'] ? p['VideoEncoderConfiguration']['H264']['GovLength'] : null + 'govlength': p['VideoEncoderConfiguration']['H264'] ? parseInt(p['VideoEncoderConfiguration']['H264']['GovLength'],10) : null }; } if (p['AudioSourceConfiguration']) { From 7dc0dc654438e9648f6a1fabb644ee88f9d8dd5d Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Mon, 15 Nov 2021 13:52:16 -0400 Subject: [PATCH 24/44] Print more details when setting video encoder fails --- lib/modules/service-media.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index ac42102..67d4e6f 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -329,7 +329,7 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call soap_body += 'true'; soap_body += '
'; } catch (err){ - reject(new Error('Missing required configuration parammeters')); + reject(new Error(`Missing required configuration parameters ${JSON.stringify(err,null,4)}`)); return; } let soap = this._createRequestSoap(soap_body); From ef369bca6bae940c0c35b1140d0c5940cdd92d89 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 17 Nov 2021 15:51:27 -0400 Subject: [PATCH 25/44] Implement methods to manage OSD --- lib/modules/service-media.js | 219 +++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 67d4e6f..c46b1dd 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2052,4 +2052,223 @@ OnvifServiceMedia.prototype.getSnapshotUri = function(params, callback) { } }; +/* ------------------------------------------------------------------ +* Method: createOSD(params[, callback]) +* - params: +* - OSDToken | String | required | a token of the OSD +* +* { +* 'OSDToken': 'OSD1',' +* 'VideoSourceConfigurationToken': 'VS1', +* 'text': 'Text to display' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.createOSD = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { + reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + soap_body += '' + params['OSDToken'] + ''; + soap_body += '' + params['VideoSourceConfigurationToken'] + '' + soap_body += 'Text' + soap_body += 'LowerLeft' + soap_body += 'Plain' + params['text'] + '' + soap_body += '' + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'CreateOSD', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + +/* ------------------------------------------------------------------ +* Method: deleteOSD(params[, callback]) +* - params: +* - OSDToken | String | required | a token of the Profile +* +* { +* 'OSDToken': 'OSD1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { + reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['OSDToken'] + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'DeleteOSD', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; +/* ------------------------------------------------------------------ +* Method: getOSDs(params[, callback]) +* - params: +* - VideoSourceToken | String | required | a token of the video source +* +* { +* 'VideoSourceToken': 'VS1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getOSDs = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDs', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; +/* ------------------------------------------------------------------ +* Method: getOSD(params[, callback]) +* - params: +* - OSDToken | String | required | a token of the OSD +* +* { +* 'OSDToken': 'OSD1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getOSD = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['OSDToken'] + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDs', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; +/* ------------------------------------------------------------------ +* Method: getOSDOptions(params[, callback]) +* - params: +* - VideoSourceToken | String | required | a token of the video source +* +* { +* 'VideoSourceToken': 'VS1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += ''; + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDOptions', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + module.exports = OnvifServiceMedia; \ No newline at end of file From 22d70f61b7777ac4a154064adf15da068d071546 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 23 Nov 2021 09:25:33 -0400 Subject: [PATCH 26/44] Implemente GetOSDOptions --- lib/modules/service-media.js | 137 +++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 56 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index c46b1dd..0197a7e 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2053,17 +2053,15 @@ OnvifServiceMedia.prototype.getSnapshotUri = function(params, callback) { }; /* ------------------------------------------------------------------ -* Method: createOSD(params[, callback]) +* Method: getOSDs(params[, callback]) * - params: -* - OSDToken | String | required | a token of the OSD +* - VideoSourceToken | String | optional | a token of the video source * * { -* 'OSDToken': 'OSD1',' -* 'VideoSourceConfigurationToken': 'VS1', -* 'text': 'Text to display' +* 'VideoSourceToken': 'VS1' * } * ---------------------------------------------------------------- */ -OnvifServiceMedia.prototype.createOSD = function(params, callback) { +OnvifServiceMedia.prototype.getOSDs = function(params, callback) { let promise = new Promise((resolve, reject) => { let err_msg = ''; if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { @@ -2071,24 +2069,14 @@ OnvifServiceMedia.prototype.createOSD = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { - reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); - return; - } - let soap_body = ''; - soap_body += ''; - soap_body += '' - soap_body += '' + params['OSDToken'] + ''; - soap_body += '' + params['VideoSourceConfigurationToken'] + '' - soap_body += 'Text' - soap_body += 'LowerLeft' - soap_body += 'Plain' + params['text'] + '' - soap_body += '' - soap_body += ''; + soap_body += ''; + if (params['VideoSourceToken']) + soap_body += '' + params['VideoSourceConfigurationToken'] + '' + soap_body += ''; let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'CreateOSD', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDs', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); @@ -2106,15 +2094,15 @@ OnvifServiceMedia.prototype.createOSD = function(params, callback) { }; /* ------------------------------------------------------------------ -* Method: deleteOSD(params[, callback]) +* Method: getOSDOptions(params[, callback]) * - params: -* - OSDToken | String | required | a token of the Profile +* - VideoSourceToken | String | required | a token of the video source * * { -* 'OSDToken': 'OSD1' +* 'VideoSourceToken': 'VS1' * } * ---------------------------------------------------------------- */ -OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { +OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { let promise = new Promise((resolve, reject) => { let err_msg = ''; if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { @@ -2122,18 +2110,18 @@ OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { - reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); return; } let soap_body = ''; - soap_body += ''; - soap_body += '' + params['OSDToken'] + ''; - soap_body += ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += ''; let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'DeleteOSD', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDOptions', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); @@ -2149,16 +2137,19 @@ OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { return promise; } }; + /* ------------------------------------------------------------------ -* Method: getOSDs(params[, callback]) +* Method: createOSD(params[, callback]) * - params: -* - VideoSourceToken | String | required | a token of the video source +* - OSDToken | String | required | a token of the OSD * * { -* 'VideoSourceToken': 'VS1' +* 'VideoSourceConfigurationToken': 'OSD1' +* 'Position': ': UpperLeft | UpperRight | LowerLeft | LowerRight +* 'Text': 'this is text' * } * ---------------------------------------------------------------- */ -OnvifServiceMedia.prototype.getOSDs = function(params, callback) { +OnvifServiceMedia.prototype.createOSD = function(params, callback) { let promise = new Promise((resolve, reject) => { let err_msg = ''; if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { @@ -2166,12 +2157,30 @@ OnvifServiceMedia.prototype.getOSDs = function(params, callback) { return; } + if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { + reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); + return; + } + let soap_body = ''; - soap_body += ''; - soap_body += ''; + soap_body += ''; + soap_body += ''; + soap_body += '' + params['VideoSourceConfigurationToken'] + ''; + soap_body += 'Text'; + soap_body += ''; + soap_body += 'Plain' + soap_body += '' + params['Text'] + ''; + soap_body += '' + soap_body += '' + soap_body += '' + soap_body += '' + params['Position'] + '' + soap_body += '' + soap_body += ''; + soap_body += ''; + soap_body += ''; let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDs', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'CreateOSD', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); @@ -2187,16 +2196,19 @@ OnvifServiceMedia.prototype.getOSDs = function(params, callback) { return promise; } }; + /* ------------------------------------------------------------------ -* Method: getOSD(params[, callback]) +* Method: setOSD(params[, callback]) * - params: -* - OSDToken | String | required | a token of the OSD +* - VideoSourceConfigurationToken | String | required | a token of the OSD * * { -* 'OSDToken': 'OSD1' +* 'VideoSourceConfigurationToken': 'OSD1' +* 'Position': ': UpperLeft | UpperRight | LowerLeft | LowerRight +* 'Text': 'this is text' * } * ---------------------------------------------------------------- */ -OnvifServiceMedia.prototype.getOSD = function(params, callback) { +OnvifServiceMedia.prototype.setOSD = function(params, callback) { let promise = new Promise((resolve, reject) => { let err_msg = ''; if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { @@ -2205,12 +2217,24 @@ OnvifServiceMedia.prototype.getOSD = function(params, callback) { } let soap_body = ''; - soap_body += ''; - soap_body += '' + params['OSDToken'] + ''; - soap_body += ''; + soap_body += '' + soap_body += ''; + soap_body += '' + params['VideoSourceConfigurationToken'] + ''; + soap_body += 'Text'; + soap_body += ''; + soap_body += 'Plain' + soap_body += '' + params['Text'] + ''; + soap_body += '' + soap_body += '' + soap_body += '' + soap_body += '' + params['Position'] + '' + soap_body += '' + soap_body += ''; + soap_body += ''; + soap_body += ''; let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDs', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'SetOSD', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); @@ -2226,16 +2250,17 @@ OnvifServiceMedia.prototype.getOSD = function(params, callback) { return promise; } }; + /* ------------------------------------------------------------------ -* Method: getOSDOptions(params[, callback]) +* Method: deleteOSD(params[, callback]) * - params: -* - VideoSourceToken | String | required | a token of the video source +* - OSDToken | String | required | a token of the Profile * * { -* 'VideoSourceToken': 'VS1' +* 'OSDToken': 'OSD1' * } * ---------------------------------------------------------------- */ -OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { +OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { let promise = new Promise((resolve, reject) => { let err_msg = ''; if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { @@ -2243,18 +2268,18 @@ OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { - reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { + reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); return; } let soap_body = ''; - soap_body += ''; - soap_body += '' + params['ConfigurationToken'] + ''; - soap_body += ''; + soap_body += ''; + soap_body += '' + params['OSDToken'] + ''; + soap_body += ''; let soap = this._createRequestSoap(soap_body); - mOnvifSoap.requestCommand(this.oxaddr, 'GetOSDOptions', soap).then((result) => { + mOnvifSoap.requestCommand(this.oxaddr, 'DeleteOSD', soap).then((result) => { resolve(result); }).catch((error) => { reject(error); From 97a1bebb8ca52f7bb149be2224eb978387d44cce Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 23 Nov 2021 15:20:19 -0400 Subject: [PATCH 27/44] Implemente setOSD --- lib/modules/service-media.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 0197a7e..fdd42f2 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2203,6 +2203,7 @@ OnvifServiceMedia.prototype.createOSD = function(params, callback) { * - VideoSourceConfigurationToken | String | required | a token of the OSD * * { +* 'Token': OSD token * 'VideoSourceConfigurationToken': 'OSD1' * 'Position': ': UpperLeft | UpperRight | LowerLeft | LowerRight * 'Text': 'this is text' @@ -2218,19 +2219,17 @@ OnvifServiceMedia.prototype.setOSD = function(params, callback) { let soap_body = ''; soap_body += '' - soap_body += ''; - soap_body += '' + params['VideoSourceConfigurationToken'] + ''; - soap_body += 'Text'; - soap_body += ''; - soap_body += 'Plain' - soap_body += '' + params['Text'] + ''; - soap_body += '' - soap_body += '' - soap_body += '' - soap_body += '' + params['Position'] + '' - soap_body += '' - soap_body += ''; - soap_body += ''; + soap_body += '' + soap_body += '' + params['VideoSourceConfigurationToken'] + ''; + soap_body += 'Text' + soap_body += '' + soap_body += '' + params['Position'] + '' + soap_body += ''; + soap_body += ''; + soap_body += 'Plain' + soap_body += '' + params['Text'] + ''; + soap_body += '' + soap_body += '' soap_body += ''; let soap = this._createRequestSoap(soap_body); From 6d74f374abab526c14365c511e3b562d17388301 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 25 Nov 2021 13:20:35 -0400 Subject: [PATCH 28/44] Debug OSD methods --- lib/modules/service-media.js | 96 ++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index fdd42f2..ebfdb38 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2072,7 +2072,7 @@ OnvifServiceMedia.prototype.getOSDs = function(params, callback) { let soap_body = ''; soap_body += ''; if (params['VideoSourceToken']) - soap_body += '' + params['VideoSourceConfigurationToken'] + '' + soap_body += '' + params['VideoSourceToken'] + '' soap_body += ''; let soap = this._createRequestSoap(soap_body); @@ -2110,14 +2110,14 @@ OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { - reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + if(err_msg = mOnvifSoap.isInvalidValue(params['VideoSourceToken'], 'string')) { + reject(new Error('The "VideoSourceToken" property was invalid: ' + err_msg)); return; } let soap_body = ''; soap_body += ''; - soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += '' + params['VideoSourceToken'] + ''; soap_body += ''; let soap = this._createRequestSoap(soap_body); @@ -2141,12 +2141,14 @@ OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { /* ------------------------------------------------------------------ * Method: createOSD(params[, callback]) * - params: -* - OSDToken | String | required | a token of the OSD * * { +* 'Token': OSD token * 'VideoSourceConfigurationToken': 'OSD1' * 'Position': ': UpperLeft | UpperRight | LowerLeft | LowerRight -* 'Text': 'this is text' +* 'Text': [optional] 'this is text' +* 'DateFormat': [optional] YYY-MM-DD +* 'TimeFormat': [optional] hh:mm:ss * } * ---------------------------------------------------------------- */ OnvifServiceMedia.prototype.createOSD = function(params, callback) { @@ -2157,26 +2159,37 @@ OnvifServiceMedia.prototype.createOSD = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { - reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); - return; - } - let soap_body = ''; soap_body += ''; - soap_body += ''; - soap_body += '' + params['VideoSourceConfigurationToken'] + ''; - soap_body += 'Text'; - soap_body += ''; - soap_body += 'Plain' - soap_body += '' + params['Text'] + ''; - soap_body += '' - soap_body += '' - soap_body += '' - soap_body += '' + params['Position'] + '' - soap_body += '' - soap_body += ''; - soap_body += ''; + soap_body += '' + soap_body += '' + params['VideoSourceConfigurationToken'] + ''; + soap_body += 'Text' + soap_body += '' + soap_body += '' + params['Position'] + '' + soap_body += ''; + soap_body += ''; + if (params['DateFormat']){ + if (params['TimeFormat']){ + soap_body += 'DateAndTime' + soap_body += '' + params['DateFormat'] + '' + soap_body += '' + params['TimeFormat'] + '' + } else { + soap_body += 'Date' + soap_body += '' + params['DateFormat'] + '' + } + } else if (params['TimeFormat']){ + soap_body += 'Time' + soap_body += '' + params['TimeFormat'] + '' + } else if (params['Text']){ + soap_body += 'Plain' + soap_body += '' + params['Text'] + ''; + } else { + soap_body += 'Plain' + soap_body += '' + } + soap_body += '' + soap_body += '' + soap_body += ''; let soap = this._createRequestSoap(soap_body); @@ -2206,7 +2219,9 @@ OnvifServiceMedia.prototype.createOSD = function(params, callback) { * 'Token': OSD token * 'VideoSourceConfigurationToken': 'OSD1' * 'Position': ': UpperLeft | UpperRight | LowerLeft | LowerRight -* 'Text': 'this is text' +* 'Text': [optional] 'this is text' +* 'DateFormat': [optional] YYY-MM-DD +* 'TimeFormat': [optional] hh:mm:ss * } * ---------------------------------------------------------------- */ OnvifServiceMedia.prototype.setOSD = function(params, callback) { @@ -2226,8 +2241,25 @@ OnvifServiceMedia.prototype.setOSD = function(params, callback) { soap_body += '' + params['Position'] + '' soap_body += ''; soap_body += ''; - soap_body += 'Plain' - soap_body += '' + params['Text'] + ''; + if (params['DateFormat']){ + if (params['TimeFormat']){ + soap_body += 'DateAndTime' + soap_body += '' + params['DateFormat'] + '' + soap_body += '' + params['TimeFormat'] + '' + } else { + soap_body += 'Date' + soap_body += '' + params['DateFormat'] + '' + } + } else if (params['TimeFormat']){ + soap_body += 'Time' + soap_body += '' + params['TimeFormat'] + '' + } else if (params['Text']){ + soap_body += 'Plain' + soap_body += '' + params['Text'] + ''; + } else { + soap_body += 'Plain' + soap_body += '' + } soap_body += '' soap_body += '' soap_body += ''; @@ -2253,10 +2285,10 @@ OnvifServiceMedia.prototype.setOSD = function(params, callback) { /* ------------------------------------------------------------------ * Method: deleteOSD(params[, callback]) * - params: -* - OSDToken | String | required | a token of the Profile +* - Token | String | required | a token of the Profile * * { -* 'OSDToken': 'OSD1' +* 'Token': 'OSD1' * } * ---------------------------------------------------------------- */ OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { @@ -2267,14 +2299,14 @@ OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { return; } - if(err_msg = mOnvifSoap.isInvalidValue(params['OSDToken'], 'string')) { - reject(new Error('The "OSDToken" property was invalid: ' + err_msg)); + if(err_msg = mOnvifSoap.isInvalidValue(params['Token'], 'string')) { + reject(new Error('The "Token" property was invalid: ' + err_msg)); return; } let soap_body = ''; soap_body += ''; - soap_body += '' + params['OSDToken'] + ''; + soap_body += '' + params['Token'] + ''; soap_body += ''; let soap = this._createRequestSoap(soap_body); From 36fca26f0883354c64bf5c216a77cc7730b9c293 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 15 Dec 2021 09:17:11 -0400 Subject: [PATCH 29/44] Some cameras use the wrong namespace on OSD configuration --- lib/modules/service-media.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index ebfdb38..1a3c4c3 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2117,6 +2117,7 @@ OnvifServiceMedia.prototype.getOSDOptions = function(params, callback) { let soap_body = ''; soap_body += ''; + soap_body += '' + params['VideoSourceToken'] + ''; soap_body += '' + params['VideoSourceToken'] + ''; soap_body += ''; let soap = this._createRequestSoap(soap_body); @@ -2307,6 +2308,7 @@ OnvifServiceMedia.prototype.deleteOSD = function(params, callback) { let soap_body = ''; soap_body += ''; soap_body += '' + params['Token'] + ''; + soap_body += '' + params['Token'] + ''; soap_body += ''; let soap = this._createRequestSoap(soap_body); From 9739c5cb37f0df971229ec3034902394ede00ab1 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 19 Jan 2022 13:12:15 -0400 Subject: [PATCH 30/44] Add missing node modules --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c0eaa73..0997986 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.7", "description": "The node-onvif is a Node.js module which allows you to communicate with the network camera which supports the ONVIF specifications.", "engines": { - "node" : ">=4.4" + "node": ">=4.4" }, "main": "./lib/node-onvif.js", "files": [ @@ -33,7 +33,7 @@ }, "readmeFilename": "README.md", "dependencies": { - "xml2js": ">=0.4.17", - "html": ">=1.0.0" + "html": ">=1.0.0", + "xml2js": "^0.4.23" } } From 00064d5e6e0702c8a56aed756e033860b23c9b76 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 19 Jan 2022 13:34:29 -0400 Subject: [PATCH 31/44] Ignore package lock file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 579043d..8659eab 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ Temporary Items test node_modules +package-lock.json From 5126216698eeaff09db18a626d2340dcd189456c Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 8 Mar 2022 14:11:42 -0400 Subject: [PATCH 32/44] Regular express for verifying timezone ala posix 1003.1 was wrong. Just ignore it an let camera deal with this --- lib/modules/service-device.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index a118331..5530337 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -1056,9 +1056,9 @@ OnvifServiceDevice.prototype.setSystemDateAndTime = function(params, callback) { if(err_msg = mOnvifSoap.isInvalidValue(params['TimeZone'], 'string')) { reject(new Error('The "TimeZone" property was invalid: ' + err_msg)); return; - } else if(!params['TimeZone'].match(/^[A-Z]{3}\-?\d{1,2}([A-Z]{3,4})?$/)) { - reject(new Error('The "TimeZone" property must be a string representing a time zone which is defined in POSIX 1003.1.')); - return; + //} else if(!params['TimeZone'].match(/^[A-Z]{3}\-?\d{1,2}([A-Z]{3,4})?$/)) { + //reject(new Error('The "TimeZone" property must be a string representing a time zone which is defined in POSIX 1003.1.')); + //return; } } @@ -1780,4 +1780,4 @@ OnvifServiceDevice.prototype.getServiceCapabilities = function(callback) { } }; -module.exports = OnvifServiceDevice; \ No newline at end of file +module.exports = OnvifServiceDevice; From 0a09ee99fae2556f474f61fba39babca1fe9cdb8 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 20 Apr 2022 10:03:50 -0300 Subject: [PATCH 33/44] Add new isONVIF method. THis just asks the device for the date/time --- lib/modules/device.js | 54 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index 05c02cf..9d0ee02 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -352,7 +352,16 @@ OnvifDevice.prototype.init = function (callback) { }).then(() => { return this._getDeviceInformation(); }).then(() => { - return this._mediaGetProfiles(); + return this._getVideoSources(); + }).then(() => { + if (!Array.isArray(this.video_sources)) + return this._mediaGetProfiles(); + else { + this.isMultiCameraDevice = true + return(new Promise((res,rej) => { + res(null) + })) + } }).then(() => { return this._mediaGetStreamURI(); }).then(() => { @@ -380,6 +389,33 @@ OnvifDevice.prototype.init = function (callback) { } }; +/* ------------------------------------------------------------------ +* Method: isONVIF([callback]) +* ---------------------------------------------------------------- */ +OnvifDevice.prototype.isONVIF = function (callback) { + let promise = new Promise((resolve, reject) => { + this._getSystemDateAndTime().then(() => { + resolve(null) + }).catch((error) => { + + // If we can't even talk to this guy, then abort - but mark the guy as unaddressable + if (this.lastError.toString().search(/Network Error/i) >= 0){ + this.isAddressable = false; + } + reject(error); + }); + }); + if (this._isValidCallback(callback)) { + promise.then((info) => { + callback(null, info); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + // GetSystemDateAndTime (Access Class: PRE_AUTH) OnvifDevice.prototype._getSystemDateAndTime = function () { let promise = new Promise((resolve, reject) => { @@ -459,6 +495,22 @@ OnvifDevice.prototype._getCapabilities = function () { return promise; }; +// GetVideoSources (Access Class: READ_SYSTEM) +OnvifDevice.prototype._getVideoSources = function () { + let promise = new Promise((resolve, reject) => { + this.services.media.getVideoSources((error, result) => { + this.lastError = error; + this.lastResponse = result; + if (error) { + reject(new Error('Failed to initialize the device: ' + error.toString())); + } else { + this.video_sources = result['data']['GetVideoSourcesResponse']['VideoSources']; + resolve(); + } + }); + }); + return promise; +}; // GetDeviceInformation (Access Class: READ_SYSTEM) OnvifDevice.prototype._getDeviceInformation = function () { let promise = new Promise((resolve, reject) => { From 6701d2d82cbed086a32eb9fe6f627cf4cb67249a Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 26 Apr 2022 12:37:43 -0300 Subject: [PATCH 34/44] Make _getProfiles a public method --- lib/modules/device.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/device.js b/lib/modules/device.js index 9d0ee02..10a64e3 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -355,7 +355,7 @@ OnvifDevice.prototype.init = function (callback) { return this._getVideoSources(); }).then(() => { if (!Array.isArray(this.video_sources)) - return this._mediaGetProfiles(); + return this.getProfiles(); else { this.isMultiCameraDevice = true return(new Promise((res,rej) => { @@ -529,7 +529,7 @@ OnvifDevice.prototype._getDeviceInformation = function () { }; // Media::GetProfiles (Access Class: READ_MEDIA) -OnvifDevice.prototype._mediaGetProfiles = function () { +OnvifDevice.prototype.getProfiles = function () { let promise = new Promise((resolve, reject) => { this.services.media.getProfiles((error, result) => { this.lastError = error; From c0a8c35f351336653f5d806eb54d10692f17e877 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 29 Jun 2022 09:55:08 -0300 Subject: [PATCH 35/44] Add new methods to manage audio configuration --- lib/modules/service-media.js | 290 ++++++++++++++++++++++++++++++++++- 1 file changed, 288 insertions(+), 2 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 1a3c4c3..3eeb111 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -299,7 +299,7 @@ OnvifServiceMedia.prototype.setVideoEncoderConfiguration = function(params, call soap_body += ''; soap_body += ''; soap_body += '' + params.Configuration.Name + ''; - soap_body += '' + params.Configuration.UseCount + ''; + soap_body += '' + params.Configuration.UseCount + ''; // deprecated in Media 2 as read-only soap_body += '' + params.Configuration.Encoding + ''; soap_body += ''; soap_body += '' + params.Configuration.Resolution.Width + ''; @@ -1577,6 +1577,56 @@ OnvifServiceMedia.prototype.addAudioSourceConfiguration = function(params, callb } }; +/* ------------------------------------------------------------------ +* Method: removeAudioEncoderConfiguration(params[, callback]) +* - params: +* - ProfileToken | Object | required | a token of the profile +* +* { +* 'ProfileToken' : 'Profile_1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.removeAudioEncoderConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += '' + params.ProfileToken + ''; + soap_body += ''; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'RemoveAudioEncoderConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + /* ------------------------------------------------------------------ * Method: getCompatibleAudioSourceConfigurations(params[, callback]) * - params: @@ -1683,6 +1733,55 @@ OnvifServiceMedia.prototype.getAudioSourceConfigurationOptions = function(params } }; +/* ------------------------------------------------------------------ +* Method: removeAudioSourceConfiguration(params[, callback]) +* - params: +* - ProfileToken | Object | required | a token of the profile +* +* { +* 'ProfileToken' : 'Profile_1' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.removeAudioSourceConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['ProfileToken'], 'string')) { + reject(new Error('The "ProfileToken" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + try { + soap_body += ''; + soap_body += '' + params.ProfileToken + ''; + soap_body += ''; + } catch (err){ + reject(new Error('Missing required configuration parammeters')); + return; + } + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'RemoveAudioSourceConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; /* ------------------------------------------------------------------ * Method: getAudioEncoderConfiguration(params[, callback]) * - params: @@ -1913,6 +2012,193 @@ OnvifServiceMedia.prototype.getAudioEncoderConfigurationOptions = function(param } }; +/* ------------------------------------------------------------------ +* Method: setAudioEncoderConfiguration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | +* - Encoding | String | required +* - Bitrate | number | optional +* - SampleRate | number | optional +* +* { +* 'ConfigurationToken': 'audio_encoder_token' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.setAudioEncoderConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['Encoding'], 'string')) { + reject(new Error('The "Encoding" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += ``; + if (params['Encoding']) + soap_body += '' + params['Encoding'] + '' + if (params['Bitrate']) + soap_body += '' + params['Bitrate'] + '' + if (params['SampleRate']) + soap_body += '' + params['SampleRate'] + '' + soap_body += ''; + soap_body += ''; + soap_body += 'IPv4'; + soap_body += '0.0.0.0'; + soap_body += ''; + soap_body += ''; + soap_body += '0'; + soap_body += '5'; + soap_body += 'false'; + soap_body += ''; + soap_body += 'PT60S'; + soap_body += ''; + soap_body += 'true'; + soap_body += ''; + + let soap = this._createRequestSoap(soap_body); + mOnvifSoap.requestCommand(this.oxaddr, 'SetAudioEncoderConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + +/* ------------------------------------------------------------------ +* Method: getAudioOutputs([, callback]) +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getAudioOutputs = function(callback) { + let promise = new Promise((resolve, reject) => { + + let soap_body = ''; + soap_body += ''; + soap_body += ''; + + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetAudioOutputs', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + +/* ------------------------------------------------------------------ +* Method: getAudioOutputConfigurations([, callback]) +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.getAudioOutputConfigurations = function(callback) { + let promise = new Promise((resolve, reject) => { + + let soap_body = ''; + soap_body += ''; + soap_body += ''; + + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'GetAudioOutputConfigurations', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; + +/* ------------------------------------------------------------------ +* Method: setAudioOutputConfiguration(params[, callback]) +* - params: +* - ConfigurationToken | String | required | +* - OutputToken | String | required | +* - OutputLevel | integer | required + +* +* { +* 'ConfigurationToken': 'audio_encoder_token' +* } +* ---------------------------------------------------------------- */ +OnvifServiceMedia.prototype.setAudioOutputConfiguration = function(params, callback) { + let promise = new Promise((resolve, reject) => { + let err_msg = ''; + if(err_msg = mOnvifSoap.isInvalidValue(params, 'object')) { + reject(new Error('The value of "params" was invalid: ' + err_msg)); + return; + } + + if(err_msg = mOnvifSoap.isInvalidValue(params['ConfigurationToken'], 'string')) { + reject(new Error('The "ConfigurationToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['OutputToken'], 'string')) { + reject(new Error('The "OutputToken" property was invalid: ' + err_msg)); + return; + } + if(err_msg = mOnvifSoap.isInvalidValue(params['OutputLevel'], 'integer')) { + reject(new Error('The "OutputLevel" property was invalid: ' + err_msg)); + return; + } + + let soap_body = ''; + soap_body += ''; + soap_body += '' + params['ConfigurationToken'] + ''; + soap_body += '' + params['OutputToken'] + ''; + soap_body += '' + params['OutputLevel'] + '' + soap_body += ''; + + let soap = this._createRequestSoap(soap_body); + + mOnvifSoap.requestCommand(this.oxaddr, 'SetAudioOutputConfiguration', soap).then((result) => { + resolve(result); + }).catch((error) => { + reject(error); + }); + }); + if(callback) { + promise.then((result) => { + callback(null, result); + }).catch((error) => { + callback(error); + }); + } else { + return promise; + } +}; /* ------------------------------------------------------------------ * Method: startMulticastStreaming(params[, callback]) * - params: @@ -2072,7 +2358,7 @@ OnvifServiceMedia.prototype.getOSDs = function(params, callback) { let soap_body = ''; soap_body += ''; if (params['VideoSourceToken']) - soap_body += '' + params['VideoSourceToken'] + '' + soap_body += '' + params['VideoSourceToken'] + '' soap_body += ''; let soap = this._createRequestSoap(soap_body); From 876c5241b6865611ef1f8c72915db84fcaabbb70 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Wed, 20 Jul 2022 13:56:41 -0300 Subject: [PATCH 36/44] setAudioConfiguration requires parameters that are optional in the spec...hmmm...oh well --- lib/modules/service-media.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/modules/service-media.js b/lib/modules/service-media.js index 3eeb111..932c91a 100644 --- a/lib/modules/service-media.js +++ b/lib/modules/service-media.js @@ -2015,7 +2015,8 @@ OnvifServiceMedia.prototype.getAudioEncoderConfigurationOptions = function(param /* ------------------------------------------------------------------ * Method: setAudioEncoderConfiguration(params[, callback]) * - params: -* - ConfigurationToken | String | required | +* - ConfigurationToken | String | required | +* - Name | String | required * - Encoding | String | required * - Bitrate | number | optional * - SampleRate | number | optional @@ -2040,27 +2041,32 @@ OnvifServiceMedia.prototype.setAudioEncoderConfiguration = function(params, call reject(new Error('The "Encoding" property was invalid: ' + err_msg)); return; } + if(err_msg = mOnvifSoap.isInvalidValue(params['Name'], 'string')) { + reject(new Error('The "Name" property was invalid: ' + err_msg)); + return; + } let soap_body = ''; soap_body += ''; soap_body += ``; + soap_body += `${params['Name']}` + soap_body += `0` // Required, but ignored by device + soap_body += ` + + IPv4 + 0.0.0.0 + + 0 + 1 + false + ` // Required, but I don't do anything with it + soap_body += `PT0H0M30S`// Required, but I don't do anything with it if (params['Encoding']) soap_body += '' + params['Encoding'] + '' if (params['Bitrate']) soap_body += '' + params['Bitrate'] + '' if (params['SampleRate']) soap_body += '' + params['SampleRate'] + '' - soap_body += ''; - soap_body += ''; - soap_body += 'IPv4'; - soap_body += '0.0.0.0'; - soap_body += ''; - soap_body += ''; - soap_body += '0'; - soap_body += '5'; - soap_body += 'false'; - soap_body += ''; - soap_body += 'PT60S'; soap_body += ''; soap_body += 'true'; soap_body += ''; From 1026f2c6a13d56fe8eeb54009ef8401fd2294323 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 16 Aug 2022 11:23:43 -0300 Subject: [PATCH 37/44] Add timeout to HTTP requests so non-compliant devices don't permanently block us --- lib/modules/soap.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/soap.js b/lib/modules/soap.js index 9eed334..117a84f 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -123,6 +123,7 @@ OnvifSoap.prototype._request = function(oxaddr, soap) { port : oxaddr.port || 80, path : oxaddr.pathname, method : 'POST', + timeout : 3000, headers: { //'Content-Type': 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/GetScopes"', 'Content-Type': 'application/soap+xml; charset=utf-8;', From 7e81362cede04be6023015c62ddbccc443fcec59 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 3 Nov 2022 09:34:37 -0300 Subject: [PATCH 38/44] Add ability to enable/disable DHCP on IP configuration --- lib/modules/service-device.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index 5530337..e06221d 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -684,7 +684,7 @@ OnvifServiceDevice.prototype.setNetworkInterfaces = function(info,callback) { soap_body += '
' + info.ip + '
' soap_body += '24' soap_body += '' - soap_body += 'false' + soap_body += '' + (info.dhcp ? 'true' : 'false') + '' soap_body += '' soap_body += '' soap_body += '
'; From 282665c88fc880d3a1b6db7b875ea1e725917f31 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 3 Nov 2022 10:11:13 -0300 Subject: [PATCH 39/44] When making IP address dynamic - don't includ MANUAL IP elemement --- lib/modules/service-device.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index e06221d..761ee96 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -680,11 +680,15 @@ OnvifServiceDevice.prototype.setNetworkInterfaces = function(info,callback) { //soap_body += '1500' soap_body += '' soap_body += 'true' - soap_body += '' - soap_body += '
' + info.ip + '
' - soap_body += '24' - soap_body += '
' - soap_body += '' + (info.dhcp ? 'true' : 'false') + '' + if (info.dhcp){ + soap_body += 'true' + } else { + soap_body += '' + soap_body += '
' + info.ip + '
' + soap_body += '24' + soap_body += '
' + soap_body += 'false' + } soap_body += '
' soap_body += '' soap_body += ''; From 767772d750fff2462c0c9811b83963cddb56b0ce Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 4 Apr 2023 12:09:50 -0400 Subject: [PATCH 40/44] Add unparsed response from GetSystemDateAndTime and save in the device object --- lib/modules/device.js | 1 + lib/modules/service-device.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/modules/device.js b/lib/modules/device.js index 10a64e3..c99d0e5 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -428,6 +428,7 @@ OnvifDevice.prototype._getSystemDateAndTime = function () { if (!error){ this.time_diff = this.services.device.getTimeDiff(); this.date_time = this.services.device.getDateTime() + this.date_time_unparsed = result.converted.Body.GetSystemDateAndTimeResponse.SystemDateAndTime this.onvifCompliant = 'yes' resolve() } else diff --git a/lib/modules/service-device.js b/lib/modules/service-device.js index 761ee96..f6ff11d 100644 --- a/lib/modules/service-device.js +++ b/lib/modules/service-device.js @@ -970,6 +970,7 @@ OnvifServiceDevice.prototype.getSystemDateAndTime = function(callback) { let my_time = (new Date()).getTime(); this.time_diff = device_time - my_time; this.date_time = parsed.date + this.date_time_unparsed = result.converted.Body.GetSystemDateAndTimeResponse.SystemDateAndTime } resolve(result); }).catch((error) => { From cb9713dc9a880ea8e6e67544026757e2c30ed81f Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Mon, 5 Jun 2023 12:06:51 +0000 Subject: [PATCH 41/44] OOPS! I wasn't providing the EncodingInterval in the encoder --- lib/modules/device.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/device.js b/lib/modules/device.js index c99d0e5..1e2028f 100644 --- a/lib/modules/device.js +++ b/lib/modules/device.js @@ -606,6 +606,7 @@ OnvifDevice.prototype.getProfiles = function () { 'quality': parseInt(p['VideoEncoderConfiguration']['Quality'], 10), 'framerate': parseInt(p['VideoEncoderConfiguration']['RateControl']['FrameRateLimit'], 10), 'bitrate': parseInt(p['VideoEncoderConfiguration']['RateControl']['BitrateLimit'], 10), + 'encodinginterval': parseInt(p['VideoEncoderConfiguration']['RateControl']['EncodingInterval'], 10), 'encoding': p['VideoEncoderConfiguration']['Encoding'], 'govlength': p['VideoEncoderConfiguration']['H264'] ? parseInt(p['VideoEncoderConfiguration']['H264']['GovLength'],10) : null }; From 45d7266a684097818416a7fe39d8e0a9f90748eb Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 15 Jun 2023 11:32:34 -0300 Subject: [PATCH 42/44] Add methods to enable logging of all SOAP messages --- lib/modules/soap.js | 21 +++++++++++++++++++++ lib/node-onvif.js | 9 +++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/modules/soap.js b/lib/modules/soap.js index 117a84f..ff1e6e2 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -10,6 +10,7 @@ const mXml2Js = require('xml2js'); const mHttp = require('http'); const mCrypto = require('crypto'); let mHtml = null; +let mLogger = null; try { mHtml = require('html'); } catch(e) {} @@ -21,6 +22,16 @@ function OnvifSoap() { this.HTTP_TIMEOUT = 3000; // ms } +/* ------------------------------------------------------------------ +* Method: enableLogging(method) +* +* J. Udall +* - method - a logger function that will be invoked for every SOAP message +* ---------------------------------------------------------------- */ +OnvifSoap.prototype.enableLogging = function(method) { + mLogger = method +} + /* ------------------------------------------------------------------ * Method: parse(soap) * ---------------------------------------------------------------- */ @@ -131,6 +142,10 @@ OnvifSoap.prototype._request = function(oxaddr, soap) { } }; + // J. UDALL - log SOAP request + if (mLogger){ + mLogger(soap) + } let req = mHttp.request(post_opts, (res) => { res.setEncoding('utf8'); let xml = ''; @@ -148,6 +163,12 @@ OnvifSoap.prototype._request = function(oxaddr, soap) { res.removeAllListeners('end'); } if(res.statusCode === 200) { + + // J. UDALL - log SOAP response + if (mLogger){ + mLogger(soap) + } + resolve(xml); } else { let err = new Error(res.statusCode + ' ' + res.statusMessage); diff --git a/lib/node-onvif.js b/lib/node-onvif.js index 0de8e2b..cbbfa91 100644 --- a/lib/node-onvif.js +++ b/lib/node-onvif.js @@ -28,6 +28,15 @@ function Onvif() { this._discovery_wait_timer = null; } +/* ------------------------------------------------------------------ +* Method: enableLogging() +* J. UDALL - added ability to log SOAP messages +* - method - a method that will be invoked for every SOAP message sent/received +* ---------------------------------------------------------------- */ +Onvif.prototype.enableLogging = function(method) { + this._OnvifSoap.enableLogging(method) +} + /* ------------------------------------------------------------------ * Method: startDiscovery(callback) * [Caution] From 9ac483ca13db37cb5813f5d8acf14e71adc0a0a4 Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Thu, 15 Jun 2023 12:51:49 -0300 Subject: [PATCH 43/44] OOPS! I wasn't logging the response - but rather the request again --- lib/modules/soap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/soap.js b/lib/modules/soap.js index ff1e6e2..0c64ac4 100644 --- a/lib/modules/soap.js +++ b/lib/modules/soap.js @@ -166,7 +166,7 @@ OnvifSoap.prototype._request = function(oxaddr, soap) { // J. UDALL - log SOAP response if (mLogger){ - mLogger(soap) + mLogger(xml) } resolve(xml); From c7c6c4ca087f61e0e3e5ae36f034b80cfdfc00db Mon Sep 17 00:00:00 2001 From: Jim Udall Date: Tue, 18 Jul 2023 08:35:54 -0300 Subject: [PATCH 44/44] Fix: Upgrade xml2js package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0997986..21e1d18 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,6 @@ "readmeFilename": "README.md", "dependencies": { "html": ">=1.0.0", - "xml2js": "^0.4.23" + "xml2js": "^0.6.0" } }