-
Notifications
You must be signed in to change notification settings - Fork 25
/
onvif_config.js
225 lines (193 loc) · 10.7 KB
/
onvif_config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* Copyright 2018 Bart Butenaers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
var settings = RED.settings;
const onvif = require('onvif');
function setOnvifStatus(node, onvifStatus) {
node.onvifStatus = onvifStatus;
// Pass the new status to all the available listeners
node.emit('onvif_status', onvifStatus);
}
function OnVifConfigNode(config) {
RED.nodes.createNode(this, config);
this.xaddress = config.xaddress;
this.port = parseInt(config.port || 80);
this.name = config.name;
this.timeout = config.timeout || 3;
this.checkConnectionInterval = config.checkConnectionInterval || 5;
// Remark: user name and password are stored in this.credentials
var node = this;
// All Onvif nodes can add a listener to track the 'onvif_status' events.
// However by default only 10 listeners are allowed, which results in a warning when more Onvif nodes use this config node:
// MaxListenersExceededWarning: Possible EventEmitter memory leak detected
// To avoid that, we will allow an infinite number of listeners.
// Caution: when you have suspicion that the listeners are leaking, put the next line in comment !!!
node.setMaxListeners(0);
this.getProfiles = function(clientConfig, response) {
var profileNames = [];
var config = {};
// TODO checken of er altijd een this.credentials bestaat, indien username en paswoord niet ingevuld is.
// The client credentials will only contain the data (i.e. user name or password) which has changed.
// The other data is not changed, so we will need use the original data stored on the server.
clientConfig.username = clientConfig.user || this.credentials.user;
clientConfig.password = clientConfig.password || this.credentials.password;
// When the user appends some new text to the existing password, then the original password is passed via the client as __PWRD__
// So replace __PWRD__ again by the original password.
if (clientConfig.password && this.credentials.password) {
clientConfig.password.replace('___PWRD__', this.credentials.password);
}
if (this.credentials.user !== clientConfig.user || this.credentials.password !== clientConfig.password || this.xaddress !== clientConfig.hostname){
var cam = new onvif.Cam(clientConfig, function(err) {
if (!err) {
if (cam.profiles) {
for(var i = 0; i < cam.profiles.length; i++) {
profileNames.push({
label: cam.profiles[i].name,
value: cam.profiles[i].$.token
});
}
}
response.json(profileNames);
}
});
}
else {
if (this.cam.profiles) {
// The current deployed cam is still up-to-date, so let’s use that one (for performance reasons)
for(var i = 0; i < this.cam.profiles.length; i++) {
profileNames.push({
label: this.cam.profiles[i].name,
value: this.cam.profiles[i].$.token
});
}
}
response.json(profileNames);
}
}
this.getProfileTokenByName = function(profileName) {
if (this.cam.profiles) {
// Try to find a profile with the specified name, and return the token
for(var i = 0; i < this.cam.profiles.length; i++) {
if (this.cam.profiles[i].name === profileName) {
return this.cam.profiles[i].$.token;
}
}
}
// No token found with the specified name
return null;
}
// This should be called by all nodes that use this config node
this.initialize = function() {
// This config node can only be initialized oncellchange
if (this.cam) {
return;
}
// Without an xaddress, it is impossible to connect to an Onvif device
if (!this.xaddress) {
// Make sure the Catch-node can catch the error
node.error( "Cannot connect to unconfigured Onvif device", {} );
this.cam = null;
setOnvifStatus(node, "unconfigured");
return;
}
setOnvifStatus(node, "initializing");
var options = {};
options.hostname = this.xaddress;
options.port = this.port;
options.timeout = this.timeout * 1000;
if (this.credentials && this.credentials.user) {
options.username = this.credentials.user;
options.password = this.credentials.password;
}
// Create a new camera instance, which will automatically connect to the device (to load configuration data)
this.cam = new onvif.Cam(options, function(err) {
if (err) {
// Make sure the Catch-node can catch the error
node.error( err, {} );
setOnvifStatus(node, "disconnected");
}
else {
setOnvifStatus(node, "connected");
}
});
// When an checkConnection timer is running, then stop it
if (this.checkConnectionTimer) {
clearInterval(this.checkConnectionTimer);
this.checkConnectionTimer = null;
}
if (this.checkConnectionInterval > 0) {
// Start a new checkConnection timer, that checks at the specified interval whether the Onvif device is disconnected.
// This way we can keep the node status in the flow editor up to date ...
this.checkConnectionTimer = setInterval(function() {
// Check whether the Onvif device is connected, by calling the device system and time
node.cam.getSystemDateAndTime(function(err, date, xml) {
if (err) {
setOnvifStatus(node, "disconnected");
}
else {
if (!node.cam.capabilities && !node.cam.services) {
// Probably the device was unavailable when this node was being started, so the autoConnect was not able
// to load all the data from the camera (like e.g. its capabilities). Afterwards the device became available,
// so it is now time to load the device data into the Cam instance (by calling its 'connect' function).
// Note that this needs to be executed before setOnvifStatus, because that function uses the capabilities.
node.cam.connect(function(err, date, xml) {
if (err) {
node.error("The device is now connected, but the data cannot be loaded");
}
});
}
setOnvifStatus(node, "connected");
}
});
}, this.checkConnectionInterval * 1000);
}
}
node.on('close', function(){
setOnvifStatus(this, "");
this.removeAllListeners("onvif_status");
// When an checkConnection timer is running, then stop it
if (this.checkConnectionTimer) {
clearInterval(this.checkConnectionTimer);
this.checkConnectionTimer = null;
}
});
}
RED.nodes.registerType("onvif-config",OnVifConfigNode,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
// Make all the available profiles accessible for the node's config screen
RED.httpAdmin.get('/onvifdevice/:cmd/:config_node_id', RED.auth.needsPermission('onvifdevice.read'), function(req, res){
var configNode = RED.nodes.getNode(req.params.config_node_id);
switch (req.params.cmd) {
case "profiles":
if (!configNode) {
console.log("Cannot determine profile list from node " + req.params.config_node_id);
return;
}
// Get the profiles of the camera, based on the config data on the client, instead of the config data
// stored inside this config node. Reason is that the config data on the client might be 'dirty', i.e. changed
// by the user but not deployed yet on this config node. But the client still needs to be able to get the profiles
// corresponding to that dirty config node. That way the config screen can be filled with profiles already...
// But when the config data is not dirty, we will just use the profiles already loaded in this config node (which is faster).
// See https://discourse.nodered.org/t/initializing-config-screen-based-on-new-config-node/7327/10?u=bartbutenaers
configNode.getProfiles(req.query, res);
break;
}
});
}