Skip to content

Commit

Permalink
Changes to PTZ + others (credit @hawkeye64)
Browse files Browse the repository at this point in the history
Remove old node versions from TravisCI testing
Add gotoHomePosition and setHomePosition methods (with appropriate test suites)
Fixes some camera’s fault parsing
Bumps NPM package version, adds @hawekeye64 as contributor, fixes some npm deps
Updates readme
Updates test suite XML files to better naming convention
  • Loading branch information
chriswiggins committed Jan 19, 2018
1 parent a21199c commit 88a4575
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 111 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
language: node_js
node_js:
- "0.12"
- "4"
- "5"
- "6"
before_install:
- "npm config set spin false"
Expand Down
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,49 @@ The options are:
* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`

### gotoPreset(options, callback)
Operation to go to a saved preset position for the PTZ node in the selected profile.
*PTZ.* Operation to go to a saved preset position for the PTZ node in the selected profile.

The options are:

* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`
* `preset` - the name of preset. List of presets you can get by `#getPresets` method or in `#presets` property.

### setPreset(options, callback)
*PTZ.* Operation to set the current position as a preset for the PTZ node in the selected profile. If `presetToken` is passed as an option, then the preset for which that token is attached will be replaced. After success, you should re-fetch the presets with `#getPresets` method.

The options are:

* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`
* `presetName` - the name to give to the preset. (optional) is this is a preset update.

### removePreset(options, callback)
*PTZ.* Operation to remove a preset specified by the preset token. After success, you should re-fetch the presets with `#getPresets` method.

The options are:

* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`
* `presetToken` - the preset token to use for preset removal (this will be the `value` of a preset object found in `#presets` after calling the `#getPresets` method.

### gotoHomePosition(options, callback)
*PTZ.* Operation to go to the saved `home` position for the PTZ node in the selected profile. If no `home` position has been saved, the ONVIF camera will do nothing.

The options are:

* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`
* `speed` An object with properties
- `x` Pan speed
- `y` Tilt speed
- `zoom` Zoom speed

If the speed option is omitted, the default speed set by the PTZConfiguration will be used.

### setHomePosition(options, callback)
*PTZ.* Operation to set the current position as the `home` position for the PTZ node in the selected profile.

The options are:

* `profileToken` (optional) - defines media profile to use and will define the configuration of the content of the stream. Default is `#activeSource.profileToken`

### getNodes(callback)
*PTZ.* Returns the properties of the current PTZ node, if it exists.
Use this function to get maximum number of presets, ranges of admitted values for x, y, zoom, iris, focus.
Expand Down Expand Up @@ -273,7 +309,7 @@ The options are:
Callback is optional and means essentially nothing

### continuousMove(options, callback)
Operation for continuous Pan/Tilt and Zoom movements
*PTZ.* Operation for continuous Pan/Tilt and Zoom movements

The options are:

Expand Down Expand Up @@ -338,6 +374,8 @@ configuration object
* GetReplayUri

