@tier0/node-opc-da 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +171 -0
- package/docs/opcda.idl +1166 -0
- package/package.json +30 -0
- package/src/constants.js +212 -0
- package/src/enumString.js +161 -0
- package/src/filetime.js +79 -0
- package/src/index.js +62 -0
- package/src/opcAsyncIO.js +102 -0
- package/src/opcBrowser.js +285 -0
- package/src/opcCommon.js +84 -0
- package/src/opcGroupStateManager.js +248 -0
- package/src/opcItemIO.js +60 -0
- package/src/opcItemManager.js +415 -0
- package/src/opcItemProperties.js +102 -0
- package/src/opcServer.js +447 -0
- package/src/opcSyncIO.js +213 -0
- package/src/opctypes.js +116 -0
package/src/opcServer.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/*
|
|
3
|
+
Copyright: (c) 2019, Guilherme Francescon Cittolin <gfcittolin@gmail.com>
|
|
4
|
+
GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const constants = require('./constants.js');
|
|
8
|
+
const EnumString = require('./enumString.js');
|
|
9
|
+
const OPCCommon = require('./opcCommon.js');
|
|
10
|
+
const OPCBrowser = require('./opcBrowser.js');
|
|
11
|
+
const OPCItemIO = require('./opcItemIO.js');
|
|
12
|
+
const OPCItemProperties = require('./opcItemProperties');
|
|
13
|
+
const OPCGroupStateManager = require('./opcGroupStateManager');
|
|
14
|
+
const filetime = require('./filetime');
|
|
15
|
+
const util = require('util');
|
|
16
|
+
const debug = util.debuglog('node-opca-da');
|
|
17
|
+
const events = require('events');
|
|
18
|
+
const { CallBuilder, ComString, ComValue, Flags, Pointer, Struct, Types } = require('node-dcom');
|
|
19
|
+
|
|
20
|
+
const groupCache = new WeakMap();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {import('./opcGroupStateManager').GroupProperties} GroupProperties
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} GroupStatus
|
|
28
|
+
* @property {Date} startTime
|
|
29
|
+
* @property {Date} currentTime
|
|
30
|
+
* @property {Date} lastUpdateTime
|
|
31
|
+
* @property {number} serverState
|
|
32
|
+
* @property {number} groupCount
|
|
33
|
+
* @property {number} bandWidth
|
|
34
|
+
* @property {number} majorVersion
|
|
35
|
+
* @property {number} minorVersion
|
|
36
|
+
* @property {number} buildNumber
|
|
37
|
+
* @property {number} reserved
|
|
38
|
+
* @property {string} vendorInfo
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents an OPC Server
|
|
43
|
+
*/
|
|
44
|
+
class OPCServer extends events.EventEmitter {
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param {object} [opts]
|
|
49
|
+
* @param {object} [opts.groupDefaults]
|
|
50
|
+
* @param {boolean} [opts.groupDefaults.active=true]
|
|
51
|
+
* @param {number} [opts.groupDefaults.updateRate=1000]
|
|
52
|
+
* @param {number} [opts.groupDefaults.timeBias=60]
|
|
53
|
+
* @param {number} [opts.groupDefaults.deadband=0]
|
|
54
|
+
* @param {number} [opts.defaultLocale=1033]
|
|
55
|
+
*/
|
|
56
|
+
constructor(opts) {
|
|
57
|
+
debug("Creating OPCServer...");
|
|
58
|
+
//TODO - default values for addGroup
|
|
59
|
+
super();
|
|
60
|
+
opts = opts || {};
|
|
61
|
+
this._defaultLocale = opts.defaultLocale || 1033;
|
|
62
|
+
|
|
63
|
+
let def = opts.groupDefaults || {};
|
|
64
|
+
this._groupDef = {
|
|
65
|
+
active: !!(def.active !== undefined ? def.active : true),
|
|
66
|
+
updateRate: !isNaN(def.updateRate) ? def.updateRate : 1000,
|
|
67
|
+
timeBias: !isNaN(def.timeBias) ? def.timeBias : 60,
|
|
68
|
+
deadband: !isNaN(def.deadband) ? def.deadband : 0,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this._comObj = null;
|
|
72
|
+
this._opcCommon = null;
|
|
73
|
+
this._opcBrowser = null;
|
|
74
|
+
this._opcItemIO = null;
|
|
75
|
+
this._opcItemProperties = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
* @param {*} unknown
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
async init(unknown) {
|
|
84
|
+
debug("Initing OPCServer...")
|
|
85
|
+
if (this._comObj) throw new Error("Already initialized");
|
|
86
|
+
|
|
87
|
+
if (!unknown) throw new Error("OPCServer init failed. No COM Server given.");
|
|
88
|
+
|
|
89
|
+
this._comObj = await unknown.queryInterface(constants.iid.IOPCServer_IID);
|
|
90
|
+
|
|
91
|
+
// now that we have the comObj, we can get the events emitted
|
|
92
|
+
this._comObj.on('disconnected', function(){
|
|
93
|
+
this.emit('disconnected');
|
|
94
|
+
});
|
|
95
|
+
debug("OPCServer inited successfully");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
*/
|
|
101
|
+
async end() {
|
|
102
|
+
debug("Ending OPCServer...")
|
|
103
|
+
if (!this._comObj) return;
|
|
104
|
+
|
|
105
|
+
let obj = this._comObj;
|
|
106
|
+
let opcCommon = this._opcCommon;
|
|
107
|
+
let opcBrowser = this._opcBrowser;
|
|
108
|
+
let opcItemIO = this._opcItemIO;
|
|
109
|
+
let opcItemProperties = this._opcItemProperties;
|
|
110
|
+
this._comObj = null;
|
|
111
|
+
this._opcCommon = null;
|
|
112
|
+
this._opcBrowser = null;
|
|
113
|
+
this._opcItemIO = null;
|
|
114
|
+
this._opcItemProperties = null;
|
|
115
|
+
|
|
116
|
+
//TODO maybe paralelize this with Promise.all
|
|
117
|
+
if (opcCommon) await opcCommon.end();
|
|
118
|
+
if (opcBrowser) await opcBrowser.end();
|
|
119
|
+
if (opcItemIO) await opcItemIO.end();
|
|
120
|
+
if (opcItemProperties) await opcItemProperties.end();
|
|
121
|
+
await obj.release();
|
|
122
|
+
debug("OPCServer successfully ended.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
*
|
|
127
|
+
* @param {String} name the group name
|
|
128
|
+
* @param {GroupProperties} [opts]
|
|
129
|
+
* @returns {Promise<?>}
|
|
130
|
+
* @opNum 0
|
|
131
|
+
*/
|
|
132
|
+
async addGroup(name, opts) {
|
|
133
|
+
debug("Adding new group \"" + name + "\"...");
|
|
134
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
135
|
+
|
|
136
|
+
opts = opts || {
|
|
137
|
+
active: this._groupDef.active,
|
|
138
|
+
updateRate: this._groupDef.updateRate,
|
|
139
|
+
clientHandle: Math.trunc(Math.random() * 0xFFFFFFFF),
|
|
140
|
+
timeBias: this._groupDef.timeBias,
|
|
141
|
+
deadband: this._groupDef.deadband,
|
|
142
|
+
localeID: this._defaultLocale
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
let active = opts.active !== undefined ? opts.active : this._groupDef.active;
|
|
146
|
+
let updateRate = !isNaN(opts.updateRate) ? opts.updateRate : this._groupDef.updateRate;
|
|
147
|
+
let clientHandle = !isNaN(opts.clientHandle) ? opts.clientHandle : Math.trunc(Math.random() * 0xFFFFFFFF);
|
|
148
|
+
let timeBias = !isNaN(opts.timeBias) ? opts.timeBias : this._groupDef.timeBias;
|
|
149
|
+
let deadband = !isNaN(opts.deadband) ? opts.deadband : this._groupDef.deadband;
|
|
150
|
+
let localeID = !isNaN(opts.localeID) ? opts.localeID : this._defaultLocale;
|
|
151
|
+
|
|
152
|
+
let timeBiasCV = new ComValue(timeBias, Types.INTEGER);
|
|
153
|
+
let deadbandCV = new ComValue(deadband, Types.FLOAT);
|
|
154
|
+
|
|
155
|
+
let callObject = new CallBuilder(true);
|
|
156
|
+
callObject.setOpnum(0);
|
|
157
|
+
|
|
158
|
+
callObject.addInParamAsString(name, Flags.FLAG_REPRESENTATION_STRING_LPWSTR);
|
|
159
|
+
callObject.addInParamAsInt(active ? 1 : 0, Flags.FLAG_NULL);
|
|
160
|
+
callObject.addInParamAsInt(updateRate, Flags.FLAG_NULL);
|
|
161
|
+
callObject.addInParamAsInt(clientHandle, Flags.FLAG_NULL);
|
|
162
|
+
callObject.addInParamAsPointer(new Pointer(timeBiasCV), Flags.FLAG_NULL);
|
|
163
|
+
callObject.addInParamAsPointer(new Pointer(deadbandCV), Flags.FLAG_NULL);
|
|
164
|
+
callObject.addInParamAsInt(localeID, Flags.FLAG_NULL);
|
|
165
|
+
callObject.addOutParamAsType(Types.INTEGER, Flags.FLAG_NULL);
|
|
166
|
+
callObject.addOutParamAsType(Types.INTEGER, Flags.FLAG_NULL);
|
|
167
|
+
callObject.addInParamAsUUID(constants.iid.IOPCGroupStateMgt_IID, Flags.FLAG_NULL);
|
|
168
|
+
callObject.addOutParamAsType(Types.COMOBJECT, Flags.FLAG_NULL);
|
|
169
|
+
|
|
170
|
+
let resultObj = await this._comObj.call(callObject);
|
|
171
|
+
|
|
172
|
+
let hresult = resultObj.hresult;
|
|
173
|
+
let result = resultObj.getResults();
|
|
174
|
+
if (hresult != 0) {
|
|
175
|
+
if (result.lenght == 0)
|
|
176
|
+
throw new Error(String(hresult));
|
|
177
|
+
else {
|
|
178
|
+
if (hresult == 0x0004000D) {
|
|
179
|
+
this.emit('opc_s_unsuportedrate');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let group = new OPCGroupStateManager();
|
|
185
|
+
await group.init(result[2].getValue());
|
|
186
|
+
groupCache.set(group, clientHandle);
|
|
187
|
+
debug(name + " added successfully.");
|
|
188
|
+
return group;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @param {number} error
|
|
194
|
+
* @param {number} [localeID]
|
|
195
|
+
* @returns {Promise<?>} a descriptive string for the error code
|
|
196
|
+
* @opNum 1
|
|
197
|
+
*/
|
|
198
|
+
async getErrorString(error, localeID) {
|
|
199
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
200
|
+
|
|
201
|
+
let callObject = new CallBuilder(true);
|
|
202
|
+
callObject.setOpnum(1);
|
|
203
|
+
|
|
204
|
+
localeID = localeID || this._defaultLocale;
|
|
205
|
+
|
|
206
|
+
let outString = new ComValue(new ComString(Flags.FLAG_REPRESENTATION_STRING_LPWSTR), Types.COMSTRING);
|
|
207
|
+
|
|
208
|
+
callObject.addInParamAsInt(error, Flags.FLAG_NULL);
|
|
209
|
+
callObject.addInParamAsInt(localeID, Flags.FLAG_NULL);
|
|
210
|
+
callObject.addOutParamAsObject(outString, Flags.FLAG_NULL);
|
|
211
|
+
//callObject.addOutParamAsObject(new Pointer(new ComString(Flags.FLAG_REPRESENTATION_STRING_LPWSTR)), Flags.FLAG_NULL);
|
|
212
|
+
|
|
213
|
+
let resultObj = await this._comObj.call(callObject);
|
|
214
|
+
|
|
215
|
+
let hresult = resultObj.hresult;
|
|
216
|
+
let result = resultObj.getResults();
|
|
217
|
+
if (hresult != 0) {
|
|
218
|
+
if (result.lenght == 0)
|
|
219
|
+
throw new Error(String(hresult));
|
|
220
|
+
else
|
|
221
|
+
debug(new Error(String(hresult)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result[0].getString();
|
|
225
|
+
//return result[0].getReferent().getString();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
*
|
|
230
|
+
* @param {string} name
|
|
231
|
+
* @returns {Promise<OPCGroupStateManager>}
|
|
232
|
+
* @opNum 2
|
|
233
|
+
*/
|
|
234
|
+
async getGroupByName(name) {
|
|
235
|
+
debug("Getting group \"" + name + "\"...");
|
|
236
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
237
|
+
|
|
238
|
+
let callObject = new CallBuilder(true);
|
|
239
|
+
callObject.setOpnum(2);
|
|
240
|
+
|
|
241
|
+
callObject.addInParamAsString(name, Flags.FLAG_REPRESENTATION_STRING_LPWSTR);
|
|
242
|
+
callObject.addInParamAsUUID(constants.iid.IOPCGroupStateMgt_IID, Flags.FLAG_NULL);
|
|
243
|
+
callObject.addOutParamAsType(Types.COMOBJECT, Flags.FLAG_NULL);
|
|
244
|
+
|
|
245
|
+
let resultObj = await this._comObj.call(callObject);
|
|
246
|
+
|
|
247
|
+
let hresult = resultObj.hresult;
|
|
248
|
+
let result = resultObj.getResults();
|
|
249
|
+
if (hresult != 0) {
|
|
250
|
+
if (result.lenght == 0)
|
|
251
|
+
throw new Error(String(hresult));
|
|
252
|
+
else
|
|
253
|
+
debug(new Error(String(hresult)));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let group = new OPCGroupStateManager();
|
|
257
|
+
await group.init(result[0]);
|
|
258
|
+
debug("Successfully get request for group \"" + name +"\".");
|
|
259
|
+
return group;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @returns {Promise<GroupStatus>} the server status
|
|
264
|
+
* @opNum 3
|
|
265
|
+
*/
|
|
266
|
+
async getStatus() {
|
|
267
|
+
debug("Querying server for status information..");
|
|
268
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
269
|
+
|
|
270
|
+
let statusStruct = new Struct();
|
|
271
|
+
statusStruct.addMember(new ComValue(filetime.getStruct(), Types.STRUCT)); //startTime
|
|
272
|
+
statusStruct.addMember(new ComValue(filetime.getStruct(), Types.STRUCT)); //currentTime
|
|
273
|
+
statusStruct.addMember(new ComValue(filetime.getStruct(), Types.STRUCT)); //lastUpdate
|
|
274
|
+
statusStruct.addMember(new ComValue(null, Types.SHORT)); //serverState
|
|
275
|
+
statusStruct.addMember(new ComValue(null, Types.INTEGER)); //groupCount
|
|
276
|
+
statusStruct.addMember(new ComValue(null, Types.INTEGER)); //bandwidth
|
|
277
|
+
statusStruct.addMember(new ComValue(null, Types.SHORT)); //majorVersion
|
|
278
|
+
statusStruct.addMember(new ComValue(null, Types.SHORT)); //minorVersion
|
|
279
|
+
statusStruct.addMember(new ComValue(null, Types.SHORT)); //buildNumber
|
|
280
|
+
statusStruct.addMember(new ComValue(null, Types.SHORT)); //reserved
|
|
281
|
+
let vendorInfoCV = new ComValue(new ComString(Flags.FLAG_REPRESENTATION_STRING_LPWSTR), Types.COMSTRING);
|
|
282
|
+
statusStruct.addMember(new ComValue(new Pointer(vendorInfoCV), Types.POINTER)); //vendorInfo
|
|
283
|
+
|
|
284
|
+
let statusStructPointer = new Pointer(new ComValue(statusStruct, Types.STRUCT));
|
|
285
|
+
|
|
286
|
+
let callObject = new CallBuilder(true);
|
|
287
|
+
callObject.setOpnum(3);
|
|
288
|
+
callObject.addOutParamAsObject(new ComValue(statusStructPointer, Types.POINTER), Flags.FLAG_NULL);
|
|
289
|
+
|
|
290
|
+
let resultObj = await this._comObj.call(callObject);
|
|
291
|
+
|
|
292
|
+
let hresult = resultObj.hresult;
|
|
293
|
+
let result = resultObj.getResults();
|
|
294
|
+
if (hresult != 0) {
|
|
295
|
+
if (result.lenght == 0)
|
|
296
|
+
throw new Error(String(hresult));
|
|
297
|
+
else
|
|
298
|
+
debug(new Error(String(hresult)));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let resStruct = result[0].getValue().getReferent();
|
|
302
|
+
|
|
303
|
+
debug("Server Status information obtained.");
|
|
304
|
+
return {
|
|
305
|
+
startTime: filetime.fromStruct(resStruct.getMember(0).getValue()).getDate(),
|
|
306
|
+
currentTime: filetime.fromStruct(resStruct.getMember(1).getValue()).getDate(),
|
|
307
|
+
lastUpdateTime: filetime.fromStruct(resStruct.getMember(2).getValue()).getDate(),
|
|
308
|
+
serverState: resStruct.getMember(3).getValue(),
|
|
309
|
+
groupCount: resStruct.getMember(4).getValue(),
|
|
310
|
+
bandWidth: resStruct.getMember(5).getValue(),
|
|
311
|
+
majorVersion: resStruct.getMember(6).getValue(),
|
|
312
|
+
minorVersion: resStruct.getMember(7).getValue(),
|
|
313
|
+
buildNumber: resStruct.getMember(8).getValue(),
|
|
314
|
+
reserved: resStruct.getMember(9).getValue(),
|
|
315
|
+
vendorInfo: resStruct.getMember(10).getValue().getReferent().getString()
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
*
|
|
321
|
+
* @param {OPCGroupStateManager|number} handle
|
|
322
|
+
* @param {boolean} [force=false]
|
|
323
|
+
* @returns {Promise<?>}
|
|
324
|
+
* @opNum 4
|
|
325
|
+
*/
|
|
326
|
+
async removeGroup(handle, force) {
|
|
327
|
+
debug("Removing group \"" + name + "\"...");
|
|
328
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
329
|
+
|
|
330
|
+
if (handle instanceof OPCGroupStateManager) {
|
|
331
|
+
handle = groupCache.get(handle);
|
|
332
|
+
}
|
|
333
|
+
if (typeof handle != 'number') {
|
|
334
|
+
throw new Error("Cannot find handle for the provided group");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let callObject = new CallBuilder(true);
|
|
338
|
+
callObject.setOpnum(4);
|
|
339
|
+
|
|
340
|
+
callObject.addInParamAsInt(handle, Flags.FLAG_NULL);
|
|
341
|
+
callObject.addInParamAsInt(force ? 1 : 0, Flags.FLAG_NULL);
|
|
342
|
+
|
|
343
|
+
await this._comObj.call(callObject);
|
|
344
|
+
debug("Group \"" + name + "\" successfully removed.");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
* @param {number} scope an OPC scope
|
|
350
|
+
* @returns {Promise<string[]>}
|
|
351
|
+
* @opNum 5
|
|
352
|
+
*/
|
|
353
|
+
async getGroups(scope) {
|
|
354
|
+
debug("Querying OPCServer for a list of available groups...");
|
|
355
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
356
|
+
|
|
357
|
+
let callObject = new CallBuilder(true);
|
|
358
|
+
callObject.setOpnum(5);
|
|
359
|
+
|
|
360
|
+
callObject.addInParamAsShort(scope, Flags.FLAG_NULL);
|
|
361
|
+
callObject.addInParamAsUUID(constants.iid.IEnumString_IID, Flags.FLAG_NULL);
|
|
362
|
+
callObject.addOutParamAsType(Types.COMOBJECT, Flags.FLAG_NULL);
|
|
363
|
+
|
|
364
|
+
let resultObj = await this._comObj.call(callObject);
|
|
365
|
+
|
|
366
|
+
let hresult = resultObj.hresult;
|
|
367
|
+
let result = resultObj.getResults();
|
|
368
|
+
if (hresult != 0) {
|
|
369
|
+
if (result.lenght == 0)
|
|
370
|
+
throw new Error(String(hresult));
|
|
371
|
+
else
|
|
372
|
+
debug("No groups were found");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let enumStr = new EnumString();
|
|
376
|
+
await enumStr.init(result[0].getValue());
|
|
377
|
+
let res = await enumStr.asArray();
|
|
378
|
+
await enumStr.end(hresult);
|
|
379
|
+
debug("Group list successfully obtained.");
|
|
380
|
+
return res;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ------
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @returns {Promise<OPCCommon>} an OPCCommon instance of this server
|
|
387
|
+
*/
|
|
388
|
+
async getCommon() {
|
|
389
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
390
|
+
|
|
391
|
+
if (!this._opcCommon) {
|
|
392
|
+
let opcCommon = new OPCCommon(); //TODO pass comObj handle
|
|
393
|
+
await opcCommon.init(this._comObj);
|
|
394
|
+
this._opcCommon = opcCommon;
|
|
395
|
+
}
|
|
396
|
+
return this._opcCommon;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @returns {Promise<OPCBrowser>} an OPCBrowser instance of this server
|
|
401
|
+
*/
|
|
402
|
+
async getBrowser() {
|
|
403
|
+
debug("Creating a browser instance...");
|
|
404
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
405
|
+
|
|
406
|
+
if (!this._opcBrowser) {
|
|
407
|
+
let opcBrowser = new OPCBrowser(); //TODO pass comObj handle
|
|
408
|
+
await opcBrowser.init(this._comObj);
|
|
409
|
+
this._opcBrowser = opcBrowser;
|
|
410
|
+
}
|
|
411
|
+
debug("Browser instance succesfully created.");
|
|
412
|
+
return this._opcBrowser;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* @returns {Promise<OPCItemIO>} an OPCBrowser insance of this server
|
|
417
|
+
*/
|
|
418
|
+
async getItemIO() {
|
|
419
|
+
debug("Creating an ItemIO instance...");
|
|
420
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
421
|
+
|
|
422
|
+
if (!this._opcItemIO) {
|
|
423
|
+
let opcItemIO = new OPCItemIO(); //TODO pass comObj handle
|
|
424
|
+
await opcItemIO.init(this._comObj);
|
|
425
|
+
this._opcItemIO = opcItemIO;
|
|
426
|
+
}
|
|
427
|
+
debug("ItemIO instance successfully created.");
|
|
428
|
+
return this._opcItemIO;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @returns {Promise<OPCItemProperties>} an OPCBrowser insance of this server
|
|
433
|
+
*/
|
|
434
|
+
async getItemProperties() {
|
|
435
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
436
|
+
|
|
437
|
+
if (!this._opcItemProperties) {
|
|
438
|
+
let opcItemProperties = new OPCItemProperties(); //TODO pass comObj handle
|
|
439
|
+
await opcItemProperties.init(this._comObj);
|
|
440
|
+
this._opcItemProperties = opcItemProperties;
|
|
441
|
+
}
|
|
442
|
+
return this._opcItemProperties;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
module.exports = OPCServer;
|
package/src/opcSyncIO.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/*
|
|
3
|
+
Copyright: (c) 2019, Guilherme Francescon Cittolin <gfcittolin@gmail.com>
|
|
4
|
+
GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const constants = require('./constants.js');
|
|
8
|
+
const filetime = require('./filetime');
|
|
9
|
+
const util = require('util');
|
|
10
|
+
const dcom = require('node-dcom');
|
|
11
|
+
const debug = util.debuglog('node-opc-da');
|
|
12
|
+
|
|
13
|
+
const { CallBuilder, ComArray, ComValue, Flags, Pointer, Struct, Variant, Types } = require('node-dcom');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents an OPC Sync IO Object
|
|
17
|
+
*/
|
|
18
|
+
class OPCSyncIO {
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this._comObj = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {*} unknown
|
|
27
|
+
* @returns {Promise<?>}
|
|
28
|
+
*/
|
|
29
|
+
async init(unknown) {
|
|
30
|
+
debug("Initing OPCSyncIO...");
|
|
31
|
+
if (this._comObj) throw new Error("Already initialized");
|
|
32
|
+
|
|
33
|
+
this._comObj = await unknown.queryInterface(constants.iid.IOPCSyncIO_IID);
|
|
34
|
+
|
|
35
|
+
debug("OPCSyncIO successfully initted.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async end() {
|
|
39
|
+
debug("Destroying OPCSyncIO...");
|
|
40
|
+
if (!this._comObj) return;
|
|
41
|
+
|
|
42
|
+
let obj = this._comObj;
|
|
43
|
+
this._comObj = null;
|
|
44
|
+
await obj.release();
|
|
45
|
+
debug("OPCSyncIO successfully destroyed.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {number} source
|
|
51
|
+
* @param {number[]} handles an array of server handles
|
|
52
|
+
* @returns {Promise<object[]>}
|
|
53
|
+
* @opNum 0
|
|
54
|
+
*/
|
|
55
|
+
async read(source, handles) {
|
|
56
|
+
debug("Reading " + handles.length + " items from " + source + ":");
|
|
57
|
+
for (let i = 0; i < handles.length; i++) {
|
|
58
|
+
debug("Item Handle: " + handles[i]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
62
|
+
|
|
63
|
+
if (!(handles.length > 0)) return [];
|
|
64
|
+
|
|
65
|
+
// TODO maybe we can have a single static instance of this,
|
|
66
|
+
// without the need to instantiate one every call. To be tested
|
|
67
|
+
let itemStateStruct = new Struct();
|
|
68
|
+
itemStateStruct.addMember(new ComValue(null, Types.INTEGER));
|
|
69
|
+
itemStateStruct.addMember(new ComValue(filetime.getStruct(), Types.STRUCT));
|
|
70
|
+
itemStateStruct.addMember(new ComValue(null, Types.SHORT));
|
|
71
|
+
itemStateStruct.addMember(new ComValue(null, Types.SHORT));
|
|
72
|
+
itemStateStruct.addMember(new ComValue(null, Types.VARIANT));
|
|
73
|
+
|
|
74
|
+
let callObject = new CallBuilder(true);
|
|
75
|
+
callObject.setOpnum(0);
|
|
76
|
+
|
|
77
|
+
callObject.addInParamAsShort(source, Flags.FLAG_NULL);
|
|
78
|
+
callObject.addInParamAsInt(handles.length, Flags.FLAG_NULL);
|
|
79
|
+
|
|
80
|
+
let temporaryHandles = new Array();
|
|
81
|
+
for (let i = 0; i < handles.length; i++)
|
|
82
|
+
temporaryHandles.push(new ComValue(handles[i], Types.INTEGER));
|
|
83
|
+
callObject.addInParamAsArray(new ComArray(new ComValue(temporaryHandles, Types.INTEGER), true), Flags.FLAG_NULL);
|
|
84
|
+
|
|
85
|
+
let resStructArray = new ComArray(new ComValue(itemStateStruct, Types.STRUCT), null, 1, true)
|
|
86
|
+
let errCodesArray = new ComArray(new ComValue(null, Types.INTEGER), null, 1, true)
|
|
87
|
+
callObject.addOutParamAsObject(new ComValue(new Pointer(new ComValue(resStructArray, Types.COMARRAY)), Types.POINTER), Flags.FLAG_NULL);
|
|
88
|
+
callObject.addOutParamAsObject(new ComValue(new Pointer(new ComValue(errCodesArray, Types.COMARRAY)), Types.POINTER), Flags.FLAG_NULL);
|
|
89
|
+
|
|
90
|
+
let resultObj = await this._comObj.call(callObject);
|
|
91
|
+
|
|
92
|
+
let hresult = resultObj.hresult;
|
|
93
|
+
let result = resultObj.getResults();
|
|
94
|
+
if (hresult != 0) {
|
|
95
|
+
if (result.lenght == 0)
|
|
96
|
+
throw new Error(String(hresult));
|
|
97
|
+
else
|
|
98
|
+
throw new Error(String(hresult));
|
|
99
|
+
}
|
|
100
|
+
let results = result[0].getValue().getReferent().getArrayInstance();
|
|
101
|
+
let errorCodes = result[1].getValue().getReferent().getArrayInstance();
|
|
102
|
+
|
|
103
|
+
let res = [];
|
|
104
|
+
let failed = [];
|
|
105
|
+
for (let i = 0; i < handles.length; i++) {
|
|
106
|
+
let resObj = {
|
|
107
|
+
errorCode: errorCodes[i].getValue(),
|
|
108
|
+
clientHandle: results[i].getValue().getMember(0).getValue(),
|
|
109
|
+
timestamp: filetime.fromStruct(results[i].getValue().getMember(1).getValue()).getDate(),
|
|
110
|
+
quality: results[i].getValue().getMember(2).getValue(),
|
|
111
|
+
reserved: results[i].getValue().getMember(3).getValue(),
|
|
112
|
+
value: this.extractValue(results[i])
|
|
113
|
+
};
|
|
114
|
+
res.push(resObj);
|
|
115
|
+
if (errorCodes[i].getValue() != 0) {
|
|
116
|
+
failed.push([resObj.clientHandle, resObj.errorCode]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
debug("Successfully read " + (res.length - failed.length) + " items.");
|
|
121
|
+
if (failed.length > 0) {
|
|
122
|
+
debug("The following items were not added: ");
|
|
123
|
+
for (let i = 0; i < failed.length; i++) {
|
|
124
|
+
debug("Item: " + failed[i][0] + " ErrorCode: " + failed[i][1]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
extractValue(item){
|
|
131
|
+
// first we decapsulate from the Struct
|
|
132
|
+
item = (!item.getValue().getMember(4).getValue().member.getValue().referent.isArray) ?
|
|
133
|
+
item.getValue().getMember(4).getValue().member.getValue().referent.obj.getValue() :
|
|
134
|
+
item.getValue().getMember(4).getValue().member.getValue().referent.getArray(0).memberArray;
|
|
135
|
+
|
|
136
|
+
if (item == null) return item;
|
|
137
|
+
|
|
138
|
+
if (item instanceof dcom.ComString ||
|
|
139
|
+
item .constructor.name == "ComString") {
|
|
140
|
+
if (item.member) {
|
|
141
|
+
item = item.member._obj.referent._obj;
|
|
142
|
+
}
|
|
143
|
+
// in case is a common string
|
|
144
|
+
if (item instanceof dcom.Pointer ||
|
|
145
|
+
item.constructor.type == "Pointer") {
|
|
146
|
+
item = item.referent._obj;
|
|
147
|
+
}
|
|
148
|
+
} else if (item instanceof dcom.ComValue ||
|
|
149
|
+
item.constructor.name == "ComValue") {
|
|
150
|
+
item = item._obj;
|
|
151
|
+
} else if (item instanceof Array) {
|
|
152
|
+
for (let i = 0; i < item.length; i++) {
|
|
153
|
+
if (item[i]._type == 23){
|
|
154
|
+
item[i] = item[i]._obj.member._obj.referent._obj;
|
|
155
|
+
} else {
|
|
156
|
+
item[i] = item[i]._obj;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return item;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
*
|
|
165
|
+
* @param {object[]} writes
|
|
166
|
+
* @param {number} writes[].handle
|
|
167
|
+
* @param {number} writes[].type
|
|
168
|
+
* @param {*} writes[].value
|
|
169
|
+
* @returns {Promise<number[]>} error codes
|
|
170
|
+
* @opNum 1
|
|
171
|
+
*/
|
|
172
|
+
async write(writes) {
|
|
173
|
+
if (!this._comObj) throw new Error("Not initialized");
|
|
174
|
+
|
|
175
|
+
if (!(writes.length > 0)) return [];
|
|
176
|
+
|
|
177
|
+
let handles = [];
|
|
178
|
+
let values = [];
|
|
179
|
+
for (const write of writes) {
|
|
180
|
+
var valVariant = new Variant.Variant(new ComValue(write.value, write.type));
|
|
181
|
+
|
|
182
|
+
if(Array.isArray(write.value) && write.type == Types.BOOLEAN) {
|
|
183
|
+
valVariant.setFlag(Flags.FLAG_REPRESENTATION_VARIANT_BOOL);
|
|
184
|
+
}
|
|
185
|
+
handles.push(new ComValue(write.handle, Types.INTEGER));
|
|
186
|
+
values.push(new ComValue(valVariant, Types.VARIANT));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let callObject = new CallBuilder(true);
|
|
190
|
+
callObject.setOpnum(1);
|
|
191
|
+
|
|
192
|
+
callObject.addInParamAsInt(writes.length, Flags.FLAG_NULL);
|
|
193
|
+
callObject.addInParamAsArray(new ComArray(new ComValue(handles, Types.INTEGER), true), Flags.FLAG_NULL);
|
|
194
|
+
callObject.addInParamAsArray(new ComArray(new ComValue(values, Types.VARIANT), true), Flags.FLAG_NULL);
|
|
195
|
+
let errCodesArray = new ComArray(new ComValue(null, Types.INTEGER), null, 1, true)
|
|
196
|
+
callObject.addOutParamAsObject(new ComValue(new Pointer(new ComValue(errCodesArray, Types.COMARRAY)), Types.POINTER), Flags.FLAG_NULL);
|
|
197
|
+
|
|
198
|
+
let resultObj = await this._comObj.call(callObject);
|
|
199
|
+
|
|
200
|
+
let hresult = resultObj.hresult;
|
|
201
|
+
let result = resultObj.getResults();
|
|
202
|
+
if (hresult != 0) {
|
|
203
|
+
if (result.lenght == 0)
|
|
204
|
+
throw new Error(String(hresult));
|
|
205
|
+
else
|
|
206
|
+
debug(new Error(String(hresult)));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return result[0].getValue().getReferent().getArrayInstance();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = OPCSyncIO;
|