Skip to content

Commit 3582c49

Browse files
committed
chore: revisit opcua binding examples
1 parent 8dc6ed8 commit 3582c49

File tree

5 files changed

+364
-36
lines changed

5 files changed

+364
-36
lines changed

packages/examples/package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,18 @@
1717
"build": "tsc -b",
1818
"lint": "eslint .",
1919
"lint:fix": "eslint . --fix",
20-
"format": "prettier --write \"src/**/*.ts\" \"**/*.json\""
20+
"format": "prettier --write \"src/**/*.ts\" \"**/*.json\"",
21+
"demo:coap:server": "node dist/bindings/coap/example-server.js",
22+
"demo:coap:client": "node dist/bindings/coap/example-client.js",
23+
"demo:http:server": "node dist/bindings/http/example-server.js",
24+
"demo:http:server-secure": "node dist/bindings/http/example-server-secure.js",
25+
"demo:http:client": "node dist/bindings/http/example-client.js",
26+
"demo:opcua:1": "node dist/bindings/opcua/opcua-demo1.js",
27+
"demo:opcua:2": "node dist/bindings/opcua/opcua-demo2.js",
28+
"demo:opcua:coffee-machine": "node dist/bindings/opcua/opcua-coffee-machine-demo.js",
29+
"quickstart:smart-clock": "node dist/quickstart/smart-clock.js",
30+
"quickstart:simple-coffee-machine": "node dist/quickstart/simple-coffee-machine.js",
31+
"quickstart:presence-sensor": "node dist/quickstart/presence-sensor.js"
2132
},
2233
"bugs": {
2334
"url": "https://github.com/eclipse-thingweb/node-wot/issues"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Binding Examples
2+
3+
This folder contains examples for different binding protocols.
4+
5+
It demonstrates how to create Things that take their properties, actions, and events from different protocol bindings.
6+
7+
For each use case a Thing Description is provided that describes the Thing in a protocol-agnostic way.
8+
Then a Servient is created that uses the respective binding protocol to expose the Thing.
9+
A console client is also provided to interact with the Thing.
10+
11+
Examples are located in
12+
13+
- `bindings\coap`
14+
- `bindings\http`
15+
- `bindings\opcua`
16+
17+
## OPCUA
18+
19+
For inializing an OPCUA client Servient, we need to import the `OPCUAClientFactory` from the `@node-wot/binding-opcua` package.
20+
21+
```typescript
22+
const servient = new Servient();
23+
servient.addClientFactory(new OPCUAClientFactory());
24+
const wot = await servient.start();
25+
const thing = await wot.consume(thingDescription);
26+
```
27+
28+
Then we can interact with the Thing as usual:
29+
30+
```typescript
31+
// now interact with the things
32+
await thing.invokeAction(...);
33+
await thing.readProperty(...);
34+
await thing.subscribeEvent(...);
35+
36+
```
37+
38+
Finally, we can shutdown the servient:
39+
40+
```typescript
41+
await servient.shutdown();
42+
```

packages/examples/src/bindings/opcua/demo-opcua-thing-description.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const thingDescription: WoT.ThingDescription = {
2525
security: "nosec_sc",
2626
title: "servient",
2727
description: "node-wot CLI Servient",
28+
base: endpointUrl,
2829
properties: {
2930
pumpSpeed: {
3031
description: "the pump speed",
@@ -34,7 +35,7 @@ export const thingDescription: WoT.ThingDescription = {
3435
type: "number",
3536
forms: [
3637
{
37-
href: endpointUrl + "?id=ns=1;s=PumpSpeed",
38+
href: "?id=ns=1;s=PumpSpeed",
3839
op: ["readproperty", "observeproperty"],
3940
},
4041
],
@@ -47,7 +48,7 @@ export const thingDescription: WoT.ThingDescription = {
4748
type: "number",
4849
forms: [
4950
{
50-
href: endpointUrl + "?id=ns=1;s=Temperature",
51+
href: "?id=ns=1;s=Temperature",
5152
op: ["readproperty", "observeproperty"],
5253
},
5354
],

packages/examples/src/bindings/opcua/opcua-coffee-machine-demo.ts

Lines changed: 169 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
********************************************************************************/
1515

1616
/* eslint no-console: "off" */
17-
17+
import util from "util";
1818
import { Servient } from "@node-wot/core";
1919
import { OPCUAClientFactory } from "@node-wot/binding-opcua";
2020
import { thingDescription } from "./opcua-coffee-machine-thing-description";
@@ -25,37 +25,192 @@ const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve,
2525
servient.addClientFactory(new OPCUAClientFactory());
2626

2727
const wot = await servient.start();
28+
2829
const thing = await wot.consume(thingDescription);
2930

31+
let lastTemperature = NaN;
32+
let lastWaterTankLevel = NaN;
33+
let lastCoffeeBeanLevel = NaN;
34+
let lastCurrentState = NaN;
35+
let lastGrindingDuration = NaN;
36+
let lastGrinderStatus = NaN;
37+
let lastHeaterStatus = NaN;
38+
let lastPumpStatus = NaN;
39+
let lastValveStatus = NaN;
40+
41+
const recordedActions: string[] = [];
42+
const recordAction = (actionName: string) => {
43+
recordedActions.push(`${new Date().toISOString()} - ${actionName}`);
44+
};
45+
process.stdout.write("\x1Bc"); // clear console
46+
process.stdout.write("\x1B[?25l"); // hide cursor
47+
const currentStateEnum = ["Off", "Standby", "Error", "Cleaning", "Serving Coffee", "Under Maintenance"];
48+
const grinderStates = ["Off", "On", "Jammed", "Malfunctioning"];
49+
const heaterStates = ["Off", "Heating", "Ready", "Malfunctioning"];
50+
const pumpStates = ["Off", "On", "Malfunctioning"];
51+
const valveStates = ["Open", "Opening", "Close", "Closing", "Malfunctioning"];
52+
53+
const waitingMachineCoffeeStandByState = async () => {
54+
await pause(1000);
55+
let state = lastCurrentState;
56+
while (state !== 1) {
57+
// Standby
58+
await pause(1000);
59+
state = lastCurrentState;
60+
}
61+
};
62+
const writeLine = (...args: unknown[]) => {
63+
process.stdout.write(util.format(...args) + " \n");
64+
};
65+
const displayOnlineStatus = () => {
66+
process.stdout.write("\x1B[1;1H"); // move cursor to top left
67+
writeLine(`======== Coffee Machine Status ======== ${new Date().toISOString()}`);
68+
writeLine(
69+
` 🔄 Current State : ${
70+
isNaN(lastCurrentState) ? "n/a" : (currentStateEnum[lastCurrentState] ?? lastCurrentState)
71+
}`
72+
);
73+
writeLine(
74+
` 🔥 Heater Status : ${
75+
isNaN(lastHeaterStatus) ? "n/a" : (heaterStates[lastHeaterStatus] ?? lastHeaterStatus)
76+
}`
77+
);
78+
writeLine(
79+
` 🌡️ Boiler Temperature : ${isNaN(lastTemperature) ? "n/a" : lastTemperature.toFixed(2) + " °C"}`
80+
);
81+
writeLine(
82+
` 🚰 Pump Status : ${
83+
isNaN(lastPumpStatus) ? "n/a" : (pumpStates[lastPumpStatus] ?? lastPumpStatus)
84+
}`
85+
);
86+
writeLine(
87+
` 🚪 Valve Status : ${
88+
isNaN(lastValveStatus) ? "n/a" : (valveStates[lastValveStatus] ?? lastValveStatus)
89+
}`
90+
);
91+
writeLine(
92+
` 💧 Water Tank Level : ${isNaN(lastWaterTankLevel) ? "n/a" : lastWaterTankLevel.toFixed(2) + " ml"}`
93+
);
94+
writeLine(
95+
` ⚙️ Grinder Status : ${
96+
isNaN(lastGrinderStatus) ? "n/a" : (grinderStates[lastGrinderStatus] ?? lastGrinderStatus)
97+
}`
98+
);
99+
writeLine(
100+
` ⏱️ Grinding Duration : ${
101+
isNaN(lastGrindingDuration) ? "n/a" : lastGrindingDuration.toFixed(2) + " s"
102+
}`
103+
);
104+
writeLine(
105+
` ☕ Coffee Bean Level : ${isNaN(lastCoffeeBeanLevel) ? "n/a" : lastCoffeeBeanLevel.toFixed(2) + " g"}`
106+
);
107+
writeLine("========================================");
108+
writeLine("---- Recorded Actions (last 5) ----");
109+
recordedActions
110+
.slice(-5)
111+
.forEach((action) => writeLine(action + " "));
112+
writeLine("-----------------------------------");
113+
};
30114
try {
31115
thing
32116
.observeProperty("waterTankLevel", async (data) => {
33-
const waterTankLevel = await data.value();
34-
console.log("------------------------------");
35-
console.log("tankLevel : ", waterTankLevel, "ml");
36-
console.log("------------------------------");
117+
lastWaterTankLevel = (await data.value()) as number;
118+
displayOnlineStatus();
37119
})
38120
.catch((err) => {
39121
console.error("Error observing waterTankLevel property:", err);
40122
});
41123
thing
42124
.observeProperty("coffeeBeanLevel", async (data) => {
43-
const coffeBeanLevel = await data.value();
44-
console.log("------------------------------");
45-
console.log("bean level : ", coffeBeanLevel, "g");
46-
console.log("------------------------------");
125+
lastCoffeeBeanLevel = (await data.value()) as number;
126+
displayOnlineStatus();
47127
})
48128
.catch((err) => {
49129
console.error("Error observing coffeeBeanLevel property:", err);
50130
});
131+
thing
132+
.observeProperty("temperature", async (data) => {
133+
lastTemperature = (await data.value()) as number;
134+
displayOnlineStatus();
135+
})
136+
.catch((err) => {
137+
console.error("Error observing temperature property:", err);
138+
});
139+
thing
140+
.observeProperty("currentState", async (data) => {
141+
lastCurrentState = (await data.value()) as number;
142+
displayOnlineStatus();
143+
})
144+
.catch((err) => {
145+
console.error("Error observing currentState property:", err);
146+
});
147+
thing
148+
.observeProperty("grinderStatus", async (data) => {
149+
lastGrinderStatus = (await data.value()) as number;
150+
displayOnlineStatus();
151+
})
152+
.catch((err) => {
153+
console.error("Error observing grinderStatus property:", err);
154+
});
155+
thing
156+
.observeProperty("grindingDuration", async (data) => {
157+
lastGrindingDuration = (await data.value()) as number;
158+
displayOnlineStatus();
159+
})
160+
.catch((err) => {
161+
console.error("Error observing grindingDuration property:", err);
162+
});
163+
thing
164+
.observeProperty("heaterStatus", async (data) => {
165+
lastHeaterStatus = (await data.value()) as number;
166+
displayOnlineStatus();
167+
})
168+
.catch((err) => {
169+
console.error("Error observing heaterStatus property:", err);
170+
});
171+
thing
172+
.observeProperty("pumpStatus", async (data) => {
173+
lastPumpStatus = (await data.value()) as number;
174+
displayOnlineStatus();
175+
})
176+
.catch((err) => {
177+
console.error("Error observing pumpStatus property:", err);
178+
});
179+
thing
180+
.observeProperty("valveStatus", async (data) => {
181+
lastValveStatus = (await data.value()) as number;
182+
displayOnlineStatus();
183+
})
184+
.catch((err) => {
185+
console.error("Error observing valveStatus property:", err);
186+
});
187+
188+
// give some time to gather initial values
189+
await pause(2000);
190+
await waitingMachineCoffeeStandByState();
191+
recordAction("Machine is ready !");
192+
193+
await pause(10000);
194+
195+
recordAction("Invoking brewCoffee(Mocha) action...");
196+
await thing.invokeAction("brewCoffee", { RecipeName: "Mocha" });
197+
await waitingMachineCoffeeStandByState();
198+
recordAction("Coffee is ready !");
199+
200+
await pause(10000);
201+
202+
recordAction("Invoking brewCoffee(Americano) action...");
203+
await thing.invokeAction("brewCoffee", { RecipeName: "Americano" });
204+
await waitingMachineCoffeeStandByState();
205+
recordAction("Coffee is ready !");
51206

52-
await thing.invokeAction("brewCoffee", { CoffeeType: 1 });
53-
await pause(5000);
54-
await thing.invokeAction("brewCoffee", { CoffeeType: 0 });
55-
await pause(5000);
207+
await pause(10000);
56208

209+
recordAction("Invoking fillTank action...");
57210
await thing.invokeAction("fillTank");
58-
await pause(5000);
211+
await waitingMachineCoffeeStandByState();
212+
recordAction("Tank is refilled !");
213+
recordAction("Done !");
59214
} finally {
60215
await servient.shutdown();
61216
}

0 commit comments

Comments
 (0)