@amigo9090/ih-libiec61850-node 1.0.0
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_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/examples/index_iec61850_client.js +302 -0
- package/examples/index_iec61850_client2.js +132 -0
- package/examples/test.js +7 -0
- package/index.js +33 -0
- package/package.json +57 -0
- package/readme.md +171 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/*const { MmsClient } = require('../build/Release/addon_iec61850');
|
|
2
|
+
const util = require('util');
|
|
3
|
+
|
|
4
|
+
function readDataSet(client, ref) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const onData = (data) => {
|
|
7
|
+
if (data.type === 'data' && data.event === 'dataSet' && data.dataSetRef === ref) {
|
|
8
|
+
cleanup();
|
|
9
|
+
resolve(data.value);
|
|
10
|
+
} else if (data.type === 'error' && data.dataSetRef === ref) {
|
|
11
|
+
cleanup();
|
|
12
|
+
reject(new Error(data.reason));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const cleanup = () => {
|
|
17
|
+
client.off('data', onData);
|
|
18
|
+
clearTimeout(timeoutId);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const timeoutId = setTimeout(() => {
|
|
22
|
+
cleanup();
|
|
23
|
+
reject(new Error(`Timeout waiting for dataset ${ref}`));
|
|
24
|
+
}, 5000);
|
|
25
|
+
|
|
26
|
+
client.on('data', onData);
|
|
27
|
+
client.readDataSetValues(ref);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = new MmsClient((event, data) => {
|
|
32
|
+
console.log(`Event: ${event}, Data: ${util.inspect(data, { depth: null })}`);
|
|
33
|
+
|
|
34
|
+
if (event === 'conn' && data.event === 'opened') {
|
|
35
|
+
console.log('Connection opened, browsing data model...');
|
|
36
|
+
client.browseDataModel()
|
|
37
|
+
.then(async dataModel => {
|
|
38
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
39
|
+
|
|
40
|
+
// Extract dataset references
|
|
41
|
+
const dataSets = [];
|
|
42
|
+
dataModel.forEach(ld => {
|
|
43
|
+
ld.logicalNodes.forEach(ln => {
|
|
44
|
+
ln.dataSets.forEach(ds => {
|
|
45
|
+
console.log(`Found dataset: ${ds.reference}`);
|
|
46
|
+
dataSets.push(ds);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Read datasets one by one
|
|
52
|
+
console.log('Available datasets from browseDataModel:');
|
|
53
|
+
for (const ds of dataSets) {
|
|
54
|
+
console.log(`- ${ds.reference}`);
|
|
55
|
+
try {
|
|
56
|
+
const values = await readDataSet(client, ds.reference);
|
|
57
|
+
console.log(`Values for ${ds.reference}:`, util.inspect(values, { depth: null }));
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`Error reading ${ds.reference}:`, err.message);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Enable reporting
|
|
64
|
+
const rcbRef = 'simpleIOGenericIO/LLN0.RP.EventsRCB01';
|
|
65
|
+
const dataSetRef = 'simpleIOGenericIO/LLN0.Events';
|
|
66
|
+
console.log(`Enabling reporting for ${rcbRef} with dataset ${dataSetRef}`);
|
|
67
|
+
client.enableReporting(rcbRef, dataSetRef);
|
|
68
|
+
|
|
69
|
+
// Wait for some reports
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
71
|
+
|
|
72
|
+
// Disable reporting
|
|
73
|
+
console.log(`Disabling reporting for ${rcbRef}`);
|
|
74
|
+
client.disableReporting(rcbRef);
|
|
75
|
+
})
|
|
76
|
+
.catch(err => console.error('Error browsing data model:', err.message));
|
|
77
|
+
|
|
78
|
+
console.log('Reading data...');
|
|
79
|
+
const dataRefs = [
|
|
80
|
+
'simpleIOGenericIO/GGIO1.AnIn1.mag.f',
|
|
81
|
+
'simpleIOGenericIO/GGIO1.AnIn2.mag.f',
|
|
82
|
+
'simpleIOGenericIO/GGIO1.AnIn3.mag.f',
|
|
83
|
+
'simpleIOGenericIO/GGIO1.AnIn4.mag.f',
|
|
84
|
+
'simpleIOGenericIO/GGIO1.SPCSO1.stVal',
|
|
85
|
+
'simpleIOGenericIO/GGIO1.SPCSO2.stVal',
|
|
86
|
+
'simpleIOGenericIO/GGIO1.SPCSO3.stVal',
|
|
87
|
+
'simpleIOGenericIO/GGIO1.SPCSO4.stVal'
|
|
88
|
+
];
|
|
89
|
+
dataRefs.forEach(ref => client.readData(ref));
|
|
90
|
+
|
|
91
|
+
console.log('And now we try do control operation!!!!!!!!!!!!!!!...');
|
|
92
|
+
client.controlObject("simpleIOGenericIO/GGIO1.SPCSO1", true);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (event === 'data' && data.type === 'data') {
|
|
96
|
+
if (data.event === 'logicalDevices') {
|
|
97
|
+
console.log(`Logical Devices received: ${util.inspect(data.logicalDevices, { depth: null })}`);
|
|
98
|
+
} else if (data.event === 'dataSetDirectory') {
|
|
99
|
+
console.log(`DataSet Directory for ${data.logicalNodeRef}: ${util.inspect(data.dataSets, { depth: null })}`);
|
|
100
|
+
} else if (data.event === 'dataModel') {
|
|
101
|
+
console.log(`Data Model received: ${util.inspect(data.dataModel, { depth: null })}`);
|
|
102
|
+
} else if (data.event === 'dataSet') {
|
|
103
|
+
console.log(`DataSet received for ${data.dataSetRef}: ${util.inspect(data.value, { depth: null })}`);
|
|
104
|
+
} else if (data.event === 'report') {
|
|
105
|
+
console.log(`Report received for ${data.rcbRef} (rptId: ${data.rptId}):`);
|
|
106
|
+
if (data.timestamp) {
|
|
107
|
+
console.log(` Timestamp: ${data.timestamp}`);
|
|
108
|
+
}
|
|
109
|
+
data.values.forEach((value, index) => {
|
|
110
|
+
if (data.reasonsForInclusion[index] !== 0) {
|
|
111
|
+
console.log(` Value[${index}]: ${util.inspect(value, { depth: null })}, Reason: ${data.reasonsForInclusion[index]}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`Data received for ${data.dataRef || 'undefined'}: ${util.inspect(data.value, { depth: null })}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (event === 'data' && data.type === 'error') {
|
|
120
|
+
console.error(`Error received: ${data.reason}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (event === 'conn' && data.event === 'reconnecting') {
|
|
124
|
+
console.error(`Reconnection failed: ${data.reason}`);
|
|
125
|
+
if (data.reason.includes('attempt 3')) {
|
|
126
|
+
throw new Error('Max reconnection attempts reached');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (event === 'data' && data.type === 'control') {
|
|
131
|
+
if (data.event === 'reportingEnabled') {
|
|
132
|
+
console.log(`Reporting enabled for ${data.rcbRef}`);
|
|
133
|
+
} else if (data.event === 'reportingDisabled') {
|
|
134
|
+
console.log(`Reporting disabled for ${data.rcbRef}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
async function main() {
|
|
140
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
console.log('Starting client...');
|
|
144
|
+
await client.connect({
|
|
145
|
+
ip: '127.0.0.1',
|
|
146
|
+
port: 102,
|
|
147
|
+
clientID: 'mms_client1',
|
|
148
|
+
reconnectDelay: 2
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await sleep(20000);
|
|
152
|
+
|
|
153
|
+
console.log('Client status:', client.getStatus());
|
|
154
|
+
|
|
155
|
+
console.log('Closing client...');
|
|
156
|
+
await client.close();
|
|
157
|
+
console.log('Client closed.');
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('Main error:', err.message);
|
|
160
|
+
await client.close().catch(e => console.error('Close error:', e.message));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch(err => console.error('Fatal error:', err.message));*/
|
|
165
|
+
|
|
166
|
+
const { MmsClient } = require('../build/Release/addon_iec61850');
|
|
167
|
+
const util = require('util');
|
|
168
|
+
|
|
169
|
+
const client = new MmsClient((event, data) => {
|
|
170
|
+
console.log(`Event: ${event}, Data: ${util.inspect(data, { depth: null })}`);
|
|
171
|
+
|
|
172
|
+
if (event === 'conn' && data.event === 'opened') {
|
|
173
|
+
console.log('Connection opened, browsing data model...');
|
|
174
|
+
client.browseDataModel()
|
|
175
|
+
.then(dataModel => {
|
|
176
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
177
|
+
|
|
178
|
+
// Извлечение ссылок на датасеты
|
|
179
|
+
const dataSets = [];
|
|
180
|
+
dataModel.forEach(ld => {
|
|
181
|
+
ld.logicalNodes.forEach(ln => {
|
|
182
|
+
ln.dataSets.forEach(ds => {
|
|
183
|
+
console.log(`Found dataset: ${ds.reference}`);
|
|
184
|
+
dataSets.push(ds);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Чтение датасетов напрямую без промисов
|
|
190
|
+
console.log('Reading datasets...');
|
|
191
|
+
dataSets.forEach(ds => {
|
|
192
|
+
console.log(`- ${ds.reference}`);
|
|
193
|
+
client.readDataSetValues(ds.reference);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Чтение данных
|
|
197
|
+
console.log('Reading data...');
|
|
198
|
+
const dataRefs = [
|
|
199
|
+
'simpleIOGenericIO/GGIO1.AnIn1.mag.f',
|
|
200
|
+
'simpleIOGenericIO/GGIO1.AnIn2.mag.f',
|
|
201
|
+
'simpleIOGenericIO/GGIO1.AnIn3.mag.f',
|
|
202
|
+
'simpleIOGenericIO/GGIO1.AnIn4.mag.f',
|
|
203
|
+
'simpleIOGenericIO/GGIO1.SPCSO1.stVal',
|
|
204
|
+
'simpleIOGenericIO/GGIO1.SPCSO2.stVal',
|
|
205
|
+
'simpleIOGenericIO/GGIO1.SPCSO3.stVal',
|
|
206
|
+
'simpleIOGenericIO/GGIO1.SPCSO4.stVal'
|
|
207
|
+
];
|
|
208
|
+
dataRefs.forEach(ref => client.readData(ref));
|
|
209
|
+
|
|
210
|
+
// Включение отчётов
|
|
211
|
+
const rcbRef = 'simpleIOGenericIO/LLN0.RP.EventsRCB01';
|
|
212
|
+
const dataSetRef = 'simpleIOGenericIO/LLN0.Events';
|
|
213
|
+
console.log(`Enabling reporting for ${rcbRef} with dataset ${dataSetRef}`);
|
|
214
|
+
client.enableReporting(rcbRef, dataSetRef);
|
|
215
|
+
})
|
|
216
|
+
.catch(err => console.error('Error browsing data model:', err.message));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (event === 'data' && data.type === 'data') {
|
|
220
|
+
if (data.event === 'logicalDevices') {
|
|
221
|
+
console.log(`Logical Devices received: ${util.inspect(data.logicalDevices, { depth: null })}`);
|
|
222
|
+
} else if (data.event === 'dataSetDirectory') {
|
|
223
|
+
console.log(`DataSet Directory for ${data.logicalNodeRef}: ${util.inspect(data.dataSets, { depth: null })}`);
|
|
224
|
+
} else if (data.event === 'dataModel') {
|
|
225
|
+
console.log(`Data Model received: ${util.inspect(data.dataModel, { depth: null })}`);
|
|
226
|
+
} else if (data.event === 'dataSet') {
|
|
227
|
+
console.log(`DataSet received for ${data.dataSetRef}: ${util.inspect(data.value, { depth: null })}`);
|
|
228
|
+
} else if (data.event === 'report') {
|
|
229
|
+
console.log(`Report received for ${data.rcbRef} (rptId: ${data.rptId}):`);
|
|
230
|
+
if (data.timestamp) {
|
|
231
|
+
console.log(` Timestamp: ${data.timestamp}`);
|
|
232
|
+
}
|
|
233
|
+
data.values.forEach((value, index) => {
|
|
234
|
+
if (data.reasonsForInclusion[index] !== 0) {
|
|
235
|
+
console.log(` Value[${index}]: ${util.inspect(value, { depth: null })}, Reason: ${data.reasonsForInclusion[index]}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
console.log(`Data received for ${data.dataRef || 'undefined'}: ${util.inspect(data.value, { depth: null })}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (event === 'data' && data.type === 'error') {
|
|
244
|
+
console.error(`Error received: ${data.reason}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (event === 'conn' && data.event === 'reconnecting') {
|
|
248
|
+
console.error(`Reconnection failed: ${data.reason}`);
|
|
249
|
+
if (data.reason.includes('attempt 3')) {
|
|
250
|
+
throw new Error('Max reconnection attempts reached');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (event === 'data' && data.type === 'control') {
|
|
255
|
+
if (data.event === 'reportingEnabled') {
|
|
256
|
+
console.log(`Reporting enabled for ${data.rcbRef}`);
|
|
257
|
+
} else if (data.event === 'reportingDisabled') {
|
|
258
|
+
console.log(`Reporting disabled for ${data.rcbRef}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
async function main() {
|
|
264
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
console.log('Starting client...');
|
|
268
|
+
await client.connect({
|
|
269
|
+
ip: '127.0.0.1',
|
|
270
|
+
port: 102,
|
|
271
|
+
clientID: 'mms_client1',
|
|
272
|
+
reconnectDelay: 2
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Ожидание обработки данных и отчетов
|
|
276
|
+
console.log('Waiting for data and reports...');
|
|
277
|
+
await sleep(10000);
|
|
278
|
+
|
|
279
|
+
// Отключение отчетов
|
|
280
|
+
const rcbRef = 'simpleIOGenericIO/LLN0.RP.EventsRCB01';
|
|
281
|
+
console.log(`Disabling reporting for ${rcbRef}`);
|
|
282
|
+
client.disableReporting(rcbRef);
|
|
283
|
+
|
|
284
|
+
// Выполнение операции управления
|
|
285
|
+
console.log('And now we try do control operation!!!!!!!!!!!!!!!...');
|
|
286
|
+
client.controlObject("simpleIOGenericIO/GGIO1.SPCSO1", true);
|
|
287
|
+
|
|
288
|
+
// Дополнительное ожидание для обработки операции управления
|
|
289
|
+
await sleep(5000);
|
|
290
|
+
|
|
291
|
+
console.log('Client status:', client.getStatus());
|
|
292
|
+
|
|
293
|
+
console.log('Closing client...');
|
|
294
|
+
await client.close();
|
|
295
|
+
console.log('Client closed.');
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.error('Main error:', err.message);
|
|
298
|
+
await client.close().catch(e => console.error('Close error:', e.message));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
main().catch(err => console.error('Fatal error:', err.message));
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
client.browseDataModel()
|
|
10
|
+
.then(dataModel => {
|
|
11
|
+
console.log('Data Model:', util.inspect(dataModel, { depth: null }));
|
|
12
|
+
|
|
13
|
+
// Извлечение ссылок на датасеты
|
|
14
|
+
const dataSets = [];
|
|
15
|
+
dataModel.forEach(ld => {
|
|
16
|
+
ld.logicalNodes.forEach(ln => {
|
|
17
|
+
ln.dataSets.forEach(ds => {
|
|
18
|
+
console.log(`Found dataset: ${ds.reference}`);
|
|
19
|
+
dataSets.push(ds);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Чтение датасетов напрямую без промисов
|
|
25
|
+
console.log('Reading datasets...');
|
|
26
|
+
dataSets.forEach(ds => {
|
|
27
|
+
console.log(`- ${ds.reference}`);
|
|
28
|
+
client.readDataSetValues(ds.reference);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Чтение данных
|
|
32
|
+
console.log('Reading data...');
|
|
33
|
+
const dataRefs = [
|
|
34
|
+
'WAGO61850ServerDevice/XCBR1.Pos.stVal',
|
|
35
|
+
'WAGO61850ServerDevice/GGIO1.Ind.stVal',
|
|
36
|
+
'WAGO61850ServerDevice/CALH1.GrAlm.stVal'
|
|
37
|
+
];
|
|
38
|
+
dataRefs.forEach(ref => client.readData(ref));
|
|
39
|
+
|
|
40
|
+
// Включение отчётов
|
|
41
|
+
const rcbRef = 'WAGO61850ServerDevice/LLN0.RP.ReportBlock0101';
|
|
42
|
+
const dataSetRef = 'WAGO61850ServerDevice/LLN0.DataSet01';
|
|
43
|
+
console.log(`Enabling reporting for ${rcbRef} with dataset ${dataSetRef}`);
|
|
44
|
+
client.enableReporting(rcbRef, dataSetRef);
|
|
45
|
+
})
|
|
46
|
+
.catch(err => console.error('Error browsing data model:', err.message));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (event === 'data' && data.type === 'data') {
|
|
50
|
+
if (data.event === 'logicalDevices') {
|
|
51
|
+
console.log(`Logical Devices received: ${util.inspect(data.logicalDevices, { depth: null })}`);
|
|
52
|
+
} else if (data.event === 'dataSetDirectory') {
|
|
53
|
+
console.log(`DataSet Directory for ${data.logicalNodeRef}: ${util.inspect(data.dataSets, { depth: null })}`);
|
|
54
|
+
} else if (data.event === 'dataModel') {
|
|
55
|
+
console.log(`Data Model received: ${util.inspect(data.dataModel, { depth: null })}`);
|
|
56
|
+
} else if (data.event === 'dataSet') {
|
|
57
|
+
console.log(`DataSet received for ${data.dataSetRef}: ${util.inspect(data.value, { depth: null })}`);
|
|
58
|
+
} else if (data.event === 'report') {
|
|
59
|
+
console.log(`Report received for ${data.rcbRef} (rptId: ${data.rptId}):`);
|
|
60
|
+
if (data.timestamp) {
|
|
61
|
+
console.log(` Timestamp: ${data.timestamp}`);
|
|
62
|
+
}
|
|
63
|
+
data.values.forEach((value, index) => {
|
|
64
|
+
if (data.reasonsForInclusion[index] !== 0) {
|
|
65
|
+
console.log(` Value[${index}]: ${util.inspect(value, { depth: null })}, Reason: ${data.reasonsForInclusion[index]}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`Data received for ${data.dataRef || 'undefined'}: ${util.inspect(data.value, { depth: null })}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (event === 'data' && data.type === 'error') {
|
|
74
|
+
console.error(`Error received: ${data.reason}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (event === 'conn' && data.event === 'reconnecting') {
|
|
78
|
+
console.error(`Reconnection failed: ${data.reason}`);
|
|
79
|
+
if (data.reason.includes('attempt 3')) {
|
|
80
|
+
throw new Error('Max reconnection attempts reached');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (event === 'data' && data.type === 'control') {
|
|
85
|
+
if (data.event === 'reportingEnabled') {
|
|
86
|
+
console.log(`Reporting enabled for ${data.rcbRef}`);
|
|
87
|
+
} else if (data.event === 'reportingDisabled') {
|
|
88
|
+
console.log(`Reporting disabled for ${data.rcbRef}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
async function main() {
|
|
94
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
console.log('Starting client...');
|
|
98
|
+
await client.connect({
|
|
99
|
+
ip: '192.168.0.102',
|
|
100
|
+
port: 102,
|
|
101
|
+
clientID: 'mms_client1',
|
|
102
|
+
reconnectDelay: 2
|
|
103
|
+
});
|
|
104
|
+
await sleep(5000);
|
|
105
|
+
// Выполнение операции управления
|
|
106
|
+
//console.log('And now we try do control operation!!!!!!!!!!!!!!!...');
|
|
107
|
+
//client.controlObject("WAGO61850ServerDevice/XCBR1.Pos", true);
|
|
108
|
+
|
|
109
|
+
// Дополнительное ожидание для обработки операции управления
|
|
110
|
+
//await sleep(5000);
|
|
111
|
+
|
|
112
|
+
// Ожидание обработки данных и отчетов
|
|
113
|
+
console.log('Waiting for data and reports...');
|
|
114
|
+
await sleep(30000);
|
|
115
|
+
|
|
116
|
+
// Отключение отчетов
|
|
117
|
+
const rcbRef = 'WAGO61850ServerDevice/LLN0.RP.ReportBlock0101';
|
|
118
|
+
console.log(`Disabling reporting for ${rcbRef}`);
|
|
119
|
+
client.disableReporting(rcbRef);
|
|
120
|
+
|
|
121
|
+
console.log('Client status:', client.getStatus());
|
|
122
|
+
|
|
123
|
+
console.log('Closing client...');
|
|
124
|
+
await client.close();
|
|
125
|
+
console.log('Client closed.');
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error('Main error:', err.message);
|
|
128
|
+
await client.close().catch(e => console.error('Close error:', e.message));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main().catch(err => console.error('Fatal error:', err.message));
|
package/examples/test.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const { NodeGOOSESubscriber } = require('../build/Release/addon_iec61850');
|
|
2
|
+
const subscriber = new NodeGOOSESubscriber((event, data) => {
|
|
3
|
+
if (event === 'data' && data.type === 'data' && data.event === 'goose') {
|
|
4
|
+
console.log(`GOOSE-сообщение для ${data.goCbRef}: ${JSON.stringify(data.values)}`);
|
|
5
|
+
}
|
|
6
|
+
});
|
|
7
|
+
subscriber.subscribe('en5', 'WAGO61850ServerDevice/LLN0$GO$GSB_0');
|
package/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const platform = os.platform();
|
|
6
|
+
const arch = os.arch();
|
|
7
|
+
|
|
8
|
+
// Карта соответствий платформ и архитектур с именами папок
|
|
9
|
+
const bindingsMap = {
|
|
10
|
+
'win32_x64': 'windows_x64',
|
|
11
|
+
'darwin_x64': 'macos_x64',
|
|
12
|
+
'darwin_arm64': 'macos_arm64',
|
|
13
|
+
'linux_x64': 'linux_x64',
|
|
14
|
+
'linux_arm64': 'linux_arm64',
|
|
15
|
+
'linux_arm': 'linux_arm'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const key = platform+"_"+arch;
|
|
20
|
+
const bindingFolder = bindingsMap[key];
|
|
21
|
+
|
|
22
|
+
if (!bindingFolder) {
|
|
23
|
+
throw new Error(`Unsupported platform/architecture combination: ${platform}/${arch}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const bindingPath = path.join(__dirname, 'builds', bindingFolder, 'addon_iec61850.node');
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(bindingPath)) {
|
|
29
|
+
throw new Error(`Native binding not found at: ${bindingPath}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const binding = require(bindingPath);
|
|
33
|
+
module.exports = binding;
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amigo9090/ih-libiec61850-node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.js addon for IEC 61850 client (MMS, GOOSE) using libiec61850",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"configure": "node-gyp configure",
|
|
8
|
+
"build": "node-gyp build",
|
|
9
|
+
"prebuild": "prebuild --target=20.19.0 --napi-versions 11",
|
|
10
|
+
"prebuild-upload": "prebuild --upload-all",
|
|
11
|
+
"install": "node-gyp rebuild"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"native",
|
|
15
|
+
"addon",
|
|
16
|
+
"cross-platform"
|
|
17
|
+
],
|
|
18
|
+
"author": "Amigo9090",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"builds/",
|
|
23
|
+
"examples/"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"node-addon-api": "^7.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"prebuild": "12.1.0",
|
|
30
|
+
"node-gyp": "^10.1.0",
|
|
31
|
+
"node-addon-api": "^7.1.1"
|
|
32
|
+
},
|
|
33
|
+
"os": [
|
|
34
|
+
"darwin",
|
|
35
|
+
"linux",
|
|
36
|
+
"win32"
|
|
37
|
+
],
|
|
38
|
+
"cpu": [
|
|
39
|
+
"x64",
|
|
40
|
+
"ia32",
|
|
41
|
+
"arm64",
|
|
42
|
+
"arm"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/intrahouseio/ih-libiec61850-node.git"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/intrahouseio/ih-libiec61850-node#readme",
|
|
49
|
+
"directories": {
|
|
50
|
+
"example": "examples",
|
|
51
|
+
"lib": "lib"
|
|
52
|
+
},
|
|
53
|
+
"gypfile": true,
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/intrahouseio/ih-libiec61850-node/issues"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# ih-lib60870-node
|
|
2
|
+
|
|
3
|
+
A cross-platform Node.js native addon for the **IEC 60870-5 protocol suite**, enabling seamless communication with industrial control systems, SCADA, and RTUs. Built with `node-gyp` and `prebuild`, this addon ensures compatibility across multiple operating systems and architectures.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- **IEC 60870-5 Protocol Support**: Implements key functionalities of IEC 60870-5 standards (e.g., IEC 60870-5-101/104) for robust industrial automation communication.
|
|
10
|
+
- **Cross-Platform Compatibility**: Supports Windows, Linux, and macOS with prebuilt binaries for x64, arm, and arm64 architectures.
|
|
11
|
+
- **High Performance**: Native C++ implementation optimized for low-latency and reliable data exchange.
|
|
12
|
+
- **File Transfer**: Supports file transfer operations in both monitoring and control directions.
|
|
13
|
+
- **Flexible Integration**: Easy-to-use APIs for integration with Node.js applications, SCADA systems, or custom control solutions.
|
|
14
|
+
- **Prebuilt Binaries**: Includes precompiled binaries for Node.js v20, simplifying setup and deployment.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🖥️ Supported Platforms
|
|
19
|
+
|
|
20
|
+
| Operating System | Architectures |
|
|
21
|
+
|------------------|--------------------|
|
|
22
|
+
| Windows | x64 |
|
|
23
|
+
| Linux | x64, arm, arm64 |
|
|
24
|
+
| macOS | x64, arm64 |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🚀 Installation
|
|
29
|
+
|
|
30
|
+
1. Ensure you have **Node.js v20** installed.
|
|
31
|
+
2. Install the package via npm:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install ih-lib60870-node
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Prebuilt binaries will be automatically downloaded for your platform and architecture. If a prebuilt binary is unavailable, the addon will be compiled using `node-gyp`, requiring:
|
|
38
|
+
- **Python 3.11+**
|
|
39
|
+
- A compatible C++ compiler:
|
|
40
|
+
- `gcc` on Linux
|
|
41
|
+
- `MSVC` on Windows
|
|
42
|
+
- `clang` on macOS
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📖 Usage
|
|
47
|
+
|
|
48
|
+
Below is an example of using `ih-lib60870-node` to establish an IEC 60870-5-104 connection and handle data:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const { IEC104Client } = require('ih-lib60870-node');
|
|
52
|
+
const util = require('util');
|
|
53
|
+
const fs = require('fs');
|
|
54
|
+
|
|
55
|
+
// Initialize an IEC 60870-5-104 client
|
|
56
|
+
const client = new IEC104Client((event, data) => {
|
|
57
|
+
if (data.event === 'opened') client.sendStartDT();
|
|
58
|
+
console.log(`Server 1 Event: ${event}, Data: ${util.inspect(data)}`);
|
|
59
|
+
if (data.event === 'activated') client.sendCommands([
|
|
60
|
+
{ typeId: 100, ioa: 0, asdu: 1, value: 20 }, // Interrogation command
|
|
61
|
+
{ typeId: 45, ioa: 145, value: true, asdu: 1, bselCmd: true, ql: 1 }, // C_SC_NA_1: On
|
|
62
|
+
{ typeId: 46, ioa: 4000, value: 1, asdu: 1, bselCmd: 1, ql: 2 }, // C_DC_NA_1: Off
|
|
63
|
+
{ typeId: 47, ioa: 147, value: 1, asdu: 1, bselCmd: 1, ql: 0 }, // C_RC_NA_1: Increase
|
|
64
|
+
{ typeId: 48, ioa: 148, value: 0.001, asdu: 1, selCmd: 1, ql: 0 }, // C_SE_NA_1: Normalized setpoint
|
|
65
|
+
{ typeId: 49, ioa: 149, value: 5000, asdu: 1, bselCmd: 1, ql: 0 }, // C_SE_NB_1: Scaled setpoint
|
|
66
|
+
{ typeId: 50, ioa: 150, value: 123.45, asdu: 1 }, // C_SE_NC_1: Floating point setpoint
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const client2 = new IEC104Client((event, data) => {
|
|
71
|
+
if (data.event === 'opened') client2.sendStartDT();
|
|
72
|
+
console.log(`Server 2 Event: ${event}, Data: ${util.inspect(data)}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
77
|
+
|
|
78
|
+
client.connect({
|
|
79
|
+
ip: "192.168.0.1",
|
|
80
|
+
port: 2404,
|
|
81
|
+
clientID: "client1",
|
|
82
|
+
ipReserve: "192.168.0.2",
|
|
83
|
+
reconnectDelay: 2, // Reconnection delay in seconds
|
|
84
|
+
originatorAddress: 0,
|
|
85
|
+
asduAddress: 1,
|
|
86
|
+
k: 12,
|
|
87
|
+
w: 8,
|
|
88
|
+
t0: 30,
|
|
89
|
+
t1: 15,
|
|
90
|
+
t2: 10,
|
|
91
|
+
t3: 20,
|
|
92
|
+
maxRetries: 5
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
client2.connect({
|
|
96
|
+
ip: "192.168.0.10",
|
|
97
|
+
port: 2404,
|
|
98
|
+
clientID: "client2",
|
|
99
|
+
originatorAddress: 1,
|
|
100
|
+
k: 12,
|
|
101
|
+
w: 8,
|
|
102
|
+
t0: 30,
|
|
103
|
+
t1: 15,
|
|
104
|
+
t2: 10,
|
|
105
|
+
t3: 20,
|
|
106
|
+
reconnectDelay: 2,
|
|
107
|
+
maxRetries: 5
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Wait for synchronization (optional)
|
|
111
|
+
await sleep(1000);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
📚 **Additional Examples**: Examples for each supported protocol (e.g., IEC 60870-5-101, IEC 60870-5-104) are available in the [`examples/` directory](examples/). These demonstrate various configurations and use cases for industrial automation.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 🛠️ Building from Source
|
|
122
|
+
|
|
123
|
+
To build the addon from source:
|
|
124
|
+
|
|
125
|
+
1. Clone the repository:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
git clone https://github.com/intrahouseio/ih-lib60870-node.git
|
|
129
|
+
cd ih-lib60870-node
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
2. Install dependencies:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
3. Configure and build:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run configure
|
|
142
|
+
npm run build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
4. Optionally, generate prebuilt binaries:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm run prebuild
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 🤝 Contributing
|
|
154
|
+
|
|
155
|
+
Contributions are welcome! To contribute:
|
|
156
|
+
|
|
157
|
+
1. Fork the repository.
|
|
158
|
+
2. Create a new branch for your feature or bug fix.
|
|
159
|
+
3. Submit a pull request with a clear description of your changes.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 📜 License
|
|
164
|
+
|
|
165
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 💬 Support
|
|
170
|
+
|
|
171
|
+
For issues, questions, or feature requests, please open an issue on the [GitHub repository](https://github.com/intrahouseio/ih-lib60870-node/issues).
|