@amigo9090/ih-libiec61850-node 1.0.47 → 1.0.49
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/builds/linux_arm/addon_iec61850.node +0 -0
- package/builds/linux_arm64/addon_iec61850.node +0 -0
- package/builds/linux_x64/addon_iec61850.node +0 -0
- package/builds/macos_arm64/addon_iec61850.node +0 -0
- package/builds/macos_x64/addon_iec61850.node +0 -0
- package/builds/windows_x64/addon_iec61850.node +0 -0
- package/examples/index_iec61850_client2 copy.js +742 -0
- package/examples/index_iec61850_client2.js +3 -3
- package/package.json +4 -4
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
const { MmsClient } = require('../build/Release/addon_iec61850');
|
|
2
|
+
const util = require('util');
|
|
3
|
+
|
|
4
|
+
const client = new MmsClient((event, data) => {
|
|
5
|
+
console.log(`Event: ${event}, Data: ${util.inspect(data, { depth: null })}`);
|
|
6
|
+
|
|
7
|
+
/*if (event === 'conn' && data.event === 'opened') {
|
|
8
|
+
console.log('Connection opened, browsing data model...');
|
|
9
|
+
//handleConnectionOpened();
|
|
10
|
+
handleConnectionOpened2();
|
|
11
|
+
}*/
|
|
12
|
+
|
|
13
|
+
if (event === 'conn' && data.event === 'opened') {
|
|
14
|
+
console.log('Connection opened');
|
|
15
|
+
// Теперь пользователь сам решит, как исследовать модель
|
|
16
|
+
console.log('\n=== Примеры использования: ===');
|
|
17
|
+
console.log('1. Получить корневые узлы:');
|
|
18
|
+
console.log(' client.browseDataModel()');
|
|
19
|
+
console.log('\n2. Получить DataObjects конкретного узла:');
|
|
20
|
+
console.log(' client.browseDataModel("WAGO61850ServerDevice/LLN0")');
|
|
21
|
+
console.log('\n3. Получить атрибуты DataObject:');
|
|
22
|
+
console.log(' client.browseDataModel("WAGO61850ServerDevice/XCBR1.Pos")');
|
|
23
|
+
console.log('\n4. Получить члены DataSet:');
|
|
24
|
+
console.log(' client.browseDataModel("WAGO61850ServerDevice/LLN0.DataSet01")');
|
|
25
|
+
console.log('\n5. Получить информацию об отчете:');
|
|
26
|
+
console.log(' client.browseDataModel("WAGO61850ServerDevice/LLN0.RP$ReportBlock0101")');
|
|
27
|
+
|
|
28
|
+
// Автоматически получаем корневые узлы для примера
|
|
29
|
+
exploreModel();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if (event === 'data' && data.type === 'data') {
|
|
34
|
+
if (data.event === 'logicalDevices') {
|
|
35
|
+
console.log(`Logical Devices received: ${util.inspect(data.logicalDevices, { depth: null })}`);
|
|
36
|
+
} else if (data.event === 'dataSetDirectory') {
|
|
37
|
+
console.log(`DataSet Directory for ${data.logicalNodeRef}: ${util.inspect(data.dataSets, { depth: null })}`);
|
|
38
|
+
} else if (data.event === 'dataModel') {
|
|
39
|
+
console.log(`Data Model received: ${util.inspect(data.dataModel, { depth: null })}`);
|
|
40
|
+
} else if (data.event === 'dataSet') {
|
|
41
|
+
console.log(`DataSet received for ${data.datasetRef}: ${util.inspect(data.value, { depth: null })}`);
|
|
42
|
+
} else if (data.event === 'multipleDataSets') {
|
|
43
|
+
console.log(`Multiple DataSets received for ${util.inspect(data.datasetRefs, { depth: null })}:`);
|
|
44
|
+
data.values.forEach((value, index) => {
|
|
45
|
+
if (value.isValid !== false) {
|
|
46
|
+
console.log(` DataSet[${index}]: ${data.datasetRefs[index]}, Value: ${util.inspect(value, { depth: null })}`);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(` DataSet[${index}]: ${data.datasetRefs[index]}, Error: ${value.errorReason}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} else if (data.event === 'report') {
|
|
52
|
+
console.log(`Report received for ${data.rcbRef} (rptId: ${data.rptId}):`);
|
|
53
|
+
if (data.timestamp) {
|
|
54
|
+
console.log(` Timestamp: ${data.timestamp}`);
|
|
55
|
+
}
|
|
56
|
+
Object.entries(data.values).forEach(([ref, value], index) => {
|
|
57
|
+
const reason = data.reasons[ref];
|
|
58
|
+
if (reason && reason !== 0) {
|
|
59
|
+
console.log(` ${ref}: ${util.inspect(value, { depth: null })}, Reason: ${reason}`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
} else if (data.event === 'batchData') {
|
|
64
|
+
console.log(`Batch Data received for ${util.inspect(data.dataRefs, { depth: null })}:`);
|
|
65
|
+
data.values.forEach((result, index) => {
|
|
66
|
+
if (result.isValid) {
|
|
67
|
+
console.log(` dataRef[${index}]: ${data.dataRefs[index]}, Value: ${util.inspect(result.value, { depth: null })}`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` dataRef[${index}]: ${data.dataRefs[index]}, Error: ${result.errorReason}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
console.log(`Data received for ${data.dataRef || 'undefined'}: ${util.inspect(data.value, { depth: null })}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (event === 'data' && data.type === 'error') {
|
|
78
|
+
console.error(`Error received: ${data.reason}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (event === 'conn' && data.event === 'reconnecting') {
|
|
82
|
+
console.error(`Reconnection failed: ${data.reason}`);
|
|
83
|
+
if (data.reason.includes('attempt 3')) {
|
|
84
|
+
throw new Error('Max reconnection attempts reached');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (event === 'data' && data.type === 'control') {
|
|
89
|
+
if (data.event === 'reportingEnabled') {
|
|
90
|
+
console.log(`Reporting enabled for ${data.rcbRef}`);
|
|
91
|
+
} else if (data.event === 'reportingDisabled') {
|
|
92
|
+
console.log(`Reporting disabled for ${data.rcbRef}`);
|
|
93
|
+
} else if (data.event === 'dataSetCreated') {
|
|
94
|
+
console.log(`DataSet created: ${data.datasetRef}`);
|
|
95
|
+
} else if (data.event === 'dataSetDeleted') {
|
|
96
|
+
console.log(`DataSet deleted: ${data.datasetRef}`);
|
|
97
|
+
} else if (data.event === 'stateChanged') {
|
|
98
|
+
console.log(`Connection state changed: ${data.state}, isConnected: ${data.isConnected}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (event === 'conn' && data.event === 'stateChanged') {
|
|
103
|
+
console.log(`Connection state changed: ${data.state}, isConnected: ${data.isConnected}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
108
|
+
|
|
109
|
+
/*async function handleConnectionOpened() {
|
|
110
|
+
try {
|
|
111
|
+
const dataModel = await client.browseDataModel();
|
|
112
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
113
|
+
|
|
114
|
+
const dataSets = [];
|
|
115
|
+
dataModel.forEach(ld => {
|
|
116
|
+
ld.logicalNodes.forEach(ln => {
|
|
117
|
+
ln.dataSets.forEach(ds => {
|
|
118
|
+
console.log(`Found dataset: ${ds.reference}`);
|
|
119
|
+
dataSets.push(ds);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log('\nЧтение значений DataSet...');
|
|
125
|
+
const datasetRefs = dataSets.map(ds => ds.reference);
|
|
126
|
+
console.log('Calling readDataSetValues...');
|
|
127
|
+
const readResults = await client.readDataSetValues(datasetRefs);
|
|
128
|
+
console.log('readDataSetValues returned');
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
readResults.forEach((res, idx) => {
|
|
132
|
+
const ds = dataSets[idx];
|
|
133
|
+
console.log(`\nDataSet: ${res.datasetRef}`);
|
|
134
|
+
if (!res.isValid) {
|
|
135
|
+
console.error(' Ошибка:', res.errorReason);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(` Значений: ${res.count}, Удаляемые: ${res.isDeletable}`);
|
|
140
|
+
|
|
141
|
+
// Функция для рекурсивного вывода вложенных структур
|
|
142
|
+
/*const printValue = (value, indent = ' ') => {
|
|
143
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
144
|
+
Object.entries(value).forEach(([key, val]) => {
|
|
145
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
146
|
+
console.log(`${indent}${key}: {`);
|
|
147
|
+
printValue(val, indent + ' ');
|
|
148
|
+
console.log(`${indent}}`);
|
|
149
|
+
} else if (Array.isArray(val)) {
|
|
150
|
+
console.log(`${indent}${key}: [`);
|
|
151
|
+
val.forEach((item, i) => {
|
|
152
|
+
console.log(`${indent} [${i}]:`);
|
|
153
|
+
printValue(item, indent + ' ');
|
|
154
|
+
});
|
|
155
|
+
console.log(`${indent}]`);
|
|
156
|
+
} else {
|
|
157
|
+
console.log(`${indent}${key}: ${util.inspect(val, { colors: true })}`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`${indent}${util.inspect(value, { colors: true })}`);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
Object.entries(res.values).forEach(([ref, value]) => {
|
|
166
|
+
console.log(` ${ref}:`);
|
|
167
|
+
printValue(value, ' ');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
// Функция для рекурсивного вывода с полным отображением всех элементов
|
|
172
|
+
const printModel = (model, indent = '') => {
|
|
173
|
+
if (Array.isArray(model)) {
|
|
174
|
+
model.forEach((item, index) => {
|
|
175
|
+
console.log(`${indent}[${index}]:`);
|
|
176
|
+
printModel(item, indent + ' ');
|
|
177
|
+
});
|
|
178
|
+
} else if (model && typeof model === 'object') {
|
|
179
|
+
Object.entries(model).forEach(([key, value]) => {
|
|
180
|
+
if (key === 'dataObjects' && Array.isArray(value)) {
|
|
181
|
+
console.log(`${indent}${key}: [`);
|
|
182
|
+
value.forEach((doObj, idx) => {
|
|
183
|
+
console.log(`${indent} [${idx}]:`);
|
|
184
|
+
printModel(doObj, indent + ' ');
|
|
185
|
+
});
|
|
186
|
+
console.log(`${indent}]`);
|
|
187
|
+
} else if (key === 'attributes' && typeof value === 'object') {
|
|
188
|
+
console.log(`${indent}${key}: {`);
|
|
189
|
+
printModel(value, indent + ' ');
|
|
190
|
+
console.log(`${indent}}`);
|
|
191
|
+
} else if (Array.isArray(value)) {
|
|
192
|
+
console.log(`${indent}${key}: [`);
|
|
193
|
+
value.forEach((item, idx) => {
|
|
194
|
+
console.log(`${indent} [${idx}]: ${item}`);
|
|
195
|
+
});
|
|
196
|
+
console.log(`${indent}]`);
|
|
197
|
+
} else if (typeof value === 'object') {
|
|
198
|
+
console.log(`${indent}${key}: {`);
|
|
199
|
+
printModel(value, indent + ' ');
|
|
200
|
+
console.log(`${indent}}`);
|
|
201
|
+
} else {
|
|
202
|
+
console.log(`${indent}${key}: ${value}`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
console.log(`${indent}${model}`);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log('Reading data...');
|
|
213
|
+
const dataRefs = [
|
|
214
|
+
'WAGO61850ServerDevice/XCBR1.Pos[ST]',
|
|
215
|
+
'WAGO61850ServerDevice/GGIO1.Ind1.stVal',
|
|
216
|
+
'WAGO61850ServerDevice/CALH12.GrAlm.stVal'
|
|
217
|
+
];
|
|
218
|
+
const readRefResult = await client.readData(dataRefs);
|
|
219
|
+
console.log("readRefResult " + util.inspect(readRefResult, { depth: null }));
|
|
220
|
+
|
|
221
|
+
const rcbRef2 = 'WAGO61850ServerDevice/LLN0.RP.ReportBlock0201';
|
|
222
|
+
const dataSetRef2 = 'WAGO61850ServerDevice/LLN0.DataSet02';
|
|
223
|
+
console.log(`Enabling reporting for ${rcbRef2} with dataset ${dataSetRef2}`);
|
|
224
|
+
await client.enableReporting(rcbRef2, dataSetRef2);
|
|
225
|
+
|
|
226
|
+
/*console.log('Reading data...');
|
|
227
|
+
const dataRefs = [
|
|
228
|
+
'A01LD0/Q1_XCBR1.Pos[ST]',
|
|
229
|
+
'A01LD0/In_GGIO1.Ind1',
|
|
230
|
+
'A01LD0/CALH1.GrAlm.stVal'
|
|
231
|
+
];
|
|
232
|
+
const readRefResult = await client.readData(dataRefs);
|
|
233
|
+
console.log("readRefResult " + util.inspect(readRefResult, { depth: null }));*/
|
|
234
|
+
|
|
235
|
+
/* const rcbRef = 'A01LD0/LLN0.RP.repTI1';
|
|
236
|
+
const dataSetRef = 'A01LD0/LLN0.TI_ASU';
|
|
237
|
+
console.log(`Enabling reporting for ${rcbRef} with dataset ${dataSetRef}`);
|
|
238
|
+
await client.enableReporting(rcbRef, dataSetRef);*/
|
|
239
|
+
|
|
240
|
+
/*const rcbRef2 = 'A01LD0/LLN0.BR.repTS1';
|
|
241
|
+
const dataSetRef2 = 'A01LD0/LLN0.TS_ASU';
|
|
242
|
+
console.log(`Enabling reporting for ${rcbRef2} with dataset ${dataSetRef2}`);
|
|
243
|
+
await client.enableReporting(rcbRef2, dataSetRef2);
|
|
244
|
+
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error('Error in handleConnectionOpened:', err.message);
|
|
247
|
+
}
|
|
248
|
+
}*/
|
|
249
|
+
|
|
250
|
+
async function handleConnectionOpened() {
|
|
251
|
+
try {
|
|
252
|
+
const dataModel = await client.browseDataModel();
|
|
253
|
+
|
|
254
|
+
const dataSets = [];
|
|
255
|
+
const reports = [];
|
|
256
|
+
|
|
257
|
+
dataModel.forEach(ld => {
|
|
258
|
+
console.log(`\nLogical Device: ${ld.name}`);
|
|
259
|
+
|
|
260
|
+
ld.logicalNodes.forEach(ln => {
|
|
261
|
+
console.log(` Logical Node: ${ln.name} (${ln.reference})`);
|
|
262
|
+
|
|
263
|
+
// Вывод DataSets
|
|
264
|
+
if (ln.dataSets && ln.dataSets.length > 0) {
|
|
265
|
+
console.log(` Datasets (${ln.dataSets.length}):`);
|
|
266
|
+
ln.dataSets.forEach(ds => {
|
|
267
|
+
console.log(` - ${ds.reference} (Deletable: ${ds.isDeletable})`);
|
|
268
|
+
dataSets.push(ds);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Вывод отчетов (Report Control Blocks)
|
|
273
|
+
if (ln.reports && ln.reports.length > 0) {
|
|
274
|
+
console.log(` Reports (${ln.reports.length}):`);
|
|
275
|
+
ln.reports.forEach((report, index) => {
|
|
276
|
+
console.log(` [${index + 1}] ${report.reference}`);
|
|
277
|
+
console.log(` Type: ${report.type} (${report.description || 'N/A'})`);
|
|
278
|
+
if (report.datasetRef) {
|
|
279
|
+
console.log(` Dataset: ${report.datasetRef}`);
|
|
280
|
+
}
|
|
281
|
+
if (report.reportId) {
|
|
282
|
+
console.log(` Report ID: ${report.reportId}`);
|
|
283
|
+
}
|
|
284
|
+
console.log(` Enabled: ${report.enabled !== undefined ? report.enabled : 'Unknown'}`);
|
|
285
|
+
reports.push(report);
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
console.log(` No reports found`);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
console.log('\n=== SUMMARY ===');
|
|
294
|
+
console.log(`Total Logical Devices: ${dataModel.length}`);
|
|
295
|
+
|
|
296
|
+
let totalDataSets = 0;
|
|
297
|
+
let totalReports = 0;
|
|
298
|
+
|
|
299
|
+
dataModel.forEach(ld => {
|
|
300
|
+
ld.logicalNodes.forEach(ln => {
|
|
301
|
+
totalDataSets += (ln.dataSets ? ln.dataSets.length : 0);
|
|
302
|
+
totalReports += (ln.reports ? ln.reports.length : 0);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
console.log(`Total Datasets found: ${totalDataSets}`);
|
|
307
|
+
console.log(`Total Reports found: ${totalReports}`);
|
|
308
|
+
|
|
309
|
+
// Выводим все найденные отчеты для удобства
|
|
310
|
+
console.log('\n=== ALL FOUND REPORTS ===');
|
|
311
|
+
reports.forEach((report, index) => {
|
|
312
|
+
const enabledStatus = report.enabled !== undefined ?
|
|
313
|
+
(report.enabled ? 'ENABLED' : 'DISABLED') : 'UNKNOWN';
|
|
314
|
+
console.log(`${index + 1}. ${report.reference} (${report.type}) - ${enabledStatus}`);
|
|
315
|
+
if (report.datasetRef) {
|
|
316
|
+
console.log(` Dataset: ${report.datasetRef}`);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
console.log('\n=== RECOMMENDED REPORTS FOR ENABLING ===');
|
|
321
|
+
// Ищем отчеты с DataSet02 (как в примере)
|
|
322
|
+
const recommendedReports = reports.filter(r =>
|
|
323
|
+
r.datasetRef && r.datasetRef.includes('DataSet01')
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (recommendedReports.length > 0) {
|
|
327
|
+
recommendedReports.forEach((report, index) => {
|
|
328
|
+
console.log(`${index + 1}. ${report.reference}`);
|
|
329
|
+
console.log(` Dataset: ${report.datasetRef}`);
|
|
330
|
+
console.log(` To enable: client.enableReporting("${report.reference}", "${report.datasetRef}")`);
|
|
331
|
+
});
|
|
332
|
+
} else {
|
|
333
|
+
console.log('No reports with DataSet01 found.');
|
|
334
|
+
// Предлагаем другие отчеты
|
|
335
|
+
if (reports.length > 0) {
|
|
336
|
+
console.log('\nAvailable reports with datasets:');
|
|
337
|
+
reports.filter(r => r.datasetRef).forEach((report, index) => {
|
|
338
|
+
console.log(`${index + 1}. ${report.reference} -> ${report.datasetRef}`);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
console.error('Error in handleConnectionOpened:', err.message);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function handleConnectionOpened2() {
|
|
348
|
+
try {
|
|
349
|
+
const dataModel = await client.browseDataModel();
|
|
350
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
351
|
+
|
|
352
|
+
const dataSets = [];
|
|
353
|
+
const reports = [];
|
|
354
|
+
|
|
355
|
+
dataModel.forEach(ld => {
|
|
356
|
+
console.log(`\nLogical Device: ${ld.name}`);
|
|
357
|
+
|
|
358
|
+
ld.logicalNodes.forEach(ln => {
|
|
359
|
+
console.log(` Logical Node: ${ln.name}`);
|
|
360
|
+
|
|
361
|
+
// Вывод DataSets
|
|
362
|
+
if (ln.dataSets && ln.dataSets.length > 0) {
|
|
363
|
+
ln.dataSets.forEach(ds => {
|
|
364
|
+
console.log(` Dataset: ${ds.reference} (Deletable: ${ds.isDeletable})`);
|
|
365
|
+
dataSets.push(ds);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Вывод отчетов (RCB - Report Control Blocks)
|
|
370
|
+
if (ln.reports && ln.reports.length > 0) {
|
|
371
|
+
console.log(` Reports (${ln.reports.length}):`);
|
|
372
|
+
ln.reports.forEach((report, index) => {
|
|
373
|
+
console.log(` [${index + 1}] ${report.reference}`);
|
|
374
|
+
console.log(` Type: ${report.type} (${report.description})`);
|
|
375
|
+
if (report.datasetRef) {
|
|
376
|
+
console.log(` Dataset: ${report.datasetRef}`);
|
|
377
|
+
}
|
|
378
|
+
if (report.reportId) {
|
|
379
|
+
console.log(` Report ID: ${report.reportId}`);
|
|
380
|
+
}
|
|
381
|
+
console.log(` Enabled: ${report.enabled}`);
|
|
382
|
+
reports.push(report);
|
|
383
|
+
});
|
|
384
|
+
} else {
|
|
385
|
+
console.log(` No reports found for ${ln.reference}`);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
console.log('\n=== SUMMARY ===');
|
|
391
|
+
console.log(`Total Logical Devices: ${dataModel.length}`);
|
|
392
|
+
console.log(`Total Datasets found: ${dataSets.length}`);
|
|
393
|
+
console.log(`Total Reports found: ${reports.length}`);
|
|
394
|
+
|
|
395
|
+
// Выводим все найденные отчеты для удобства
|
|
396
|
+
console.log('\n=== ALL FOUND REPORTS ===');
|
|
397
|
+
reports.forEach((report, index) => {
|
|
398
|
+
console.log(`${index + 1}. ${report.reference} (${report.type})`);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// 1. Читаем модель устройства
|
|
402
|
+
/*const dataModel = await client.browseDataModel();
|
|
403
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
404
|
+
|
|
405
|
+
const dataSets = [];
|
|
406
|
+
dataModel.forEach(ld => {
|
|
407
|
+
ld.logicalNodes.forEach(ln => {
|
|
408
|
+
ln.dataSets.forEach(ds => {
|
|
409
|
+
console.log(`Found dataset: ${ds.reference}`);
|
|
410
|
+
dataSets.push(ds);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});*/
|
|
414
|
+
|
|
415
|
+
// 2. Читаем одиночные значения
|
|
416
|
+
console.log('Reading single values...');
|
|
417
|
+
const dataRefs = [
|
|
418
|
+
'WAGO61850ServerDevice/XCBR1.Pos[ST]',
|
|
419
|
+
'WAGO61850ServerDevice/GGIO1.Ind1[ST]',
|
|
420
|
+
'WAGO61850ServerDevice/CALH12.GrAlm.stVal'
|
|
421
|
+
];
|
|
422
|
+
const readRefResult = await client.readData(dataRefs);
|
|
423
|
+
console.log("readRefResult " + util.inspect(readRefResult, { depth: null }));
|
|
424
|
+
|
|
425
|
+
// 3. Читаем и кэшируем структуры (делаем это один раз)
|
|
426
|
+
console.log('First read (with caching)...');
|
|
427
|
+
const firstRead = await client.readDataSetModel([
|
|
428
|
+
'WAGO61850ServerDevice/LLN0.DataSet01',
|
|
429
|
+
'WAGO61850ServerDevice/LLN0.DataSet02',
|
|
430
|
+
'WAGO61850ServerDevice/LLN0.DataSet03'
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
console.log('First read completed, structures cached');
|
|
434
|
+
|
|
435
|
+
// 4. Теперь можем использовать быстрый polling значений Dataset
|
|
436
|
+
console.log('\nStarting fast polling...');
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < 10; i++) {
|
|
439
|
+
console.log(`\n--- Poll ${i+1} ---`);
|
|
440
|
+
|
|
441
|
+
const startTime = Date.now();
|
|
442
|
+
|
|
443
|
+
const pollResults = await client.pollDataSetValues(['WAGO61850ServerDevice/LLN0.DataSet01']); // Быстрое чтение значений DataSet
|
|
444
|
+
|
|
445
|
+
const endTime = Date.now();
|
|
446
|
+
|
|
447
|
+
pollResults.forEach((result, idx) => {
|
|
448
|
+
if (result.isValid) {
|
|
449
|
+
console.log(`DataSet ${result.datasetRef}: ${result.count} values`);
|
|
450
|
+
console.log(` Read time: ${result.readTimeMicros} µs`);
|
|
451
|
+
console.log(` Process time: ${result.processTimeMicros} µs`);
|
|
452
|
+
|
|
453
|
+
// Выводим значения
|
|
454
|
+
Object.entries(result.values).forEach(([ref, value]) => {
|
|
455
|
+
console.log(` ${ref}:`, util.inspect(value, { depth: null }));
|
|
456
|
+
});
|
|
457
|
+
} else {
|
|
458
|
+
console.error(`Error: ${result.errorReason}`);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
console.log(`Total poll time: ${endTime - startTime} ms`);
|
|
463
|
+
|
|
464
|
+
// Ждем перед следующим опросом
|
|
465
|
+
await sleep(1000);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
} catch (err) {
|
|
469
|
+
console.error('Error in handleConnectionOpened2:', err.message);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/*async function exploreModel() {
|
|
474
|
+
try {
|
|
475
|
+
console.log('\n=== 1. Получение корневых узлов ===');
|
|
476
|
+
const rootNodes = await client.browseDataModel();
|
|
477
|
+
|
|
478
|
+
console.log('\nНайдено Logical Nodes:');
|
|
479
|
+
rootNodes.forEach((ln, index) => {
|
|
480
|
+
console.log(`\n${index + 1}. ${ln.name} (${ln.reference})`);
|
|
481
|
+
|
|
482
|
+
// Выводим ВСЕ датасеты
|
|
483
|
+
if (ln.dataSets && ln.dataSets.length > 0) {
|
|
484
|
+
console.log(` Datasets (${ln.dataSets.length}):`);
|
|
485
|
+
ln.dataSets.forEach((ds, idx) => {
|
|
486
|
+
console.log(` ${idx + 1}. ${ds.name}: ${ds.reference}`);
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
console.log(` Datasets: 0`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Выводим ВСЕ отчеты
|
|
493
|
+
if (ln.reports && ln.reports.length > 0) {
|
|
494
|
+
console.log(` Reports (${ln.reports.length}):`);
|
|
495
|
+
ln.reports.forEach((report, idx) => {
|
|
496
|
+
const typeDesc = report.type === 'RP' ? 'Unbuffered' : 'Buffered';
|
|
497
|
+
console.log(` ${idx + 1}. ${report.name} (${report.type} - ${typeDesc}): ${report.reference}`);
|
|
498
|
+
});
|
|
499
|
+
} else {
|
|
500
|
+
console.log(` Reports: 0`);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Выбираем LLN0 для дальнейшего исследования
|
|
505
|
+
const lln0 = rootNodes.find(ln => ln.name === 'LLN0');
|
|
506
|
+
if (lln0) {
|
|
507
|
+
console.log('\n=== 2. Исследуем LLN0 ===');
|
|
508
|
+
const lln0Details = await client.browseDataModel(lln0.reference);
|
|
509
|
+
|
|
510
|
+
console.log(`\nDataObjects в ${lln0Details.reference}: ${llln0Details.dataObjectsCount}`);
|
|
511
|
+
console.log(`DataSets в ${lln0Details.reference}: ${llln0Details.dataSetsCount}`);
|
|
512
|
+
|
|
513
|
+
// Показываем ВСЕ DataObjects
|
|
514
|
+
console.log('\nВсе DataObjects:');
|
|
515
|
+
lln0Details.dataObjects.forEach((doObj, index) => {
|
|
516
|
+
console.log(`${index + 1}. ${doObj.name} (${doObj.cdc || 'Unknown'}) - ${doObj.reference}`);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Выбираем первый DataSet для кэширования
|
|
520
|
+
if (lln0Details.dataSets.length > 0) {
|
|
521
|
+
const firstDataSet = lln0Details.dataSets[0];
|
|
522
|
+
console.log(`\n=== 3. Кэшируем DataSet ${firstDataSet.reference} ===`);
|
|
523
|
+
const dsDetails = await client.browseDataModel(firstDataSet.reference);
|
|
524
|
+
|
|
525
|
+
console.log(`DataSet ${dsDetails.reference}:`);
|
|
526
|
+
console.log(` Удаляемый: ${dsDetails.isDeletable}`);
|
|
527
|
+
console.log(` Членов: ${dsDetails.memberCount}`);
|
|
528
|
+
console.log('\n Первые члены:');
|
|
529
|
+
dsDetails.members.slice(0, 5).forEach((member, index) => {
|
|
530
|
+
console.log(` ${index + 1}. ${member.reference}`);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Теперь можем быстро читать этот DataSet
|
|
534
|
+
console.log('\n=== 4. Быстрое чтение DataSet ===');
|
|
535
|
+
const pollResults = await client.pollDataSetValues([firstDataSet.reference]);
|
|
536
|
+
console.log('Poll results:', util.inspect(pollResults, { depth: 2 }));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Выбираем первый отчет для кэширования
|
|
540
|
+
if (lln0.reports.length > 0) {
|
|
541
|
+
const firstReport = lln0.reports.find(r => r.reference.includes('ReportBlock0101'));
|
|
542
|
+
if (firstReport) {
|
|
543
|
+
console.log(`\n=== 5. Кэшируем отчет ${firstReport.reference} ===`);
|
|
544
|
+
const reportDetails = await client.browseDataModel(firstReport.reference);
|
|
545
|
+
|
|
546
|
+
console.log(`Отчет ${reportDetails.reference}:`);
|
|
547
|
+
console.log(` Тип: ${reportDetails.reportType}`);
|
|
548
|
+
console.log(` DataSet: ${reportDetails.datasetRef}`);
|
|
549
|
+
console.log(` Включен: ${reportDetails.enabled}`);
|
|
550
|
+
console.log(` Report ID: ${reportDetails.reportId}`);
|
|
551
|
+
|
|
552
|
+
// Подписываемся на отчет
|
|
553
|
+
console.log(`\n=== 6. Подписываемся на отчет ===`);
|
|
554
|
+
await client.enableReporting(firstReport.reference, reportDetails.datasetRef);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Пример чтения одиночного значения
|
|
560
|
+
console.log('\n=== 7. Чтение одиночных значений ===');
|
|
561
|
+
const singleValues = await client.readData([
|
|
562
|
+
'WAGO61850ServerDevice/XCBR1.Pos[ST]',
|
|
563
|
+
'WAGO61850ServerDevice/GGIO1.Ind1[ST]'
|
|
564
|
+
]);
|
|
565
|
+
console.log('Single values:', util.inspect(singleValues, { depth: 2 }));
|
|
566
|
+
|
|
567
|
+
} catch (err) {
|
|
568
|
+
console.error('Error in exploreModel:', err.message);
|
|
569
|
+
}
|
|
570
|
+
}*/
|
|
571
|
+
|
|
572
|
+
async function exploreModel() {
|
|
573
|
+
try {
|
|
574
|
+
console.log('\n=== 1. Получение корневых узлов ===');
|
|
575
|
+
const rootNodes = await client.browseDataModel();
|
|
576
|
+
|
|
577
|
+
console.log('\nНайдено Logical Nodes:');
|
|
578
|
+
rootNodes.forEach((ln, index) => {
|
|
579
|
+
console.log(`\n${index + 1}. ${ln.name} (${ln.reference})`);
|
|
580
|
+
|
|
581
|
+
// Выводим ВСЕ датасеты
|
|
582
|
+
if (ln.dataSets && ln.dataSets.length > 0) {
|
|
583
|
+
console.log(` Datasets (${ln.dataSets.length}):`);
|
|
584
|
+
ln.dataSets.forEach((ds, idx) => {
|
|
585
|
+
console.log(` ${idx + 1}. ${ds.name}: ${ds.reference}`);
|
|
586
|
+
});
|
|
587
|
+
} else {
|
|
588
|
+
console.log(` Datasets: 0`);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Выводим ВСЕ отчеты
|
|
592
|
+
if (ln.reports && ln.reports.length > 0) {
|
|
593
|
+
console.log(` Reports (${ln.reports.length}):`);
|
|
594
|
+
ln.reports.forEach((report, idx) => {
|
|
595
|
+
const typeDesc = report.type === 'RP' ? 'Unbuffered' : 'Buffered';
|
|
596
|
+
console.log(` ${idx + 1}. ${report.name} (${report.type} - ${typeDesc}): ${report.reference}`);
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
console.log(` Reports: 0`);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// Выбираем LLN0 для дальнейшего исследования
|
|
604
|
+
const lln0 = rootNodes.find(ln => ln.name === 'LLN0');
|
|
605
|
+
if (lln0) {
|
|
606
|
+
console.log('\n=== 2. Исследуем LLN0 ===');
|
|
607
|
+
const lln0Details = await client.browseDataModel(lln0.reference);
|
|
608
|
+
|
|
609
|
+
console.log(`\nDataObjects в ${lln0Details.reference}: ${lln0Details.dataObjectsCount}`);
|
|
610
|
+
console.log(`DataSets в ${lln0Details.reference}: ${lln0Details.dataSetsCount}`);
|
|
611
|
+
|
|
612
|
+
// Показываем ВСЕ DataObjects
|
|
613
|
+
console.log('\nВсе DataObjects:');
|
|
614
|
+
lln0Details.dataObjects.forEach((doObj, index) => {
|
|
615
|
+
console.log(`${index + 1}. ${doObj.name} (${doObj.cdc || 'Unknown'}) - ${doObj.reference}`);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Выбираем первый DataSet для кэширования
|
|
619
|
+
if (lln0Details.dataSets.length > 0) {
|
|
620
|
+
const firstDataSet = lln0Details.dataSets[0];
|
|
621
|
+
console.log(`\n=== 3. Кэшируем DataSet ${firstDataSet.reference} ===`);
|
|
622
|
+
const dsDetails = await client.browseDataModel(firstDataSet.reference);
|
|
623
|
+
|
|
624
|
+
console.log(`\nDataSet ${dsDetails.reference}:`);
|
|
625
|
+
console.log(` Удаляемый: ${dsDetails.isDeletable}`);
|
|
626
|
+
console.log(` Членов: ${dsDetails.memberCount}`);
|
|
627
|
+
console.log('\n Все члены:');
|
|
628
|
+
dsDetails.members.forEach((member, index) => {
|
|
629
|
+
console.log(` ${index + 1}. ${member.reference}`);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Теперь можем быстро читать этот DataSet
|
|
633
|
+
console.log('\n=== 4. Быстрое чтение DataSet ===');
|
|
634
|
+
const pollResults = await client.pollDataSetValues([firstDataSet.reference]);
|
|
635
|
+
|
|
636
|
+
console.log('\nPoll results:');
|
|
637
|
+
pollResults.forEach((result, idx) => {
|
|
638
|
+
if (result.isValid) {
|
|
639
|
+
console.log(`\nDataSet ${result.datasetRef}: ${result.count} значений`);
|
|
640
|
+
console.log(` Read time: ${result.readTimeMicros} µs`);
|
|
641
|
+
console.log(` Process time: ${result.processTimeMicros} µs`);
|
|
642
|
+
|
|
643
|
+
// Выводим ВСЕ значения
|
|
644
|
+
console.log('\n Значения:');
|
|
645
|
+
Object.entries(result.values).forEach(([ref, value], index) => {
|
|
646
|
+
console.log(` [${index + 1}] ${ref}:`, util.inspect(value, {
|
|
647
|
+
depth: null,
|
|
648
|
+
colors: true,
|
|
649
|
+
maxArrayLength: 10
|
|
650
|
+
}));
|
|
651
|
+
});
|
|
652
|
+
} else {
|
|
653
|
+
console.error(` Error: ${result.errorReason}`);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Выбираем первый отчет для кэширования
|
|
659
|
+
if (lln0.reports.length > 0) {
|
|
660
|
+
const firstReport = lln0.reports.find(r => r.reference.includes('ReportBlock0101'));
|
|
661
|
+
if (firstReport) {
|
|
662
|
+
console.log(`\n=== 5. Кэшируем отчет ${firstReport.reference} ===`);
|
|
663
|
+
const reportDetails = await client.browseDataModel(firstReport.reference);
|
|
664
|
+
|
|
665
|
+
console.log(`\nОтчет ${reportDetails.reference}:`);
|
|
666
|
+
console.log(` Тип: ${reportDetails.reportType}`);
|
|
667
|
+
console.log(` DataSet: ${reportDetails.datasetRef}`);
|
|
668
|
+
console.log(` Включен: ${reportDetails.enabled}`);
|
|
669
|
+
console.log(` Report ID: ${reportDetails.reportId || 'N/A'}`);
|
|
670
|
+
console.log(` Triggers: ${reportDetails.trgOps}`);
|
|
671
|
+
console.log(` Integrity Period: ${reportDetails.intgPd} ms`);
|
|
672
|
+
console.log(` Buffer Time: ${reportDetails.bufTm} ms`);
|
|
673
|
+
console.log(` GI: ${reportDetails.gi}`);
|
|
674
|
+
|
|
675
|
+
// Подписываемся на отчет
|
|
676
|
+
console.log(`\n=== 6. Подписываемся на отчет ===`);
|
|
677
|
+
await client.enableReporting(firstReport.reference, reportDetails.datasetRef);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Пример чтения одиночного значения
|
|
683
|
+
console.log('\n=== 7. Чтение одиночных значений ===');
|
|
684
|
+
const singleValues = await client.readData([
|
|
685
|
+
'WAGO61850ServerDevice/XCBR1.Pos[ST]',
|
|
686
|
+
'WAGO61850ServerDevice/GGIO1.Ind1[ST]'
|
|
687
|
+
]);
|
|
688
|
+
|
|
689
|
+
console.log('\nSingle values:');
|
|
690
|
+
singleValues.forEach((result, index) => {
|
|
691
|
+
if (result.isValid) {
|
|
692
|
+
console.log(`[${index + 1}] ${result.dataRef}:`, util.inspect(result.value, {
|
|
693
|
+
depth: null,
|
|
694
|
+
colors: true
|
|
695
|
+
}));
|
|
696
|
+
} else {
|
|
697
|
+
console.log(`[${index + 1}] ${result.dataRef}: ERROR - ${result.errorReason}`);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
} catch (err) {
|
|
702
|
+
console.error('Error in exploreModel:', err.message);
|
|
703
|
+
console.error(err.stack);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function main() {
|
|
708
|
+
try {
|
|
709
|
+
console.log('Starting client...');
|
|
710
|
+
await client.connect({
|
|
711
|
+
ip: '192.168.0.106',
|
|
712
|
+
port: 102,
|
|
713
|
+
clientID: 'mms_client1',
|
|
714
|
+
reconnectDelay: 2,
|
|
715
|
+
//heartbeatInterval: 3000 //новый параметр - интервал эхо-запросов для поддержки соединения
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
await sleep(5000);
|
|
719
|
+
|
|
720
|
+
console.log('Waiting for data and reports...');
|
|
721
|
+
await sleep(30000);
|
|
722
|
+
|
|
723
|
+
/*const rcbRef = 'A01LD0/LLN0.BR.repTS1';
|
|
724
|
+
console.log(`Disabling reporting for ${rcbRef}`);
|
|
725
|
+
await client.disableReporting(rcbRef);*/
|
|
726
|
+
|
|
727
|
+
const rcbRef2 = 'WAGO61850ServerDevice/LLN0.RP.ReportBlock0101';
|
|
728
|
+
console.log(`Disabling reporting for ${rcbRef2}`);
|
|
729
|
+
await client.disableReporting(rcbRef2);
|
|
730
|
+
|
|
731
|
+
console.log('Client status:', client.getStatus());
|
|
732
|
+
console.log('Closing client...');
|
|
733
|
+
await client.close();
|
|
734
|
+
console.log('Client closed.');
|
|
735
|
+
|
|
736
|
+
} catch (err) {
|
|
737
|
+
console.error('Main error:', err.message);
|
|
738
|
+
await client.close().catch(e => console.error('Close error:', e.message));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
main().catch(err => console.error('Fatal error:', err.message));
|
|
@@ -320,7 +320,7 @@ async function handleConnectionOpened() {
|
|
|
320
320
|
console.log('\n=== RECOMMENDED REPORTS FOR ENABLING ===');
|
|
321
321
|
// Ищем отчеты с DataSet02 (как в примере)
|
|
322
322
|
const recommendedReports = reports.filter(r =>
|
|
323
|
-
r.datasetRef && r.datasetRef.includes('
|
|
323
|
+
r.datasetRef && r.datasetRef.includes('DataSet01')
|
|
324
324
|
);
|
|
325
325
|
|
|
326
326
|
if (recommendedReports.length > 0) {
|
|
@@ -330,7 +330,7 @@ async function handleConnectionOpened() {
|
|
|
330
330
|
console.log(` To enable: client.enableReporting("${report.reference}", "${report.datasetRef}")`);
|
|
331
331
|
});
|
|
332
332
|
} else {
|
|
333
|
-
console.log('No reports with
|
|
333
|
+
console.log('No reports with DataSet01 found.');
|
|
334
334
|
// Предлагаем другие отчеты
|
|
335
335
|
if (reports.length > 0) {
|
|
336
336
|
console.log('\nAvailable reports with datasets:');
|
|
@@ -440,7 +440,7 @@ async function handleConnectionOpened2() {
|
|
|
440
440
|
|
|
441
441
|
const startTime = Date.now();
|
|
442
442
|
|
|
443
|
-
const pollResults = await client.pollDataSetValues(['WAGO61850ServerDevice/LLN0.
|
|
443
|
+
const pollResults = await client.pollDataSetValues(['WAGO61850ServerDevice/LLN0.DataSet01']); // Быстрое чтение значений DataSet
|
|
444
444
|
|
|
445
445
|
const endTime = Date.now();
|
|
446
446
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amigo9090/ih-libiec61850-node",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.49",
|
|
4
4
|
"description": "Node.js addon for IEC 61850 client (MMS, GOOSE) using libiec61850",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
],
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/intrahouseio/ih-
|
|
34
|
+
"url": "git+https://github.com/intrahouseio/ih-lib61850-node.git"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://github.com/intrahouseio/ih-
|
|
36
|
+
"homepage": "https://github.com/intrahouseio/ih-lib61850-node#readme",
|
|
37
37
|
"directories": {
|
|
38
38
|
"example": "examples",
|
|
39
39
|
"lib": "lib"
|
|
40
40
|
},
|
|
41
41
|
"gypfile": true,
|
|
42
42
|
"bugs": {
|
|
43
|
-
"url": "https://github.com/intrahouseio/ih-
|
|
43
|
+
"url": "https://github.com/intrahouseio/ih-lib61850-node/issues"
|
|
44
44
|
}
|
|
45
45
|
}
|