Skip to content

Commit

Permalink
Merge pull request #322 from agsh/feat-add-promises
Browse files Browse the repository at this point in the history
Feat: add promises
  • Loading branch information
agsh authored May 24, 2024
2 parents 3522544 + 00ad424 commit 4982b40
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 165 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ test.js
cam_tests
experiments
docs
.nyc_output/
.nyc_output
.vscode
1 change: 0 additions & 1 deletion .nyc_output/6f8e2fb0-0e2d-410d-8361-422c514bf877.json

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion .nyc_output/processinfo/index.json

This file was deleted.

36 changes: 0 additions & 36 deletions .vscode/launch.json

This file was deleted.

131 changes: 79 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,50 @@ To build jsdoc for the library with default theme run `npm run jsdoc`. Otherwise
`./lib/*.js`

## Quick example
This example asks your camera to look up and starts a web server at port 3030 that distributes a web page with vlc-plugin
container which translates video from the camera.
```javascript
var
http = require('http'),
Cam = require('onvif').Cam;

new Cam({
hostname: <CAMERA_HOST>,
username: <USERNAME>,
password: <PASSWORD>
}, function(err) {
this.absoluteMove({x: 1, y: 1, zoom: 1});
this.getStreamUri({protocol:'RTSP'}, function(err, stream) {
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<html><body>' +
'<embed type="application/x-vlc-plugin" target="' + stream.uri + '"></embed>' +
'</body></html>');
}).listen(3030);

Special teasing example how to create little funny video server with 1 ffmpeg and 3 node.js libraries:
<video src="https://github.com/agsh/onvif/assets/576263/e816fed6-067a-4f77-b3f5-ccd9d5ff1310" width="300" />

```shell
sudo apt install ffmpeg
npm install onvif socket.io rtsp-ffmpeg
```

```js
const server = require('http').createServer((req, res) =>
res.end(`
<!DOCTYPE html><body>
<canvas width='640' height='480' />
<script src="/socket.io/socket.io.js"></script><script>
const socket = io(), ctx = document.getElementsByTagName('canvas')[0].getContext("2d");
socket.on('data', (data) => {
const img = new Image;
const url = URL.createObjectURL(new Blob([new Uint8Array(data)], {type: 'application/octet-binary'}));
img.onload = () => {
URL.revokeObjectURL(url, {type: 'application/octet-binary'});
ctx.drawImage(img, 100, 100);
};
img.src = url;
});
});
</script></body></html>`));
const { Cam } = require('onvif/promises'), io = require('socket.io')(server), rtsp = require('rtsp-ffmpeg');
server.listen(6147);

