@node-i3x/demo-embedded 0.1.0 → 0.2.3
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/README.md +9 -2
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +0 -570
- package/src/demo-remote.ts +0 -148
- package/src/index.ts +0 -350
- package/tsconfig.json +0 -13
- package/tsup.config.ts +0 -41
package/README.md
CHANGED
|
@@ -82,6 +82,13 @@ See the [Embedding Tutorial](../pseudo-session-connector/TUTORIAL.md) for a step
|
|
|
82
82
|
|
|
83
83
|
## License
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Dual-licensed:
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
- **AGPL-3.0-or-later** -- you may use, modify, and distribute this software
|
|
88
|
+
freely, provided that all derivative works and network-accessible deployments
|
|
89
|
+
also make their complete source code available under the AGPL.
|
|
90
|
+
- **Sterfive Commercial License** -- allows proprietary and closed-source use
|
|
91
|
+
without copyleft obligations.
|
|
92
|
+
|
|
93
|
+
See [LICENSING.md](../../LICENSING.md) for details, or contact
|
|
94
|
+
[Sterfive](https://sterfive.com) for commercial licensing.
|
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import{parseArgs as q}from"util";var{values:j}=q({options:{url:{type:"string",de
|
|
|
4
4
|
|
|
5
5
|
`),P=(await h("/v1/info")).result.serverName;let t=await h("/v1/objects?root=true"),s=["Locations","Server","Aliases"],r=t.result.filter(o=>!s.includes(o.displayName));process.stdout.write(` Found ${r.length} user-defined root(s)
|
|
6
6
|
`);let i=[];for(let o of r)await T(o,i);for(let o of l)process.stdout.write(` \u{1F4E6} ${o.name} (${o.properties.length} props)
|
|
7
|
-
`);return i}async function T(e,t){if(C.set(e.elementId,e.displayName),!e.isComposition)return;t.push(e.
|
|
7
|
+
`);return i}async function T(e,t){if(C.set(e.elementId,e.displayName),!e.isComposition)return;t.push(e.elementId);let s=await p("/v1/objects/related",{elementIds:[e.elementId]});if(!s.results[0]?.success)return;let r=s.results[0]?.result.filter(a=>a.sourceRelationship==="HasComponent"),i=r.filter(a=>a.object.isComposition),o=r.filter(a=>!a.object.isComposition);if(o.length>0){let a=D(e.displayName),d={id:e.elementId,name:e.displayName,icon:a,properties:o.map(u=>(C.set(u.object.elementId,u.object.displayName),{id:u.object.elementId,name:u.object.displayName,value:"\u2014",quality:"Unknown",timestamp:"",changed:!1}))};l.push(d)}for(let a of i)await T(a.object,t)}function D(e){let t=e.toLowerCase();return t.includes("pump")?"\u{1F4A7}":t.includes("heater")?"\u{1F525}":t.includes("conveyor")?"\u{1F3ED}":t.includes("factory")?"\u{1F3D7}\uFE0F":"\u{1F4E6}"}async function F(){let e=l.map(s=>s.id);if(e.length===0)return;let t=await p("/v1/objects/value",{elementIds:e,maxDepth:3});for(let s of t.results){if(!s.success||!s.result.isComposition||!s.result.components)continue;let r=l.find(i=>i.id===s.elementId);if(r)for(let i of r.properties){let o=s.result.components[i.id];o&&(i.value=o.value,i.quality=o.quality,i.timestamp=o.timestamp)}}}async function V(){let e=l.map(s=>s.id);if(e.length===0)return;m=(await p("/v1/subscriptions",{clientId:"dashboard-client",displayName:"Dashboard Monitor"})).result.subscriptionId,await p("/v1/subscriptions/register",{subscriptionId:m,elementIds:e,maxDepth:3})}async function M(){try{let t=(await p("/v1/subscriptions/sync",{subscriptionId:m,acknowledgeSequence:f})).result;if(!t||t.length===0)return;N+=t.length,S++;for(let s of l)for(let r of s.properties)r.changed=!1;for(let s of t){if(s.sequenceNumber>f&&(f=s.sequenceNumber),!s.value?.isComposition||!s.value?.components)continue;let r=l.find(i=>i.id===s.elementId);if(r)for(let[i,o]of Object.entries(s.value.components)){let a=r.properties.find(d=>d.id===i);a&&(a.value=o.value,a.quality=o.quality,a.timestamp=o.timestamp,a.changed=!0)}}g=""}catch(e){g=String(e)}}function v(){let e=[],t=n.reset,s=new Date().toLocaleTimeString();e.push(""),e.push(` ${n.bgHeader}${n.white}${n.bold} \u{1F4E1} i3X Dashboard \u2014 ${P} ${t} ${n.dim}${s}${t}`),e.push("");let r=[];r.push(`${n.green}\u25CF Connected${t}`),r.push(`${n.dim}Updates: ${S}${t}`),r.push(`${n.dim}Changes: ${N}${t}`),r.push(`${n.dim}Seq: ${f}${t}`),e.push(` ${r.join(" \u2502 ")}`),e.push("");for(let i=0;i<l.length;i+=2){let o=x(l[i]),a=i+1<l.length?x(l[i+1]):null,d=Math.max(o.length,a?.length??0);for(let u=0;u<d;u++){let y=o[u]??" ".repeat(w),$=a?a[u]??" ".repeat(w):"";e.push(` ${y} ${$}`)}e.push("")}g&&e.push(` ${n.red}\u26A0 ${g}${t}`),e.push(` ${n.dim}Press Ctrl+C to stop${t}`),e.push(""),process.stdout.write(n.home+e.join(`
|
|
8
8
|
`))}function x(e){let t=[],s=n.reset,r=w;t.push(`${n.dim}${k(r)}${s}`);let i=`${e.icon} ${n.bold}${n.cyan}${e.name}${s}`;t.push(`${n.dim}${I(i,r)}${s}`),t.push(`${n.dim}${L(r)}${s}`);for(let o of e.properties){let a=U(o.name),{text:d,color:u}=B(o.value,o.name),$=`${o.changed?n.yellow:n.dim}${a.padEnd(18)}${s} ${u}${d}${s}`;t.push(`${n.dim}${I($,r)}${s}`)}return t.push(`${n.dim}${O(r)}${s}`),t}function U(e){return e.replace(" (\xB0C)"," \xB0C").replace(" (bar)"," bar").replace(" (L/min)"," L/min").replace(" (m/s)"," m/s").replace(" (%)"," %")}function B(e,t){if(typeof e=="boolean")return t.toLowerCase().includes("heater")?e?{text:"\u{1F525} ON",color:n.red}:{text:" OFF",color:n.gray}:e?{text:"\u25CF ON",color:n.green}:{text:"\u25CB OFF",color:n.gray};if(typeof e=="number"){let s=t.toLowerCase(),r=n.white;return s.includes("temperature")||s.includes("temp")?e>180?r=n.red:e>100?r=n.yellow:r=n.green:s.includes("pressure")?e>5.5?r=n.red:e>4.5?r=n.yellow:r=n.green:s.includes("power")&&(e>80?r=n.red:e>0?r=n.yellow:r=n.gray),{text:Number.isInteger(e)?e.toLocaleString():e.toFixed(2),color:r}}return{text:String(e??"\u2014"),color:n.white}}async function H(){try{await h("/health")}catch{console.error(`
|
|
9
9
|
\u274C Cannot reach i3X server at ${b}`),console.error(` Start the demo first:
|
|
10
10
|
npm run demo -w packages/demo-embedded
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"mappings":"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","names":["parseArgs","clientArgs","BASE_URL","BASE","ESC","ansi","get","path","res","post","body","nameById","cards","subId","lastSeq","updateCount","totalChanges","serverName","lastError","CARD_W","boxTop","w","boxMid","boxBot","boxRow","content","visible","pad","discover","roots","skipNames","userRoots","r","compositeIds","root","walkTree","card","obj","related","children","childAssets","c","childProps","icon","iconForAsset","child","name","n","readInitialValues","ids","values","entry","prop","vqt","createSubscription","syncUpdates","updates","u","propId","p","err","render","lines","now","statusParts","left","renderCard","right","maxLines","row","l","rr","title","label","cleanLabel","valText","color","formatPropValue","line","v","main","_compositeIds","sleep","cleanup","iteration","ms"]}
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{consoleLogger as
|
|
2
|
+
import{parseArgs as I}from"util";import{consoleLogger as A,HistoryService as U,ModelService as R,SubscriptionService as $,ValueService as x}from"@node-i3x/core";import{PseudoSessionDataSourceAdapter as E}from"@node-i3x/pseudo-session-connector";import{createApp as F}from"@node-i3x/rest-server";import{DataType as e,nodesets as B,OPCUAServer as j,Variant as n}from"node-opcua";var{values:f}=I({options:{"rest-port":{type:"string",default:"8080"},"opcua-port":{type:"string",default:"48410"},help:{type:"boolean",short:"h",default:!1}}});f.help&&(console.log(`
|
|
3
3
|
Usage: i3x-demo [options]
|
|
4
4
|
|
|
5
5
|
Options:
|
|
6
6
|
--rest-port <port> REST API port (default: 8080)
|
|
7
7
|
--opcua-port <port> OPC UA server port (default: 48410)
|
|
8
8
|
-h, --help Show this help
|
|
9
|
-
`),process.exit(0));var c=parseInt(f["rest-port"],10),T=parseInt(f["opcua-port"],10);function s(a,l,o){a.setValueFromSource(new n({dataType:l,value:o}))}async function X(){let a=new
|
|
9
|
+
`),process.exit(0));var c=parseInt(f["rest-port"],10),T=parseInt(f["opcua-port"],10);function s(a,l,o){a.setValueFromSource(new n({dataType:l,value:o}))}async function X(){let a=new j({port:T,resourcePath:"/UA/EmbeddedDemo",nodeset_filename:[B.standard],serverInfo:{applicationName:{text:"i3X Embedded Demo"}}});await a.initialize();let l=a.engine.addressSpace,o=l.getOwnNamespace(),t=o.addObject({organizedBy:l.rootFolder.objects,browseName:"SmartFactory",displayName:"Smart Factory"}),r=o.addObject({componentOf:t,browseName:"Pump",displayName:"Main Coolant Pump"}),S=o.addVariable({componentOf:r,browseName:"Temperature",displayName:"Temperature (\xB0C)",dataType:e.Double,value:new n({dataType:e.Double,value:35})}),g=o.addVariable({componentOf:r,browseName:"Pressure",displayName:"Pressure (bar)",dataType:e.Double,value:new n({dataType:e.Double,value:4.2})}),d=o.addVariable({componentOf:r,browseName:"FlowRate",displayName:"Flow Rate (L/min)",dataType:e.Double,value:new n({dataType:e.Double,value:120.5})}),i=o.addVariable({componentOf:r,browseName:"Running",displayName:"Running",dataType:e.Boolean,value:new n({dataType:e.Boolean,value:!0})}),p=o.addObject({componentOf:t,browseName:"Heater",displayName:"Process Heater"}),m=o.addVariable({componentOf:p,browseName:"HeaterOn",displayName:"Heater On/Off",dataType:e.Boolean,value:new n({dataType:e.Boolean,value:!0})}),P=o.addVariable({componentOf:p,browseName:"Temperature",displayName:"Temperature (\xB0C)",dataType:e.Double,value:new n({dataType:e.Double,value:180})}),z=o.addVariable({componentOf:p,browseName:"Setpoint",displayName:"Setpoint (\xB0C)",dataType:e.Double,value:new n({dataType:e.Double,value:200})}),V=o.addVariable({componentOf:p,browseName:"Power",displayName:"Power (%)",dataType:e.Double,value:new n({dataType:e.Double,value:85})}),O=o.addObject({componentOf:t,browseName:"Conveyor",displayName:"Assembly Conveyor"}),M=o.addVariable({componentOf:O,browseName:"Speed",displayName:"Speed (m/s)",dataType:e.Double,value:new n({dataType:e.Double,value:2.3})}),C=o.addVariable({componentOf:O,browseName:"ItemCount",displayName:"Items Processed",dataType:e.UInt32,value:new n({dataType:e.UInt32,value:8452})}),N=35,u=4.2,b=120.5,y=!0,w=180,h=85,v=2.3,D=8452;return setInterval(()=>{N+=(Math.random()-.48)*.5,u+=(Math.random()-.5)*.1,u=Math.max(3,Math.min(6,u)),b+=(Math.random()-.5)*2,b=Math.max(100,Math.min(140,b)),s(S,e.Double,N),s(g,e.Double,u),s(d,e.Double,b)},800),setInterval(()=>{y?(w+=(200-w)*.05+(Math.random()-.5)*.3,h=Math.max(0,Math.min(100,h+(Math.random()-.5)*5))):(w-=1.5+Math.random()*.5,h=0),s(P,e.Double,w),s(V,e.Double,h)},1e3),setInterval(()=>{y=!y,s(m,e.Boolean,y)},15e3),setInterval(()=>{D+=Math.floor(Math.random()*3),v+=(Math.random()-.5)*.1,v=Math.max(1.5,Math.min(3.5,v)),s(M,e.Double,v),s(C,e.UInt32,D)},1200),await a.start(),{server:a,addressSpace:l}}async function _(){let a=A;console.log(`
|
|
10
10
|
${"\u2550".repeat(60)}`),console.log(" \u{1F3ED} i3X Embedded Demo \u2014 PseudoSession Connector"),console.log("\u2550".repeat(60)),console.log(`
|
|
11
11
|
\u25B6 Starting OPC UA server...`);let{server:l,addressSpace:o}=await X();console.log(` \u2713 OPC UA binary endpoint at opc.tcp://localhost:${T}/UA/EmbeddedDemo`),console.log(`
|
|
12
|
-
\u25B6 Connecting i3X via PseudoSession...`);let t=new
|
|
13
|
-
\u25B6 Building i3X model from AddressSpace...`);let i=await r.preloadModel();console.log(` \u2713 ${i.nodesById.size} nodes, ${i.rootIds.length} roots, ${i.propertyToSource.size} properties`);let p=await
|
|
12
|
+
\u25B6 Connecting i3X via PseudoSession...`);let t=new E(o,a);await t.connect(),console.log(" \u2713 Connected \u2014 zero-network, in-process");let r=new R(t,a),S=new x(t,r,a),g=new U(t,r,a),d=new $(t,r,a,1);console.log(`
|
|
13
|
+
\u25B6 Building i3X model from AddressSpace...`);let i=await r.preloadModel();console.log(` \u2713 ${i.nodesById.size} nodes, ${i.rootIds.length} roots, ${i.propertyToSource.size} properties`);let p=await F({dataSource:t,modelService:r,valueService:S,historyService:g,subscriptionService:d,logger:a});await p.listen({port:c,host:"127.0.0.1"}),console.log(`
|
|
14
14
|
${"\u2550".repeat(60)}`),console.log(` \u{1F680} i3X REST API ready at http://127.0.0.1:${c}`),console.log("\u2550".repeat(60)),console.log(`
|
|
15
15
|
Try these:`),console.log(` curl http://localhost:${c}/health`),console.log(` curl http://localhost:${c}/v1/info`),console.log(` curl http://localhost:${c}/v1/namespaces`),console.log(` curl -X POST http://localhost:${c}/v1/objects/list`),console.log(`
|
|
16
16
|
OPC UA clients can also connect to opc.tcp://localhost:${T}/UA/EmbeddedDemo`),console.log(`
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"mappings":";AAQA,OACE,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"mappings":";AAQA,OAAS,aAAAA,MAAiB,OAC1B,OACE,iBAAAC,EACA,kBAAAC,EACA,gBAAAC,EACA,uBAAAC,EACA,gBAAAC,MACK,iBACP,OAAS,kCAAAC,MAAsC,qCAC/C,OAAS,aAAAC,MAAiB,wBAC1B,OAAS,YAAAC,EAAU,YAAAC,EAAU,eAAAC,EAA8B,WAAAC,MAAe,aAE1E,GAAM,CAAE,OAAQC,CAAK,EAAIZ,EAAU,CACjC,QAAS,CACP,YAAa,CAAE,KAAM,SAAU,QAAS,MAAO,EAC/C,aAAc,CAAE,KAAM,SAAU,QAAS,OAAQ,EACjD,KAAM,CAAE,KAAM,UAAW,MAAO,IAAK,QAAS,EAAM,CACtD,CACF,CAAC,EAEGY,EAAK,OACP,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAOb,EACC,QAAQ,KAAK,CAAC,GAGhB,IAAMC,EAAY,SAASD,EAAK,WAAW,EAAI,EAAE,EAC3CE,EAAa,SAASF,EAAK,YAAY,EAAI,EAAE,EAInD,SAASG,EAAOC,EAAeC,EAAoBC,EAAc,CAC/DF,EAAE,mBAAmB,IAAIL,EAAQ,CAAE,SAAAM,EAAU,MAAOC,CAAI,CAAC,CAAC,CAC5D,CAIA,eAAeC,GAAqB,CAClC,IAAMC,EAAS,IAAIV,EAAY,CAC7B,KAAMI,EACN,aAAc,mBACd,iBAAkB,CAACL,EAAS,QAAQ,EACpC,WAAY,CACV,gBAAiB,CAAE,KAAM,mBAAoB,CAC/C,CACF,CAAC,EAED,MAAMW,EAAO,WAAW,EACxB,IAAMC,EAAeD,EAAO,OAAO,aAC7BE,EAAKD,EAAa,gBAAgB,EAGlCE,EAAUD,EAAG,UAAU,CAC3B,YAAaD,EAAa,WAAW,QACrC,WAAY,eACZ,YAAa,eACf,CAAC,EAGKG,EAAOF,EAAG,UAAU,CACxB,YAAaC,EACb,WAAY,OACZ,YAAa,mBACf,CAAC,EAEKE,EAAcH,EAAG,YAAY,CACjC,YAAaE,EACb,WAAY,cACZ,YAAa,sBACb,SAAUhB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,EACT,CAAC,CACH,CAAC,EAEKkB,EAAeJ,EAAG,YAAY,CAClC,YAAaE,EACb,WAAY,WACZ,YAAa,iBACb,SAAUhB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,GACT,CAAC,CACH,CAAC,EAEKmB,EAAcL,EAAG,YAAY,CACjC,YAAaE,EACb,WAAY,WACZ,YAAa,oBACb,SAAUhB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,KACT,CAAC,CACH,CAAC,EAEKoB,EAAcN,EAAG,YAAY,CACjC,YAAaE,EACb,WAAY,UACZ,YAAa,UACb,SAAUhB,EAAS,QACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,QACnB,MAAO,EACT,CAAC,CACH,CAAC,EAGKqB,EAASP,EAAG,UAAU,CAC1B,YAAaC,EACb,WAAY,SACZ,YAAa,gBACf,CAAC,EAEKO,EAAcR,EAAG,YAAY,CACjC,YAAaO,EACb,WAAY,WACZ,YAAa,gBACb,SAAUrB,EAAS,QACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,QACnB,MAAO,EACT,CAAC,CACH,CAAC,EAEKuB,EAAgBT,EAAG,YAAY,CACnC,YAAaO,EACb,WAAY,cACZ,YAAa,sBACb,SAAUrB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,GACT,CAAC,CACH,CAAC,EAEKwB,EAAqBV,EAAG,YAAY,CACxC,YAAaO,EACb,WAAY,WACZ,YAAa,mBACb,SAAUrB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,GACT,CAAC,CACH,CAAC,EAEKyB,EAAiBX,EAAG,YAAY,CACpC,YAAaO,EACb,WAAY,QACZ,YAAa,YACb,SAAUrB,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,EACT,CAAC,CACH,CAAC,EAGK0B,EAAWZ,EAAG,UAAU,CAC5B,YAAaC,EACb,WAAY,WACZ,YAAa,mBACf,CAAC,EAEKY,EAAeb,EAAG,YAAY,CAClC,YAAaY,EACb,WAAY,QACZ,YAAa,cACb,SAAU1B,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,GACT,CAAC,CACH,CAAC,EAEK4B,EAAed,EAAG,YAAY,CAClC,YAAaY,EACb,WAAY,YACZ,YAAa,kBACb,SAAU1B,EAAS,OACnB,MAAO,IAAIG,EAAQ,CACjB,SAAUH,EAAS,OACnB,MAAO,IACT,CAAC,CACH,CAAC,EAKG6B,EAAW,GACXC,EAAe,IACfC,EAAe,MACfC,EAAW,GACXC,EAAa,IACbC,EAAc,GACdC,EAAY,IACZC,EAAY,KAGhB,mBAAY,IAAM,CAChBP,IAAa,KAAK,OAAO,EAAI,KAAQ,GACrCC,IAAiB,KAAK,OAAO,EAAI,IAAO,GACxCA,EAAe,KAAK,IAAI,EAAK,KAAK,IAAI,EAAKA,CAAY,CAAC,EACxDC,IAAiB,KAAK,OAAO,EAAI,IAAO,EACxCA,EAAe,KAAK,IAAI,IAAK,KAAK,IAAI,IAAKA,CAAY,CAAC,EAExDxB,EAAOU,EAAajB,EAAS,OAAQ6B,CAAQ,EAC7CtB,EAAOW,EAAclB,EAAS,OAAQ8B,CAAY,EAClDvB,EAAOY,EAAanB,EAAS,OAAQ+B,CAAY,CACnD,EAAG,GAAG,EAGN,YAAY,IAAM,CACZC,GACFC,IAAe,IAAQA,GAAc,KAAQ,KAAK,OAAO,EAAI,IAAO,GACpEC,EAAc,KAAK,IAAI,EAAG,KAAK,IAAI,IAAKA,GAAe,KAAK,OAAO,EAAI,IAAO,CAAC,CAAC,IAEhFD,GAAc,IAAM,KAAK,OAAO,EAAI,GACpCC,EAAc,GAEhB3B,EAAOgB,EAAevB,EAAS,OAAQiC,CAAU,EACjD1B,EAAOkB,EAAgBzB,EAAS,OAAQkC,CAAW,CACrD,EAAG,GAAI,EAGP,YAAY,IAAM,CAChBF,EAAW,CAACA,EACZzB,EAAOe,EAAatB,EAAS,QAASgC,CAAQ,CAChD,EAAG,IAAM,EAGT,YAAY,IAAM,CAChBI,GAAa,KAAK,MAAM,KAAK,OAAO,EAAI,CAAC,EACzCD,IAAc,KAAK,OAAO,EAAI,IAAO,GACrCA,EAAY,KAAK,IAAI,IAAK,KAAK,IAAI,IAAKA,CAAS,CAAC,EAElD5B,EAAOoB,EAAc3B,EAAS,OAAQmC,CAAS,EAC/C5B,EAAOqB,EAAc5B,EAAS,OAAQoC,CAAS,CACjD,EAAG,IAAI,EAEP,MAAMxB,EAAO,MAAM,EAEZ,CAAE,OAAAA,EAAQ,aAAAC,CAAa,CAChC,CAIA,eAAewB,GAAO,CACpB,IAAMC,EAAS7C,EAEf,QAAQ,IAAI;AAAA,EAAK,SAAI,OAAO,EAAE,CAAC,EAAE,EACjC,QAAQ,IAAI,8DAAkD,EAC9D,QAAQ,IAAI,SAAI,OAAO,EAAE,CAAC,EAG1B,QAAQ,IAAI;AAAA,iCAA+B,EAC3C,GAAM,CAAE,OAAAmB,EAAQ,aAAAC,CAAa,EAAI,MAAMF,EAAmB,EAC1D,QAAQ,IACN,0DACyBL,CAAU,kBACrC,EAMA,QAAQ,IAAI;AAAA,2CAAyC,EACrD,IAAMiC,EAAa,IAAIzC,EAA+Be,EAAcyB,CAAM,EAC1E,MAAMC,EAAW,QAAQ,EACzB,QAAQ,IAAI,oDAA0C,EAGtD,IAAMC,EAAe,IAAI7C,EAAa4C,EAAYD,CAAM,EAClDG,EAAe,IAAI5C,EAAa0C,EAAYC,EAAcF,CAAM,EAChEI,EAAiB,IAAIhD,EAAe6C,EAAYC,EAAcF,CAAM,EACpEK,EAAsB,IAAI/C,EAC9B2C,EACAC,EACAF,EACA,CACF,EAGA,QAAQ,IAAI;AAAA,+CAA6C,EACzD,IAAMM,EAAQ,MAAMJ,EAAa,aAAa,EAC9C,QAAQ,IACN,YAAOI,EAAM,UAAU,IAAI,WACtBA,EAAM,QAAQ,MAAM,WACpBA,EAAM,iBAAiB,IAAI,aAClC,EAGA,IAAMC,EAAM,MAAM9C,EAAU,CAC1B,WAAAwC,EACA,aAAAC,EACA,aAAAC,EACA,eAAAC,EACA,oBAAAC,EACA,OAAAL,CACF,CAAC,EACD,MAAMO,EAAI,OAAO,CAAE,KAAMxC,EAAW,KAAM,WAAY,CAAC,EAEvD,QAAQ,IAAI;AAAA,EAAK,SAAI,OAAO,EAAE,CAAC,EAAE,EACjC,QAAQ,IAAI,sDAA+CA,CAAS,EAAE,EACtE,QAAQ,IAAI,SAAI,OAAO,EAAE,CAAC,EAC1B,QAAQ,IAAI;AAAA,aAAgB,EAC5B,QAAQ,IAAI,6BAA6BA,CAAS,SAAS,EAC3D,QAAQ,IAAI,6BAA6BA,CAAS,UAAU,EAC5D,QAAQ,IAAI,6BAA6BA,CAAS,gBAAgB,EAClE,QAAQ,IAAI,qCAAqCA,CAAS,kBAAkB,EAC5E,QAAQ,IACN;AAAA,2DACyBC,CAAU,kBACrC,EACA,QAAQ,IAAI;AAAA;AAAA,CAA6B,EAGzC,IAAMwC,EAAW,SAAY,CAC3B,QAAQ,IAAI;AAAA;AAAA,iBAAsB,EAClC,MAAMD,EAAI,MAAM,EAChB,MAAMF,EAAoB,MAAM,EAChC,MAAMJ,EAAW,WAAW,EAC5B,MAAM3B,EAAO,SAAS,GAAG,EACzB,QAAQ,KAAK,CAAC,CAChB,EACA,QAAQ,GAAG,SAAUkC,CAAQ,EAC7B,QAAQ,GAAG,UAAWA,CAAQ,CAChC,CAEAT,EAAK,EAAE,MAAOU,GAAQ,CACpB,QAAQ,MAAM,SAAUA,CAAG,EAC3B,QAAQ,KAAK,CAAC,CAChB,CAAC","names":["parseArgs","consoleLogger","HistoryService","ModelService","SubscriptionService","ValueService","PseudoSessionDataSourceAdapter","createApp","DataType","nodesets","OPCUAServer","Variant","args","REST_PORT","OPCUA_PORT","setVar","v","dataType","val","createSampleServer","server","addressSpace","ns","factory","pump","pumpTempVar","pumpPressVar","pumpFlowVar","_pumpRunVar","heater","heaterOnVar","heaterTempVar","_heaterSetpointVar","heaterPowerVar","conveyor","convSpeedVar","itemCountVar","pumpTemp","pumpPressure","pumpFlowRate","heaterOn","heaterTemp","heaterPower","convSpeed","itemCount","main","logger","dataSource","modelService","valueService","historyService","subscriptionService","model","app","shutdown","err"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-i3x/demo-embedded",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"license": "AGPL-3.0-or-later OR LicenseRef-Sterfive-Commercial",
|
|
5
5
|
"author": "Sterfive SAS <contact@sterfive.com> (https://sterfive.com)",
|
|
6
6
|
"homepage": "https://sterfive.com",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"bin": {
|
|
17
|
-
"i3x-demo": "./dist/index.js"
|
|
17
|
+
"i3x-demo": "./dist/index.js",
|
|
18
|
+
"i3x-demo-client": "./dist/client.js"
|
|
18
19
|
},
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
@@ -24,6 +25,9 @@
|
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|
|
26
27
|
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
27
31
|
"scripts": {
|
|
28
32
|
"build": "tsup",
|
|
29
33
|
"start": "npx tsx src/index.ts",
|
|
@@ -32,10 +36,10 @@
|
|
|
32
36
|
"client": "npx tsx src/client.ts"
|
|
33
37
|
},
|
|
34
38
|
"dependencies": {
|
|
35
|
-
"@node-i3x/core": "
|
|
36
|
-
"@node-i3x/opcua-connector": "
|
|
37
|
-
"@node-i3x/pseudo-session-connector": "
|
|
38
|
-
"@node-i3x/rest-server": "
|
|
39
|
+
"@node-i3x/core": "0.2.3",
|
|
40
|
+
"@node-i3x/opcua-connector": "0.2.3",
|
|
41
|
+
"@node-i3x/pseudo-session-connector": "0.2.3",
|
|
42
|
+
"@node-i3x/rest-server": "0.2.3",
|
|
39
43
|
"fastify": "^5.0.0",
|
|
40
44
|
"@fastify/cors": "^11.0.0",
|
|
41
45
|
"fastify-plugin": "^5.0.0",
|
package/src/client.ts
DELETED
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// i3X REST Client — live dashboard with refreshing cards
|
|
3
|
-
//
|
|
4
|
-
// Run this AFTER the embedded demo is running:
|
|
5
|
-
// npm run demo -w packages/demo-embedded (terminal 1)
|
|
6
|
-
// npm run client -w packages/demo-embedded (terminal 2)
|
|
7
|
-
// ─────────────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
|
-
import { parseArgs } from 'node:util';
|
|
10
|
-
|
|
11
|
-
const { values: clientArgs } = parseArgs({
|
|
12
|
-
options: {
|
|
13
|
-
url: { type: 'string', default: process.env.I3X_URL ?? 'http://127.0.0.1:8080' },
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const BASE_URL = clientArgs.url!;
|
|
18
|
-
const BASE = BASE_URL;
|
|
19
|
-
|
|
20
|
-
// ── ANSI helpers ─────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
const ESC = '\x1b';
|
|
23
|
-
const ansi = {
|
|
24
|
-
clear: `${ESC}[2J${ESC}[H`,
|
|
25
|
-
home: `${ESC}[H`,
|
|
26
|
-
hideCursor: `${ESC}[?25l`,
|
|
27
|
-
showCursor: `${ESC}[?25h`,
|
|
28
|
-
bold: `${ESC}[1m`,
|
|
29
|
-
dim: `${ESC}[2m`,
|
|
30
|
-
reset: `${ESC}[0m`,
|
|
31
|
-
// Foreground
|
|
32
|
-
white: `${ESC}[97m`,
|
|
33
|
-
gray: `${ESC}[90m`,
|
|
34
|
-
cyan: `${ESC}[96m`,
|
|
35
|
-
green: `${ESC}[92m`,
|
|
36
|
-
red: `${ESC}[91m`,
|
|
37
|
-
yellow: `${ESC}[93m`,
|
|
38
|
-
blue: `${ESC}[94m`,
|
|
39
|
-
magenta: `${ESC}[95m`,
|
|
40
|
-
// Background
|
|
41
|
-
bgDark: `${ESC}[48;5;236m`,
|
|
42
|
-
bgCard: `${ESC}[48;5;238m`,
|
|
43
|
-
bgHeader: `${ESC}[48;5;24m`,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// ── Fetch helpers ────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
async function get<T>(path: string): Promise<T> {
|
|
49
|
-
const res = await fetch(`${BASE}${path}`);
|
|
50
|
-
if (!res.ok) throw new Error(`GET ${path} → ${res.status}`);
|
|
51
|
-
return res.json() as Promise<T>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function post<T>(path: string, body: unknown): Promise<T> {
|
|
55
|
-
const res = await fetch(`${BASE}${path}`, {
|
|
56
|
-
method: 'POST',
|
|
57
|
-
headers: { 'Content-Type': 'application/json' },
|
|
58
|
-
body: JSON.stringify(body),
|
|
59
|
-
});
|
|
60
|
-
if (!res.ok) throw new Error(`POST ${path} → ${res.status}`);
|
|
61
|
-
return res.json() as Promise<T>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── Types ────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
interface ObjectInstance {
|
|
67
|
-
elementId: string;
|
|
68
|
-
displayName: string;
|
|
69
|
-
parentId: string | null;
|
|
70
|
-
isComposition: boolean;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface RelatedResult {
|
|
74
|
-
sourceRelationship: string;
|
|
75
|
-
object: ObjectInstance;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
interface VQT {
|
|
79
|
-
value: unknown;
|
|
80
|
-
quality: string;
|
|
81
|
-
timestamp: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface ValueResult {
|
|
85
|
-
isComposition?: boolean;
|
|
86
|
-
components?: Record<string, VQT>;
|
|
87
|
-
value?: unknown;
|
|
88
|
-
quality?: string;
|
|
89
|
-
timestamp?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface SubscriptionUpdate {
|
|
93
|
-
sequenceNumber: number;
|
|
94
|
-
elementId: string;
|
|
95
|
-
value: ValueResult;
|
|
96
|
-
quality: string;
|
|
97
|
-
timestamp: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── State ────────────────────────────────────────────────────
|
|
101
|
-
|
|
102
|
-
interface AssetCard {
|
|
103
|
-
id: string;
|
|
104
|
-
name: string;
|
|
105
|
-
icon: string;
|
|
106
|
-
properties: PropertyEntry[];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface PropertyEntry {
|
|
110
|
-
id: string;
|
|
111
|
-
name: string;
|
|
112
|
-
value: unknown;
|
|
113
|
-
quality: string;
|
|
114
|
-
timestamp: string;
|
|
115
|
-
changed: boolean; // flash on recent change
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const nameById = new Map<string, string>();
|
|
119
|
-
const cards: AssetCard[] = [];
|
|
120
|
-
let subId = '';
|
|
121
|
-
let lastSeq = 0;
|
|
122
|
-
let updateCount = 0;
|
|
123
|
-
let totalChanges = 0;
|
|
124
|
-
let serverName = '';
|
|
125
|
-
let lastError = '';
|
|
126
|
-
|
|
127
|
-
// ── Card width ───────────────────────────────────────────────
|
|
128
|
-
|
|
129
|
-
const CARD_W = 44;
|
|
130
|
-
|
|
131
|
-
// ── Box drawing ──────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
function boxTop(w: number): string {
|
|
134
|
-
return `┌${'─'.repeat(w - 2)}┐`;
|
|
135
|
-
}
|
|
136
|
-
function boxMid(w: number): string {
|
|
137
|
-
return `├${'─'.repeat(w - 2)}┤`;
|
|
138
|
-
}
|
|
139
|
-
function boxBot(w: number): string {
|
|
140
|
-
return `└${'─'.repeat(w - 2)}┘`;
|
|
141
|
-
}
|
|
142
|
-
function boxRow(content: string, w: number): string {
|
|
143
|
-
// Strip ANSI for length calculation
|
|
144
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI escape stripping
|
|
145
|
-
const visible = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
146
|
-
const pad = Math.max(0, w - 4 - visible.length);
|
|
147
|
-
return `│ ${content}${' '.repeat(pad)} │`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── Discovery ────────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
async function discover(): Promise<string[]> {
|
|
153
|
-
process.stdout.write(ansi.clear);
|
|
154
|
-
process.stdout.write(ansi.hideCursor);
|
|
155
|
-
process.stdout.write(
|
|
156
|
-
`\n ${ansi.cyan}${ansi.bold}📡 Discovering i3X model...${ansi.reset}\n\n`,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const info = await get<{
|
|
160
|
-
result: { serverName: string; specVersion: string };
|
|
161
|
-
}>('/v1/info');
|
|
162
|
-
serverName = info.result.serverName;
|
|
163
|
-
|
|
164
|
-
// Root objects — skip OPC UA standard
|
|
165
|
-
const roots = await get<{
|
|
166
|
-
result: ObjectInstance[];
|
|
167
|
-
}>('/v1/objects?root=true');
|
|
168
|
-
const skipNames = ['Locations', 'Server', 'Aliases'];
|
|
169
|
-
const userRoots = roots.result.filter((r) => !skipNames.includes(r.displayName));
|
|
170
|
-
|
|
171
|
-
process.stdout.write(` Found ${userRoots.length} user-defined root(s)\n`);
|
|
172
|
-
|
|
173
|
-
// Walk tree for each root
|
|
174
|
-
const compositeIds: string[] = [];
|
|
175
|
-
for (const root of userRoots) {
|
|
176
|
-
await walkTree(root, compositeIds);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Build cards for leaf assets (ones with properties)
|
|
180
|
-
for (const card of cards) {
|
|
181
|
-
process.stdout.write(` 📦 ${card.name} ` + `(${card.properties.length} props)\n`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return compositeIds;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function walkTree(obj: ObjectInstance, compositeIds: string[]): Promise<void> {
|
|
188
|
-
nameById.set(obj.elementId, obj.displayName);
|
|
189
|
-
|
|
190
|
-
if (!obj.isComposition) return;
|
|
191
|
-
|
|
192
|
-
compositeIds.push(obj.id);
|
|
193
|
-
|
|
194
|
-
const related = await post<{
|
|
195
|
-
results: Array<{
|
|
196
|
-
success: boolean;
|
|
197
|
-
result: RelatedResult[];
|
|
198
|
-
}>;
|
|
199
|
-
}>('/v1/objects/related', {
|
|
200
|
-
elementIds: [obj.elementId],
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
if (!related.results[0]?.success) return;
|
|
204
|
-
|
|
205
|
-
const children = related.results[0]?.result.filter(
|
|
206
|
-
(r) => r.sourceRelationship === 'HasComponent',
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
// Separate assets vs properties
|
|
210
|
-
const childAssets = children.filter((c) => c.object.isComposition);
|
|
211
|
-
const childProps = children.filter((c) => !c.object.isComposition);
|
|
212
|
-
|
|
213
|
-
// If this node has properties, create a card
|
|
214
|
-
if (childProps.length > 0) {
|
|
215
|
-
const icon = iconForAsset(obj.displayName);
|
|
216
|
-
const card: AssetCard = {
|
|
217
|
-
id: obj.elementId,
|
|
218
|
-
name: obj.displayName,
|
|
219
|
-
icon,
|
|
220
|
-
properties: childProps.map((c) => {
|
|
221
|
-
nameById.set(c.object.elementId, c.object.displayName);
|
|
222
|
-
return {
|
|
223
|
-
id: c.object.elementId,
|
|
224
|
-
name: c.object.displayName,
|
|
225
|
-
value: '—',
|
|
226
|
-
quality: 'Unknown',
|
|
227
|
-
timestamp: '',
|
|
228
|
-
changed: false,
|
|
229
|
-
};
|
|
230
|
-
}),
|
|
231
|
-
};
|
|
232
|
-
cards.push(card);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Recurse into child assets
|
|
236
|
-
for (const child of childAssets) {
|
|
237
|
-
await walkTree(child.object, compositeIds);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function iconForAsset(name: string): string {
|
|
242
|
-
const n = name.toLowerCase();
|
|
243
|
-
if (n.includes('pump')) return '💧';
|
|
244
|
-
if (n.includes('heater')) return '🔥';
|
|
245
|
-
if (n.includes('conveyor')) return '🏭';
|
|
246
|
-
if (n.includes('factory')) return '🏗️';
|
|
247
|
-
return '📦';
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ── Read initial values ──────────────────────────────────────
|
|
251
|
-
|
|
252
|
-
async function readInitialValues(): Promise<void> {
|
|
253
|
-
const ids = cards.map((c) => c.id);
|
|
254
|
-
if (ids.length === 0) return;
|
|
255
|
-
|
|
256
|
-
const values = await post<{
|
|
257
|
-
results: Array<{
|
|
258
|
-
success: boolean;
|
|
259
|
-
elementId: string;
|
|
260
|
-
result: ValueResult;
|
|
261
|
-
}>;
|
|
262
|
-
}>('/v1/objects/value', {
|
|
263
|
-
elementIds: ids,
|
|
264
|
-
maxDepth: 3,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
for (const entry of values.results) {
|
|
268
|
-
if (!entry.success) continue;
|
|
269
|
-
if (!entry.result.isComposition) continue;
|
|
270
|
-
if (!entry.result.components) continue;
|
|
271
|
-
|
|
272
|
-
const card = cards.find((c) => c.id === entry.elementId);
|
|
273
|
-
if (!card) continue;
|
|
274
|
-
|
|
275
|
-
for (const prop of card.properties) {
|
|
276
|
-
const vqt = entry.result.components[prop.id];
|
|
277
|
-
if (vqt) {
|
|
278
|
-
prop.value = vqt.value;
|
|
279
|
-
prop.quality = vqt.quality;
|
|
280
|
-
prop.timestamp = vqt.timestamp;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ── Create subscription ──────────────────────────────────────
|
|
287
|
-
|
|
288
|
-
async function createSubscription(): Promise<void> {
|
|
289
|
-
const ids = cards.map((c) => c.id);
|
|
290
|
-
if (ids.length === 0) return;
|
|
291
|
-
|
|
292
|
-
const createRes = await post<{
|
|
293
|
-
result: { subscriptionId: string };
|
|
294
|
-
}>('/v1/subscriptions', {
|
|
295
|
-
clientId: 'dashboard-client',
|
|
296
|
-
displayName: 'Dashboard Monitor',
|
|
297
|
-
});
|
|
298
|
-
subId = createRes.result.subscriptionId;
|
|
299
|
-
|
|
300
|
-
await post('/v1/subscriptions/register', {
|
|
301
|
-
subscriptionId: subId,
|
|
302
|
-
elementIds: ids,
|
|
303
|
-
maxDepth: 3,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ── Sync subscription updates ────────────────────────────────
|
|
308
|
-
|
|
309
|
-
async function syncUpdates(): Promise<void> {
|
|
310
|
-
try {
|
|
311
|
-
const syncRes = await post<{
|
|
312
|
-
result: SubscriptionUpdate[];
|
|
313
|
-
}>('/v1/subscriptions/sync', {
|
|
314
|
-
subscriptionId: subId,
|
|
315
|
-
acknowledgeSequence: lastSeq,
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const updates = syncRes.result;
|
|
319
|
-
if (!updates || updates.length === 0) return;
|
|
320
|
-
|
|
321
|
-
totalChanges += updates.length;
|
|
322
|
-
updateCount++;
|
|
323
|
-
|
|
324
|
-
// Clear all flash states
|
|
325
|
-
for (const card of cards) {
|
|
326
|
-
for (const prop of card.properties) {
|
|
327
|
-
prop.changed = false;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
for (const u of updates) {
|
|
332
|
-
if (u.sequenceNumber > lastSeq) {
|
|
333
|
-
lastSeq = u.sequenceNumber;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (!u.value?.isComposition || !u.value?.components) {
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const card = cards.find((c) => c.id === u.elementId);
|
|
341
|
-
if (!card) continue;
|
|
342
|
-
|
|
343
|
-
for (const [propId, vqt] of Object.entries(u.value.components)) {
|
|
344
|
-
const prop = card.properties.find((p) => p.id === propId);
|
|
345
|
-
if (prop) {
|
|
346
|
-
prop.value = vqt.value;
|
|
347
|
-
prop.quality = vqt.quality;
|
|
348
|
-
prop.timestamp = vqt.timestamp;
|
|
349
|
-
prop.changed = true;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
lastError = '';
|
|
354
|
-
} catch (err) {
|
|
355
|
-
lastError = String(err);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ── Render ───────────────────────────────────────────────────
|
|
360
|
-
|
|
361
|
-
function render(): void {
|
|
362
|
-
const lines: string[] = [];
|
|
363
|
-
const r = ansi.reset;
|
|
364
|
-
const now = new Date().toLocaleTimeString();
|
|
365
|
-
|
|
366
|
-
// Header bar
|
|
367
|
-
lines.push('');
|
|
368
|
-
lines.push(
|
|
369
|
-
` ${ansi.bgHeader}${ansi.white}${ansi.bold}` +
|
|
370
|
-
` 📡 i3X Dashboard — ${serverName} ` +
|
|
371
|
-
`${r}` +
|
|
372
|
-
` ${ansi.dim}${now}${r}`,
|
|
373
|
-
);
|
|
374
|
-
lines.push('');
|
|
375
|
-
|
|
376
|
-
// Status line
|
|
377
|
-
const statusParts: string[] = [];
|
|
378
|
-
statusParts.push(`${ansi.green}● Connected${r}`);
|
|
379
|
-
statusParts.push(`${ansi.dim}Updates: ${updateCount}${r}`);
|
|
380
|
-
statusParts.push(`${ansi.dim}Changes: ${totalChanges}${r}`);
|
|
381
|
-
statusParts.push(`${ansi.dim}Seq: ${lastSeq}${r}`);
|
|
382
|
-
lines.push(` ${statusParts.join(' │ ')}`);
|
|
383
|
-
lines.push('');
|
|
384
|
-
|
|
385
|
-
// Render cards in pairs (2 per row)
|
|
386
|
-
for (let i = 0; i < cards.length; i += 2) {
|
|
387
|
-
const left = renderCard(cards[i]!);
|
|
388
|
-
const right = i + 1 < cards.length ? renderCard(cards[i + 1]!) : null;
|
|
389
|
-
|
|
390
|
-
const maxLines = Math.max(left.length, right?.length ?? 0);
|
|
391
|
-
|
|
392
|
-
for (let row = 0; row < maxLines; row++) {
|
|
393
|
-
const l = left[row] ?? ' '.repeat(CARD_W);
|
|
394
|
-
const rr = right ? (right[row] ?? ' '.repeat(CARD_W)) : '';
|
|
395
|
-
lines.push(` ${l} ${rr}`);
|
|
396
|
-
}
|
|
397
|
-
lines.push('');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Error line
|
|
401
|
-
if (lastError) {
|
|
402
|
-
lines.push(` ${ansi.red}⚠ ${lastError}${r}`);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Footer
|
|
406
|
-
lines.push(` ${ansi.dim}Press Ctrl+C to stop${r}`);
|
|
407
|
-
lines.push('');
|
|
408
|
-
|
|
409
|
-
// Write in one shot — move cursor home, overwrite
|
|
410
|
-
process.stdout.write(ansi.home + lines.join('\n'));
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function renderCard(card: AssetCard): string[] {
|
|
414
|
-
const lines: string[] = [];
|
|
415
|
-
const r = ansi.reset;
|
|
416
|
-
const w = CARD_W;
|
|
417
|
-
|
|
418
|
-
// Top border
|
|
419
|
-
lines.push(`${ansi.dim}${boxTop(w)}${r}`);
|
|
420
|
-
|
|
421
|
-
// Title
|
|
422
|
-
const title = `${card.icon} ${ansi.bold}${ansi.cyan}` + `${card.name}${r}`;
|
|
423
|
-
lines.push(`${ansi.dim}${boxRow(title, w)}${r}`);
|
|
424
|
-
lines.push(`${ansi.dim}${boxMid(w)}${r}`);
|
|
425
|
-
|
|
426
|
-
// Properties
|
|
427
|
-
for (const prop of card.properties) {
|
|
428
|
-
const label = cleanLabel(prop.name);
|
|
429
|
-
const { text: valText, color } = formatPropValue(prop.value, prop.name);
|
|
430
|
-
|
|
431
|
-
const flashColor = prop.changed ? ansi.yellow : ansi.dim;
|
|
432
|
-
|
|
433
|
-
const line = `${flashColor}${label.padEnd(18)}${r} ` + `${color}${valText}${r}`;
|
|
434
|
-
|
|
435
|
-
lines.push(`${ansi.dim}${boxRow(line, w)}${r}`);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Bottom border
|
|
439
|
-
lines.push(`${ansi.dim}${boxBot(w)}${r}`);
|
|
440
|
-
|
|
441
|
-
return lines;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function cleanLabel(name: string): string {
|
|
445
|
-
// Shorten common suffixes for compact display
|
|
446
|
-
return name
|
|
447
|
-
.replace(' (°C)', ' °C')
|
|
448
|
-
.replace(' (bar)', ' bar')
|
|
449
|
-
.replace(' (L/min)', ' L/min')
|
|
450
|
-
.replace(' (m/s)', ' m/s')
|
|
451
|
-
.replace(' (%)', ' %');
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function formatPropValue(v: unknown, name: string): { text: string; color: string } {
|
|
455
|
-
if (typeof v === 'boolean') {
|
|
456
|
-
if (name.toLowerCase().includes('heater')) {
|
|
457
|
-
return v
|
|
458
|
-
? { text: '🔥 ON', color: ansi.red }
|
|
459
|
-
: { text: ' OFF', color: ansi.gray };
|
|
460
|
-
}
|
|
461
|
-
return v ? { text: '● ON', color: ansi.green } : { text: '○ OFF', color: ansi.gray };
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (typeof v === 'number') {
|
|
465
|
-
const n = name.toLowerCase();
|
|
466
|
-
let color = ansi.white;
|
|
467
|
-
|
|
468
|
-
// Color-code by range
|
|
469
|
-
if (n.includes('temperature') || n.includes('temp')) {
|
|
470
|
-
if (v > 180) color = ansi.red;
|
|
471
|
-
else if (v > 100) color = ansi.yellow;
|
|
472
|
-
else color = ansi.green;
|
|
473
|
-
} else if (n.includes('pressure')) {
|
|
474
|
-
if (v > 5.5) color = ansi.red;
|
|
475
|
-
else if (v > 4.5) color = ansi.yellow;
|
|
476
|
-
else color = ansi.green;
|
|
477
|
-
} else if (n.includes('power')) {
|
|
478
|
-
if (v > 80) color = ansi.red;
|
|
479
|
-
else if (v > 0) color = ansi.yellow;
|
|
480
|
-
else color = ansi.gray;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const formatted = Number.isInteger(v) ? v.toLocaleString() : v.toFixed(2);
|
|
484
|
-
return { text: formatted, color };
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
text: String(v ?? '—'),
|
|
489
|
-
color: ansi.white,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// ── Main ─────────────────────────────────────────────────────
|
|
494
|
-
|
|
495
|
-
async function main() {
|
|
496
|
-
// Check server
|
|
497
|
-
try {
|
|
498
|
-
await get('/health');
|
|
499
|
-
} catch {
|
|
500
|
-
console.error(`\n ❌ Cannot reach i3X server at ${BASE}`);
|
|
501
|
-
console.error(
|
|
502
|
-
' Start the demo first:\n' + ' npm run demo -w packages/demo-embedded\n',
|
|
503
|
-
);
|
|
504
|
-
process.exit(1);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Discovery phase (scrolling output)
|
|
508
|
-
const _compositeIds = await discover();
|
|
509
|
-
|
|
510
|
-
// Initial values
|
|
511
|
-
process.stdout.write(`\n Reading initial values...\n`);
|
|
512
|
-
await readInitialValues();
|
|
513
|
-
|
|
514
|
-
// Subscription
|
|
515
|
-
process.stdout.write(` Creating subscription...\n`);
|
|
516
|
-
await createSubscription();
|
|
517
|
-
process.stdout.write(` ✅ Monitoring ${cards.length} assets\n\n`);
|
|
518
|
-
|
|
519
|
-
await sleep(1000);
|
|
520
|
-
|
|
521
|
-
// Clear and enter dashboard mode
|
|
522
|
-
process.stdout.write(ansi.clear);
|
|
523
|
-
process.stdout.write(ansi.hideCursor);
|
|
524
|
-
|
|
525
|
-
// Graceful shutdown
|
|
526
|
-
const cleanup = async () => {
|
|
527
|
-
process.stdout.write(ansi.showCursor);
|
|
528
|
-
process.stdout.write('\n\n');
|
|
529
|
-
if (subId) {
|
|
530
|
-
try {
|
|
531
|
-
await post('/v1/subscriptions/delete', {
|
|
532
|
-
subscriptionIds: [subId],
|
|
533
|
-
});
|
|
534
|
-
} catch {
|
|
535
|
-
/* ignore */
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
console.log(' Subscription cleaned up. Bye!\n');
|
|
539
|
-
process.exit(0);
|
|
540
|
-
};
|
|
541
|
-
process.on('SIGINT', () => {
|
|
542
|
-
void cleanup();
|
|
543
|
-
});
|
|
544
|
-
process.on('SIGTERM', () => {
|
|
545
|
-
void cleanup();
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
// Render loop
|
|
549
|
-
render();
|
|
550
|
-
let iteration = 0;
|
|
551
|
-
while (iteration < 600) {
|
|
552
|
-
// max ~20 minutes
|
|
553
|
-
await sleep(2000);
|
|
554
|
-
iteration++;
|
|
555
|
-
await syncUpdates();
|
|
556
|
-
render();
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
await cleanup();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function sleep(ms: number): Promise<void> {
|
|
563
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
main().catch((err) => {
|
|
567
|
-
process.stdout.write(ansi.showCursor);
|
|
568
|
-
console.error('Fatal:', err);
|
|
569
|
-
process.exit(1);
|
|
570
|
-
});
|
package/src/demo-remote.ts
DELETED
|
@@ -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
|
-
]);
|