Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add promises #322

Merged
merged 2 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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