## Changelog
- 0.5.5 Added #ptz.`gotoHomePosition`, #ptz.`setHomePosition`. Fixed exceptions in #ptz.`getConfigurations` and #utils.`parseSOAPString`. Added tests for #ptz.`setPreset`, #ptz.`removePreset`, #ptz.`gotoHomePosition`, and #ptz.`setHomePosition`.
- 0.5.4 Bumped for NPM.
- 0.5.3 Some fixes. Tests
- 0.5.2 `preserveAddress` property for NAT devices, discovery with multiple network interfaces (@Climax777)
- 0.5.1 Critical bugfix in SOAP-auth for some cams
Expand Down
94 changes: 85 additions & 9 deletions lib/ptz.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Cam.prototype.getPresets = function(options, callback) {
* @param {object} options
* @param {string} [options.profileToken]
* @param {string} options.preset PresetName from {@link Cam#presets} property
* @param {string} options.speed
* @param {function} callback
*/
Cam.prototype.gotoPreset = function(options, callback) {
Expand Down Expand Up @@ -102,6 +103,46 @@ Cam.prototype.removePreset = function(options,callback) {
this._envelopeFooter()
}, callback.bind(this));
};

/**
* /PTZ/ Go to home position
* @param {object} options
* @param {string} [options.profileToken]
* @param {object} [options.speed] If the speed argument is omitted, the default speed set by the PTZConfiguration will be used.
* @param {number} [options.speed.x] Pan speed, float within 0 to 1
* @param {number} [options.speed.y] Tilt speed, float within 0 to 1
* @param {number} [options.speed.zoom] Zoom speed, float within 0 to 1
* @param {function} callback
*/
Cam.prototype.gotoHomePosition = function(options, callback) {
this._request({
service: 'ptz'
, body: this._envelopeHeader() +
'<GotoHomePosition xmlns="http://www.onvif.org/ver20/ptz/wsdl">' +
'<ProfileToken>' + (options.profileToken || this.activeSource.profileToken) + '</ProfileToken>' +
(options.speed ? '<Speed>' + this._panTiltZoomVectors(options.speed) + '</Speed>' : '') +
'</GotoHomePosition>' +
this._envelopeFooter()
}, callback.bind(this));
};

/**
* /PTZ/ Go to home position
* @param {object} options
* @param {string} [options.profileToken]
* @param {function} callback
*/
Cam.prototype.setHomePosition = function(options, callback) {
this._request({
service: 'ptz'
, body: this._envelopeHeader() +
'<SetHomePosition xmlns="http://www.onvif.org/ver20/ptz/wsdl">' +
'<ProfileToken>' + (options.profileToken || this.activeSource.profileToken) + '</ProfileToken>' +
'</SetHomePosition>' +
this._envelopeFooter()
}, callback.bind(this));
};

/**
* @typedef {object} Cam~PTZStatus
* @property {object} position
Expand Down Expand Up @@ -190,16 +231,51 @@ Cam.prototype.getConfigurations = function(callback) {
if (!err) {
var configurations = {};
data[0]['getConfigurationsResponse'].forEach(function(configuration) {
configurations[configuration['PTZConfiguration'][0]['name']] = {
useCount: parseInt(configuration['PTZConfiguration'][0]['useCount'])
, nodeToken: configuration['PTZConfiguration'][0]['nodeToken'][0]
, defaultPTZSpeed: {
x: configuration['PTZConfiguration'][0]['defaultPTZSpeed'][0]['panTilt'][0].$.x
, y: configuration['PTZConfiguration'][0]['defaultPTZSpeed'][0]['panTilt'][0].$.y
, zoom: configuration['PTZConfiguration'][0]['defaultPTZSpeed'][0]['zoom'][0].$.x
var pTZConfiguration = configuration['PTZConfiguration'][0];
var name = pTZConfiguration['name'];
// some cameras have this as an array instead of a string
if (Array.isArray(name)) {
name = name[0];
}
configurations[name] = {};
configurations[name].useCount = parseInt(pTZConfiguration['useCount']);
if ('$' in pTZConfiguration) {
configurations[name].token = pTZConfiguration['$']['token'];
}
if ('nodeToken' in pTZConfiguration) {
configurations[name].nodeToken = pTZConfiguration['nodeToken'][0];
}
if ('defaultPTZSpeed' in pTZConfiguration) {
var defaultPTZSpeed = pTZConfiguration['defaultPTZSpeed'][0];
configurations[name].defaultPTZSpeed = {};
if ('panTilt' in defaultPTZSpeed) {
configurations[name].defaultPTZSpeed.x = defaultPTZSpeed['panTilt'][0].$.x;
configurations[name].defaultPTZSpeed.y = defaultPTZSpeed['panTilt'][0].$.y;
}
, defaultPTZTimeout: configuration['PTZConfiguration'][0]['defaultPTZTimeout'][0]
};
if ('zoom' in defaultPTZSpeed) {
configurations[name].defaultPTZSpeed.zoom = defaultPTZSpeed['zoom'][0].$.x;
}
}
if ('defaultPTZTimeout' in pTZConfiguration) {
configurations[name].defaultPTZTimeout = pTZConfiguration['defaultPTZTimeout'][0];
}
if ('panTiltLimits' in pTZConfiguration) {
configurations[name].panTiltLimits = {};
var panTiltLimits = pTZConfiguration['panTiltLimits'][0]['range'][0];
configurations[name].panTiltLimits.XRange = {};
configurations[name].panTiltLimits.XRange.min = panTiltLimits.XRange[0].min[0];
configurations[name].panTiltLimits.XRange.max = panTiltLimits.XRange[0].max[0];
configurations[name].panTiltLimits.YRange = {};
configurations[name].panTiltLimits.YRange.min = panTiltLimits.YRange[0].min[0];
configurations[name].panTiltLimits.YRange.max = panTiltLimits.YRange[0].max[0];
}
if ('zoomLimits' in pTZConfiguration) {
configurations[name].zoomLimits = {};
var zoomLimits = pTZConfiguration['zoomLimits'][0]['range'][0];
configurations[name].zoomLimits.XRange = {};
configurations[name].zoomLimits.XRange.min = zoomLimits.XRange[0].min[0];
configurations[name].zoomLimits.XRange.max = zoomLimits.XRange[0].max[0];
}
});
this.configurations = configurations;
}
Expand Down
28 changes: 15 additions & 13 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,21 @@ const parseSOAPString = function(xml, callback) {
callback(new Error('Wrong ONVIF SOAP response'), null, xml);
} else {
if (!err && result['envelope']['body'][0]['fault']) {
err = new Error(
'ONVIF SOAP Fault: ' +
(
result.envelope.body[0].fault[0].reason[0].text[0]._
||
JSON.stringify(linerase(result.envelope.body[0].fault[0].code[0]))
) +
(
result.envelope.body[0].fault[0].detail
? ': ' + result.envelope.body[0].fault[0].detail[0].text[0]
: ''
)
);
var fault = result['envelope']['body'][0]['fault'][0];
var reason;
if (fault['reason'][0]['text'][0]._) {
reason = fault['reason'][0]['text'][0]._;
}
if (!reason) {
reason = JSON.stringify(linerase(result.envelope.body[0].fault[0].code[0]));
}
var detail = '';
if (result.envelope.body[0].fault[0].detail && result.envelope.body[0].fault[0].detail[0].text[0]) {
detail = result.envelope.body[0].fault[0].detail[0].text[0];
}

// console.error('Fault:', reason, detail);
err = new Error('ONVIF SOAP Fault: ' + (reason) + (detail));
}
callback(err, result['envelope']['body'], xml);
}
Expand Down
24 changes: 18 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "onvif",
"version": "0.5.4",
"version": "0.5.5",
"author": "Andrew D.Laptev <[email protected]>",
"description": "Client to ONVIF NVT devices Profile S: cameras",
"main": "lib/onvif.js",
Expand All @@ -16,12 +16,24 @@
"test-coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage",
"upload-to-coveralls": "cat ./coverage/lcov.info | coveralls"
},
"contributors": [
{
"name": "Andrew D.Laptev",
"email": "[email protected]",
"url": "https://github.com/agsh/onvif"
},
{
"name": "Jeff Galbraith",
"email": "[email protected]",
"url": "http://intelliviewtech.com"
}
],
"repository": {
"type": "git",
"url": "https://github.com/agsh/onvif.git"
},
"dependencies": {
"xml2js": "^0.4"
"xml2js": "^0.4.19"
},
"keywords": [
"onvif",
Expand All @@ -32,19 +44,19 @@
],
"license": "MIT",
"engines": {
"node": ">=0.12"
"node": ">=6.0"
},
"devDependencies": {
"coffee-script": "^1.9.3",
"coffeescript": "^1.9.3",
"coveralls": ">=2.11.2",
"dot": "^1.0.3",
"istanbul": ">=0.3.5",
"jscs": "^1.13.1",
"jsdoc": "^3.3.0",
"jshint": "^2.7.0",
"keypress": "^0.2.1",
"mocha": ">=2.1.0",
"mocha-lcov-reporter": "0.0.1",
"nimble": "^0.0.2",
"keypress": "^0.2.1"
"nimble": "^0.0.2"
}
}
69 changes: 0 additions & 69 deletions test/common.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -314,75 +314,6 @@ describe 'Common functions', () ->
assert.ok typeof data == 'object'
done() if not (--cou)

describe 'absolute move', () ->
it 'should returns empty RelativeResponseObject', (done) ->
cam.absoluteMove {
x: 1
y: 1
zoom: 1
}, done
it 'should works without callback', () ->
cam.absoluteMove {
x: 0
y: 0
zoom: 1
}

describe 'relative move', () ->
it 'should returns empty RelativeResponseObject', (done) ->
cam.relativeMove {
speed: {
x: 0.1
y: 0.1
}
x: 1
y: 1
zoom: 1
}, done
it 'should works without callback', () ->
cam.relativeMove {
speed: {
x: 0.1
y: 0.1
}
x: 1
y: 1
zoom: 1
}

describe 'continuous move', () ->
it 'should returns empty ContinuousResponseObject', (done) ->
cam.continuousMove {
x: 0.1
y: 0.1
zoom: 0
}, done
it 'should set ommited pan-tilt parameters to zero', (done) ->
cam.continuousMove {
x: 0.1
zoom: 0
}, done

describe 'stop', () ->
it 'should stop all movements when options are ommited', (done) ->
cam.stop done
it 'should stop only zoom movement', (done) ->
cam.stop {zoom: true}, done
it 'should stop only pan-tilt movement', (done) ->
cam.stop {panTilt: true}, done
it 'should stop all movements', (done) ->
cam.stop {zoom: true, panTilt: true}, done
it 'should work without callback', (done) ->
cam.stop {}
cam.stop()
done()

describe 'getStatus', () ->
it 'should returns position status', (done) ->
cam.getStatus {}, (err, data) ->
assert.equal err, null
done()

describe 'systemReboot', () ->
it 'should return a server message', (done) ->
cam.systemReboot (err, data) ->
Expand Down
4 changes: 2 additions & 2 deletions test/discovery.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe 'Discovery', () ->
assert.notEqual err, null
assert.ok emit
done()
it 'should got single device for one probe', (done) ->
it 'should get single device for one probe', (done) ->
cams = {}
onCam = (data) ->
if cams[data.probeMatches.probeMatch.XAddrs]
Expand All @@ -51,7 +51,7 @@ describe 'Discovery', () ->
assert.equal Object.keys(cams).length, cCams.length
onvif.Discovery.removeListener('device', onCam)
done()
it 'should got single device for one probe when `lo` is specified', (done) ->
it 'should get single device for one probe when `lo` is specified', (done) ->
cams = {}
onCam = (data) ->
if cams[data.probeMatches.probeMatch.XAddrs]
Expand Down
Loading

0 comments on commit 88a4575

Please sign in to comment.