@node-i3x/demo-embedded 0.1.0 → 0.2.4

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.
@@ -1,148 +0,0 @@
1
- // ─────────────────────────────────────────────────────────────
2
- // i3X Remote Demo (for comparison)
3
- //
4
- // Same OPC UA server, same REST API — but connected via
5
- // OPC UA binary transport (ClientSession over TCP).
6
- //
7
- // Run this side-by-side with the embedded demo to see
8
- // the architectural difference.
9
- // ─────────────────────────────────────────────────────────────
10
-
11
- import {
12
- consoleLogger,
13
- HistoryService,
14
- ModelService,
15
- SubscriptionService,
16
- ValueService,
17
- } from '@node-i3x/core';
18
- import { OpcUaClient, OpcUaDataSourceAdapter } from '@node-i3x/opcua-connector';
19
- import { createApp } from '@node-i3x/rest-server';
20
- import { DataType, nodesets, OPCUAServer, Variant } from 'node-opcua';
21
-
22
- const REST_PORT = 8080;
23
- const OPCUA_PORT = 48411;
24
-
25
- async function createSameServer() {
26
- const server = new OPCUAServer({
27
- port: OPCUA_PORT,
28
- resourcePath: '/UA/RemoteDemo',
29
- nodeset_filename: [nodesets.standard],
30
- });
31
- await server.initialize();
32
- const addressSpace = server.engine.addressSpace;
33
- if (!addressSpace) {
34
- throw new Error('Address space not initialized');
35
- }
36
- const ns = addressSpace.getOwnNamespace();
37
-
38
- const factory = ns.addObject({
39
- organizedBy: addressSpace.rootFolder.objects,
40
- browseName: 'SmartFactory',
41
- displayName: 'Smart Factory',
42
- });
43
- const pump = ns.addObject({
44
- componentOf: factory,
45
- browseName: 'Pump',
46
- displayName: 'Main Coolant Pump',
47
- });
48
- ns.addVariable({
49
- componentOf: pump,
50
- browseName: 'Temperature',
51
- dataType: DataType.Double,
52
- value: new Variant({
53
- dataType: DataType.Double,
54
- value: 35.0,
55
- }),
56
- });
57
- ns.addVariable({
58
- componentOf: pump,
59
- browseName: 'FlowRate',
60
- dataType: DataType.Double,
61
- value: new Variant({
62
- dataType: DataType.Double,
63
- value: 120.5,
64
- }),
65
- });
66
- ns.addVariable({
67
- componentOf: pump,
68
- browseName: 'Status',
69
- dataType: DataType.String,
70
- value: new Variant({
71
- dataType: DataType.String,
72
- value: 'Running',
73
- }),
74
- });
75
-
76
- await server.start();
77
- return server;
78
- }
79
-
80
- async function main() {
81
- const logger = consoleLogger;
82
-
83
- console.log(`\n${'═'.repeat(60)}`);
84
- console.log(' 🏭 i3X Remote Demo — OPC UA Binary Transport');
85
- console.log('═'.repeat(60));
86
-
87
- const server = await createSameServer();
88
- const endpointUrl = `opc.tcp://localhost:${OPCUA_PORT}/UA/RemoteDemo`;
89
- console.log(`\n OPC UA server at ${endpointUrl}`);
90
-
91
- // ┌──────────────────────────────────────────────────────┐
92
- // │ REMOTE path: connect via OPC UA binary protocol │
93
- // │ Requires TCP connection, serialization, etc. │
94
- // └──────────────────────────────────────────────────────┘
95
- const client = new OpcUaClient(
96
- {
97
- endpointUrl,
98
- securityMode: 'None',
99
- optimizedClient: 'disabled',
100
- },
101
- logger,
102
- );
103
- const dataSource = new OpcUaDataSourceAdapter(client, logger);
104
- await dataSource.connect();
105
-
106
- const modelService = new ModelService(dataSource, logger);
107
- const valueService = new ValueService(dataSource, modelService, logger);
108
- const historyService = new HistoryService(dataSource, modelService, logger);
109
- const subscriptionService = new SubscriptionService(
110
- dataSource,
111
- modelService,
112
- logger,
113
- 1,
114
- );
115
-
116
- const model = await modelService.preloadModel();
117
- console.log(
118
- ` Model: ${model.nodesById.size} nodes, ` + `${model.rootIds.length} roots`,
119
- );
120
-
121
- const app = await createApp({
122
- dataSource,
123
- modelService,
124
- valueService,
125
- historyService,
126
- subscriptionService,
127
- logger,
128
- });
129
- await app.listen({ port: REST_PORT, host: '127.0.0.1' });
130
-
131
- console.log(`\n 🚀 REST API at http://127.0.0.1:${REST_PORT}`);
132
- console.log(' Press Ctrl+C to stop.\n');
133
-
134
- const shutdown = async () => {
135
- await app.close();
136
- await subscriptionService.close();
137
- await dataSource.disconnect();
138
- await server.shutdown(500);
139
- process.exit(0);
140
- };
141
- process.on('SIGINT', shutdown);
142
- process.on('SIGTERM', shutdown);
143
- }
144
-
145
- main().catch((err) => {
146
- console.error('Fatal:', err);
147
- process.exit(1);
148
- });
package/src/index.ts DELETED
@@ -1,350 +0,0 @@
1
- // ─────────────────────────────────────────────────────────────
2
- // i3X Embedded Demo
3
- //
4
- // Creates an OPC UA server + i3X REST API in a SINGLE process.
5
- // No TCP round-trip — PseudoSession talks directly to the
6
- // AddressSpace in memory.
7
- // ─────────────────────────────────────────────────────────────
8
-
9
- import {
10
- consoleLogger,
11
- HistoryService,
12
- ModelService,
13
- SubscriptionService,
14
- ValueService,
15
- } from '@node-i3x/core';
16
- import { PseudoSessionDataSourceAdapter } from '@node-i3x/pseudo-session-connector';
17
- import { createApp } from '@node-i3x/rest-server';
18
- import { DataType, nodesets, OPCUAServer, type UAVariable, Variant } from 'node-opcua';
19
- import { parseArgs } from 'node:util';
20
-
21
- const { values: args } = parseArgs({
22
- options: {
23
- 'rest-port': { type: 'string', default: '8080' },
24
- 'opcua-port': { type: 'string', default: '48410' },
25
- help: { type: 'boolean', short: 'h', default: false },
26
- },
27
- });
28
-
29
- if (args.help) {
30
- console.log(`
31
- Usage: i3x-demo [options]
32
-
33
- Options:
34
- --rest-port <port> REST API port (default: 8080)
35
- --opcua-port <port> OPC UA server port (default: 48410)
36
- -h, --help Show this help
37
- `);
38
- process.exit(0);
39
- }
40
-
41
- const REST_PORT = parseInt(args['rest-port']!, 10);
42
- const OPCUA_PORT = parseInt(args['opcua-port']!, 10);
43
-
44
- // ── Helper: update a variable and fire value_changed ──────
45
-
46
- function setVar(v: UAVariable, dataType: DataType, val: unknown) {
47
- v.setValueFromSource(new Variant({ dataType, value: val }));
48
- }
49
-
50
- // ── 1. Create an OPC UA server with sample nodes ─────────
51
-
52
- async function createSampleServer() {
53
- const server = new OPCUAServer({
54
- port: OPCUA_PORT,
55
- resourcePath: '/UA/EmbeddedDemo',
56
- nodeset_filename: [nodesets.standard],
57
- serverInfo: {
58
- applicationName: { text: 'i3X Embedded Demo' },
59
- },
60
- });
61
-
62
- await server.initialize();
63
- const addressSpace = server.engine.addressSpace!;
64
- const ns = addressSpace.getOwnNamespace();
65
-
66
- // ── Factory floor ────────────────────────────────────
67
- const factory = ns.addObject({
68
- organizedBy: addressSpace.rootFolder.objects,
69
- browseName: 'SmartFactory',
70
- displayName: 'Smart Factory',
71
- });
72
-
73
- // ── Pump ─────────────────────────────────────────────
74
- const pump = ns.addObject({
75
- componentOf: factory,
76
- browseName: 'Pump',
77
- displayName: 'Main Coolant Pump',
78
- });
79
-
80
- const pumpTempVar = ns.addVariable({
81
- componentOf: pump,
82
- browseName: 'Temperature',
83
- displayName: 'Temperature (°C)',
84
- dataType: DataType.Double,
85
- value: new Variant({
86
- dataType: DataType.Double,
87
- value: 35.0,
88
- }),
89
- });
90
-
91
- const pumpPressVar = ns.addVariable({
92
- componentOf: pump,
93
- browseName: 'Pressure',
94
- displayName: 'Pressure (bar)',
95
- dataType: DataType.Double,
96
- value: new Variant({
97
- dataType: DataType.Double,
98
- value: 4.2,
99
- }),
100
- });
101
-
102
- const pumpFlowVar = ns.addVariable({
103
- componentOf: pump,
104
- browseName: 'FlowRate',
105
- displayName: 'Flow Rate (L/min)',
106
- dataType: DataType.Double,
107
- value: new Variant({
108
- dataType: DataType.Double,
109
- value: 120.5,
110
- }),
111
- });
112
-
113
- const _pumpRunVar = ns.addVariable({
114
- componentOf: pump,
115
- browseName: 'Running',
116
- displayName: 'Running',
117
- dataType: DataType.Boolean,
118
- value: new Variant({
119
- dataType: DataType.Boolean,
120
- value: true,
121
- }),
122
- });
123
-
124
- // ── Heater ───────────────────────────────────────────
125
- const heater = ns.addObject({
126
- componentOf: factory,
127
- browseName: 'Heater',
128
- displayName: 'Process Heater',
129
- });
130
-
131
- const heaterOnVar = ns.addVariable({
132
- componentOf: heater,
133
- browseName: 'HeaterOn',
134
- displayName: 'Heater On/Off',
135
- dataType: DataType.Boolean,
136
- value: new Variant({
137
- dataType: DataType.Boolean,
138
- value: true,
139
- }),
140
- });
141
-
142
- const heaterTempVar = ns.addVariable({
143
- componentOf: heater,
144
- browseName: 'Temperature',
145
- displayName: 'Temperature (°C)',
146
- dataType: DataType.Double,
147
- value: new Variant({
148
- dataType: DataType.Double,
149
- value: 180.0,
150
- }),
151
- });
152
-
153
- const _heaterSetpointVar = ns.addVariable({
154
- componentOf: heater,
155
- browseName: 'Setpoint',
156
- displayName: 'Setpoint (°C)',
157
- dataType: DataType.Double,
158
- value: new Variant({
159
- dataType: DataType.Double,
160
- value: 200.0,
161
- }),
162
- });
163
-
164
- const heaterPowerVar = ns.addVariable({
165
- componentOf: heater,
166
- browseName: 'Power',
167
- displayName: 'Power (%)',
168
- dataType: DataType.Double,
169
- value: new Variant({
170
- dataType: DataType.Double,
171
- value: 85.0,
172
- }),
173
- });
174
-
175
- // ── Conveyor ─────────────────────────────────────────
176
- const conveyor = ns.addObject({
177
- componentOf: factory,
178
- browseName: 'Conveyor',
179
- displayName: 'Assembly Conveyor',
180
- });
181
-
182
- const convSpeedVar = ns.addVariable({
183
- componentOf: conveyor,
184
- browseName: 'Speed',
185
- displayName: 'Speed (m/s)',
186
- dataType: DataType.Double,
187
- value: new Variant({
188
- dataType: DataType.Double,
189
- value: 2.3,
190
- }),
191
- });
192
-
193
- const itemCountVar = ns.addVariable({
194
- componentOf: conveyor,
195
- browseName: 'ItemCount',
196
- displayName: 'Items Processed',
197
- dataType: DataType.UInt32,
198
- value: new Variant({
199
- dataType: DataType.UInt32,
200
- value: 8452,
201
- }),
202
- });
203
-
204
- // ── Simulation (setValueFromSource → fires events) ───
205
-
206
- // Mutable state for simulation
207
- let pumpTemp = 35.0;
208
- let pumpPressure = 4.2;
209
- let pumpFlowRate = 120.5;
210
- let heaterOn = true;
211
- let heaterTemp = 180.0;
212
- let heaterPower = 85.0;
213
- let convSpeed = 2.3;
214
- let itemCount = 8452;
215
-
216
- // Pump: temperature, pressure, flow drift every 800ms
217
- setInterval(() => {
218
- pumpTemp += (Math.random() - 0.48) * 0.5;
219
- pumpPressure += (Math.random() - 0.5) * 0.1;
220
- pumpPressure = Math.max(3.0, Math.min(6.0, pumpPressure));
221
- pumpFlowRate += (Math.random() - 0.5) * 2.0;
222
- pumpFlowRate = Math.max(100, Math.min(140, pumpFlowRate));
223
-
224
- setVar(pumpTempVar, DataType.Double, pumpTemp);
225
- setVar(pumpPressVar, DataType.Double, pumpPressure);
226
- setVar(pumpFlowVar, DataType.Double, pumpFlowRate);
227
- }, 800);
228
-
229
- // Heater: temp tracks setpoint, power varies
230
- setInterval(() => {
231
- if (heaterOn) {
232
- heaterTemp += (200.0 - heaterTemp) * 0.05 + (Math.random() - 0.5) * 0.3;
233
- heaterPower = Math.max(0, Math.min(100, heaterPower + (Math.random() - 0.5) * 5));
234
- } else {
235
- heaterTemp -= 1.5 + Math.random() * 0.5;
236
- heaterPower = 0;
237
- }
238
- setVar(heaterTempVar, DataType.Double, heaterTemp);
239
- setVar(heaterPowerVar, DataType.Double, heaterPower);
240
- }, 1000);
241
-
242
- // Toggle heater every ~15 seconds
243
- setInterval(() => {
244
- heaterOn = !heaterOn;
245
- setVar(heaterOnVar, DataType.Boolean, heaterOn);
246
- }, 15_000);
247
-
248
- // Conveyor: items increase, speed varies
249
- setInterval(() => {
250
- itemCount += Math.floor(Math.random() * 3);
251
- convSpeed += (Math.random() - 0.5) * 0.1;
252
- convSpeed = Math.max(1.5, Math.min(3.5, convSpeed));
253
-
254
- setVar(convSpeedVar, DataType.Double, convSpeed);
255
- setVar(itemCountVar, DataType.UInt32, itemCount);
256
- }, 1200);
257
-
258
- await server.start();
259
-
260
- return { server, addressSpace };
261
- }
262
-
263
- // ── 2. Wire everything together ──────────────────────────
264
-
265
- async function main() {
266
- const logger = consoleLogger;
267
-
268
- console.log(`\n${'═'.repeat(60)}`);
269
- console.log(' 🏭 i3X Embedded Demo — PseudoSession Connector');
270
- console.log('═'.repeat(60));
271
-
272
- // Create the OPC UA server
273
- console.log('\n▶ Starting OPC UA server...');
274
- const { server, addressSpace } = await createSampleServer();
275
- console.log(
276
- ` ✓ OPC UA binary endpoint at ` +
277
- `opc.tcp://localhost:${OPCUA_PORT}/UA/EmbeddedDemo`,
278
- );
279
-
280
- // ┌──────────────────────────────────────────────────────┐
281
- // │ THIS IS THE KEY PART — 3 lines to connect i3X │
282
- // │ directly to the AddressSpace, no network needed. │
283
- // └──────────────────────────────────────────────────────┘
284
- console.log('\n▶ Connecting i3X via PseudoSession...');
285
- const dataSource = new PseudoSessionDataSourceAdapter(addressSpace, logger);
286
- await dataSource.connect();
287
- console.log(' ✓ Connected — zero-network, in-process');
288
-
289
- // Domain services (identical to the remote OPC UA path)
290
- const modelService = new ModelService(dataSource, logger);
291
- const valueService = new ValueService(dataSource, modelService, logger);
292
- const historyService = new HistoryService(dataSource, modelService, logger);
293
- const subscriptionService = new SubscriptionService(
294
- dataSource,
295
- modelService,
296
- logger,
297
- 1,
298
- );
299
-
300
- // Preload the model
301
- console.log('\n▶ Building i3X model from AddressSpace...');
302
- const model = await modelService.preloadModel();
303
- console.log(
304
- ` ✓ ${model.nodesById.size} nodes, ` +
305
- `${model.rootIds.length} roots, ` +
306
- `${model.propertyToSource.size} properties`,
307
- );
308
-
309
- // Start REST server
310
- const app = await createApp({
311
- dataSource,
312
- modelService,
313
- valueService,
314
- historyService,
315
- subscriptionService,
316
- logger,
317
- });
318
- await app.listen({ port: REST_PORT, host: '127.0.0.1' });
319
-
320
- console.log(`\n${'═'.repeat(60)}`);
321
- console.log(` 🚀 i3X REST API ready at http://127.0.0.1:${REST_PORT}`);
322
- console.log('═'.repeat(60));
323
- console.log('\n Try these:');
324
- console.log(` curl http://localhost:${REST_PORT}/health`);
325
- console.log(` curl http://localhost:${REST_PORT}/v1/info`);
326
- console.log(` curl http://localhost:${REST_PORT}/v1/namespaces`);
327
- console.log(` curl -X POST http://localhost:${REST_PORT}/v1/objects/list`);
328
- console.log(
329
- `\n OPC UA clients can also connect to ` +
330
- `opc.tcp://localhost:${OPCUA_PORT}/UA/EmbeddedDemo`,
331
- );
332
- console.log('\n Press Ctrl+C to stop.\n');
333
-
334
- // Graceful shutdown
335
- const shutdown = async () => {
336
- console.log('\n\nShutting down...');
337
- await app.close();
338
- await subscriptionService.close();
339
- await dataSource.disconnect();
340
- await server.shutdown(500);
341
- process.exit(0);
342
- };
343
- process.on('SIGINT', shutdown);
344
- process.on('SIGTERM', shutdown);
345
- }
346
-
347
- main().catch((err) => {
348
- console.error('Fatal:', err);
349
- process.exit(1);
350
- });
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "references": [
9
- { "path": "../core" },
10
- { "path": "../pseudo-session-connector" },
11
- { "path": "../rest-server" }
12
- ]
13
- }
package/tsup.config.ts DELETED
@@ -1,41 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig([
4
- {
5
- entry: ['src/index.ts'],
6
- format: ['esm'],
7
- minify: true,
8
- sourcemap: true,
9
- esbuildOptions(options) {
10
- options.sourcesContent = false;
11
- },
12
- clean: true,
13
- external: [
14
- /^@node-i3x\//,
15
- /^node-opcua/,
16
- /^fastify/,
17
- '@fastify/cors',
18
- ],
19
- banner: {
20
- js: '#!/usr/bin/env node',
21
- },
22
- },
23
- {
24
- entry: ['src/client.ts'],
25
- format: ['esm'],
26
- minify: true,
27
- sourcemap: true,
28
- esbuildOptions(options) {
29
- options.sourcesContent = false;
30
- },
31
- external: [
32
- /^@node-i3x\//,
33
- /^node-opcua/,
34
- /^fastify/,
35
- '@fastify/cors',
36
- ],
37
- banner: {
38
- js: '#!/usr/bin/env node',
39
- },
40
- },
41
- ]);