@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.
@@ -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));
@@ -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).