const cam = new Cam({username: 'username', password: 'password', hostname: '192.168.0.116', port: 2020});
(async() => {
await cam.connect();
const input = (await cam.getStreamUri({protocol:'RTSP'})).uri.replace('://', `://${cam.username}:${cam.password}@`);
const stream = new rtsp.FFMpeg({input, resolution: '320x240', quality: 3});
io.on('connection', (socket) => {
const pipeStream = socket.emit.bind(socket, 'data');
stream.on('disconnect', () => stream.removeListener('data', pipeStream)).on('data', pipeStream);
});
setInterval(() => cam.absoluteMove({
x: Math.random() * 2 - 1,
y: Math.random() * 2 - 1,
zoom: Math.random()
}), 3000);
})().catch(console.error);
```

## Other examples (located in the Examples Folder on the Github)
Expand All @@ -90,6 +112,7 @@ For Profile G Recorders it displays the RTSP address of the first recording
Short description of library possibilities is below.

## Discovery

Since 0.2.7 version library supports WS-Discovery of NVT devices. Currently it uses only `Probe` SOAP method that just works well.
You can find devices in your subnetwork using `probe` method of the Discovery singleton.
Discovery is an EventEmitter inheritor, so you can wait until discovery timeout, or subscribe on `device` event.
Expand Down Expand Up @@ -148,10 +171,39 @@ Options
and responseXML is a body of SOAP response
- `error(error)` fires on some UDP error or on bad SOAP response from NVT

## Promises

Right now master branch have a `onvif/promises` namespace that provides promisified version of Cam constructor returns
an object with the same methods as described below or in documentation but returns promises. Short example of common
usage is here:

```js
const onvif = require('onvif/promises');
onvif.Discovery.on('device', async (cam) => {
// Set credentials to connect
cam.username = 'username';
cam.password = 'password';
await cam.connect();
cam.on('event', (event)=> console.log(JSON.stringify(event.message, null, '\t')));
cam.on('eventsError', console.error);
console.log(cam.username, cam.password);
console.log((await cam.getStreamUri({protocol:'RTSP'})).uri);
const date = await cam.getSystemDateAndTime();
console.log(date);
await cam.absoluteMove({
x: Math.random() * 2 - 1,
y: Math.random() * 2 - 1,
zoom: Math.random()
});
});
onvif.Discovery.on('error', console.error);
onvif.Discovery.probe();
```

## Cam class

```javascript
var Cam = require('onvif').Cam;
const Cam = require('onvif').Cam;
```

## new Cam(options, callback)
Expand Down Expand Up @@ -353,12 +405,12 @@ Options and callback are optional. The options properties are:

### getStatus(options, callback)
*PTZ.* Returns an object with the current PTZ values.
```javascript
```js
{
position: {
x: 'pan position'
, y: 'tilt position'
, zoom: 'zoom'
x: 'pan position',
y: 'tilt position',
zoom: 'zoom'
}
, moveStatus: {} // camera moving
, utcTime: 'current camera datetime'
Expand All @@ -381,33 +433,8 @@ configuration object
### GetRecordingOptions(callback)
*Recordings.* Get the information of a recording token. Needed in order to match a recordingToken with a sourceToken. Used with both **GetRecordings** and **GetReplayUri** will allow to retreive recordings from an [Onvif Profile G](https://www.onvif.org/profiles/profile-g/) device. Note: not all devices are 100% Onvif G compliant.

## Supported methods
* GetSystemDateAndTime
* GetCapabilities
* GetVideoSources
* GetProfiles
* GetServices
* GetDeviceInformation
* GetStreamUri
* GetSnapshotUri
* GetPresets
* GotoPreset
* RelativeMove
* AbsoluteMove
* ContinuousMove
* Stop
* GetStatus
* SystemReboot
* GetImagingSettings
* SetImagingSettings
* GetHostname
* GetScopes
* SetScopes
* GetRecordings
* GetReplayUri
* GetRecordingOptions

## Changelog
- 0.7.1 Improved events handling
- 0.6.5 Add MEDIA2 support, Profile T and GetServices XAddrs support for H265 cameras. Add support for HTTPS. Add Discovery.on('error') to examples. Add flag to only send Zoom, or only send Pan/Tilt for some broken cameras (Sony XP1 Xiongmai). Fix bug in GetServices. Improve setNTP command. API changed on getNetworkInterfaces and other methods that could return an Array or a Single Item. We now return an Array in all cases. Add example converting library so it uses Promises with Promisify. Enable 3702 Discovery on Windows for MockServer. Add MockServer test cases)
- 0.6.1 Workaround for cams that don't send date-time
- 0.6.0 Refactor modules for proper import in electron-based environment
Expand Down
14 changes: 7 additions & 7 deletions examples/example3.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
*
* The GetPresets command is left as an asynchronous command
* and the presets list may come in some time after the StreamURI is displayed
*
*
*/

var HOSTNAME = '192.168.1.128',
PORT = 80,
USERNAME = '',
PASSWORD = '',
var HOSTNAME = '192.168.0.116',
PORT = 2020,
USERNAME = 'username',
PASSWORD = 'password',
STOP_DELAY_MS = 50;

var Cam = require('../lib/onvif').Cam;
Expand Down Expand Up @@ -160,7 +160,7 @@ new Cam({

function move(x_speed, y_speed, zoom_speed, msg) {
// Step 1 - Turn off the keyboard processing (so keypresses do not buffer up)
// Step 2 - Clear any existing 'stop' timeouts. We will re-schedule a new 'stop' command in this function
// Step 2 - Clear any existing 'stop' timeouts. We will re-schedule a new 'stop' command in this function
// Step 3 - Send the Pan/Tilt/Zoom 'move' command.
// Step 4 - In the callback from the PTZ 'move' command we schedule the ONVIF Stop command to be executed after a short delay and re-enable the keyboard

Expand All @@ -181,7 +181,7 @@ new Cam({
console.log(err);
} else {
console.log('move command sent ' + msg);
// schedule a Stop command to run in the future
// schedule a Stop command to run in the future
stop_timer = setTimeout(stop,STOP_DELAY_MS);
}
// Resume keyboard processing
Expand Down
28 changes: 16 additions & 12 deletions lib/cam.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,23 @@ const http = require('http'),
* @property {?Error} error
*/

/**
* @typedef Cam~Options
* @property {boolean} useSecure Set true if `https:`, defaults to false
* @property {object} secureOpts Set options for https like ca, cert, ciphers, rejectUnauthorized, secureOptions, secureProtocol, etc.
* @property {string} hostname
* @property {string} [username]
* @property {string} [password]
* @property {number} [port=80]
* @property {string} [path=/onvif/device_service]
* @property {number} [timeout=120000]
* @property {boolean} [autoconnect=true] Set false if the camera should not connect automatically. The callback will not be executed.
* @property {boolean} [preserveAddress=false] Force using hostname and port from constructor for the services
*/

/**
* Camera class
* @param {object} options
* @param {boolean} options.useSecure Set true if `https:`, defaults to false
* @param {object} options.secureOpts Set options for https like ca, cert, ciphers, rejectUnauthorized, secureOptions, secureProtocol, etc.
* @param {string} options.hostname
* @param {string} [options.username]
* @param {string} [options.password]
* @param {number} [options.port=80]
* @param {string} [options.path=/onvif/device_service]
* @param {number} [options.timeout=120000]
* @param {boolean} [options.autoconnect=true] Set false if the camera should not connect automatically. The callback will not be executed.
* @param {boolean} [options.preserveAddress=false] Force using hostname and port from constructor for the services
* @param {Cam~Options} options
* @param {Cam~ConnectionCallback} [callback]
* @fires Cam#rawRequest
* @fires Cam#rawResponse
Expand Down Expand Up @@ -260,7 +264,7 @@ Cam.prototype._requestPart2 = function(options, callback) {
agent: this.agent //Supports things like https://www.npmjs.com/package/proxy-agent which provide SOCKS5 and other connections
,
path: options.service ?
(this.uri[options.service] ? this.uri[options.service].path : options.service) : this.path,
(this.uri && this.uri[options.service] ? this.uri[options.service].path : options.service) : this.path,
timeout: this.timeout
};
reqOptions.headers = {
Expand Down
6 changes: 3 additions & 3 deletions lib/onvif.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
*/

module.exports = {
Cam: require('./cam').Cam
, Discovery: require('./discovery').Discovery
};
Cam: require('./cam').Cam,
Discovery: require('./discovery').Discovery
};
Loading

0 comments on commit 4982b40

Please sign in to comment.