diff --git a/README.md b/README.md index 68ffea1d..576d8382 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ **Key features:** Topics, Services, Actions, Parameters, Lifecycle Nodes, TypeScript support, RxJS Observables, Electron integration, ROS 2 in the browser (typed Web SDK + thin WebSocket gateway — `rclnodejs/web`, `rosocket`), and prebuilt binaries for Linux x64/arm64. ```javascript -const rclnodejs = require('rclnodejs'); -rclnodejs.init().then(() => { - const node = new rclnodejs.Node('publisher_example_node'); - const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); - publisher.publish(`Hello ROS 2 from rclnodejs`); - node.spin(); -}); +import rclnodejs from 'rclnodejs'; + +await rclnodejs.init(); +const node = new rclnodejs.Node('publisher_example_node'); +const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); +publisher.publish(`Hello ROS 2 from rclnodejs`); +node.spin(); ``` This example assumes your ROS 2 environment is already sourced. @@ -105,7 +105,7 @@ npm install 2. Run a publisher example from this checkout. ```bash -node example/topics/publisher/publisher-example.cjs +node example/topics/publisher/publisher-example.mjs ``` More runnable examples in [example/](https://github.com/RobotWebTools/rclnodejs/tree/develop/example) and step-by-step guides in [tutorials/](./tutorials/). @@ -146,7 +146,7 @@ how much glue you want to write. rclnodejs supports [RxJS](https://rxjs.dev/) Observable subscriptions for reactive programming with ROS 2 messages. Use operators like `throttleTime()`, `debounceTime()`, `map()`, and `combineLatest()` to build declarative message processing pipelines. ```javascript -const { throttleTime, map } = require('rxjs'); +import { throttleTime, map } from 'rxjs'; const obsSub = node.createObservableSubscription( 'sensor_msgs/msg/LaserScan', @@ -173,7 +173,7 @@ rclnodejs auto-generates JavaScript bindings and TypeScript declarations for eve Use the generated types directly: ```javascript -const rclnodejs = require('rclnodejs'); +import rclnodejs from 'rclnodejs'; let stringMsgObject = rclnodejs.createMessageObject('std_msgs/msg/String'); stringMsgObject.data = 'hello world'; ``` @@ -199,9 +199,9 @@ TypeScript declaration files are included in the package and exposed through the ```jsonc { "compilerOptions": { - "module": "commonjs", - "moduleResolution": "node", - "target": "es2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "es2022", }, } ``` diff --git a/benchmark/rclnodejs/service/client-stress-test.js b/benchmark/rclnodejs/service/client-stress-test.js index ec6006c7..65e68e1c 100644 --- a/benchmark/rclnodejs/service/client-stress-test.js +++ b/benchmark/rclnodejs/service/client-stress-test.js @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - /* eslint-disable camelcase */ -const { program } = require('commander'); -const rclnodejs = require('../../../index.js'); +import { program } from 'commander'; +import rclnodejs from '../../../index.js'; program .option('-r, --run ', 'How many times to run', '1') diff --git a/benchmark/rclnodejs/service/service-stress-test.js b/benchmark/rclnodejs/service/service-stress-test.js index ab96c9db..46481369 100644 --- a/benchmark/rclnodejs/service/service-stress-test.js +++ b/benchmark/rclnodejs/service/service-stress-test.js @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - /* eslint-disable camelcase */ -const { program } = require('commander'); -const rclnodejs = require('../../../index.js'); +import { program } from 'commander'; +import rclnodejs from '../../../index.js'; program .option('-s, --size ', 'The block size in KB', '1') diff --git a/benchmark/rclnodejs/topic/publisher-stress-test.js b/benchmark/rclnodejs/topic/publisher-stress-test.js index f7b839d5..9835950e 100644 --- a/benchmark/rclnodejs/topic/publisher-stress-test.js +++ b/benchmark/rclnodejs/topic/publisher-stress-test.js @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - /* eslint-disable camelcase */ -const { program } = require('commander'); -const rclnodejs = require('../../../index.js'); +import { program } from 'commander'; +import rclnodejs from '../../../index.js'; program .option('-s, --size ', 'The block size', '1') diff --git a/benchmark/rclnodejs/topic/subscription-stress-test.js b/benchmark/rclnodejs/topic/subscription-stress-test.js index ac514b8b..db382c38 100644 --- a/benchmark/rclnodejs/topic/subscription-stress-test.js +++ b/benchmark/rclnodejs/topic/subscription-stress-test.js @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - -const rclnodejs = require('../../../index.js'); +import rclnodejs from '../../../index.js'; async function main() { try { diff --git a/demo/rosocket/README.md b/demo/rosocket/README.md index 28709c61..5d957ef5 100644 --- a/demo/rosocket/README.md +++ b/demo/rosocket/README.md @@ -11,12 +11,12 @@ library required. - Subscribe to and publish on `/chatter` (`std_msgs/msg/String`). - Call `/add_two_ints` (`example_interfaces/srv/AddTwoInts`) — the service implementation lives in - [`example/services/service/service-example.cjs`](../../example/services/service/service-example.cjs) + [`example/services/service/service-example.mjs`](../../example/services/service/service-example.mjs) and is launched in a second terminal. ## Layout -- `server.cjs` — `rclnodejs` node + `startRosocket` bridge only. +- `server.mjs` — `rclnodejs` node + `startRosocket` bridge only. - `index.html` — single-file browser client using only built-in `WebSocket` and `JSON`. @@ -27,12 +27,12 @@ library required. source /opt/ros/$ROS_DISTRO/setup.bash # 2. Terminal A — start the WebSocket gateway -node demo/rosocket/server.cjs +node demo/rosocket/server.mjs # [rosocket-demo] listening on ws://localhost:9000 (bind=0.0.0.0) # 3. Terminal B — start the AddTwoInts service so the browser has # something to call -node example/services/service/service-example.cjs +node example/services/service/service-example.mjs ``` The server binds to `0.0.0.0:9000` so it is reachable from any host diff --git a/demo/rosocket/server.cjs b/demo/rosocket/server.cjs deleted file mode 100644 index 9d11cbe4..00000000 --- a/demo/rosocket/server.cjs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2026 RobotWebTools Contributors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 - -'use strict'; - -// rosocket demo server. -// -// Bridges two ROS 2 endpoints to plain WebSocket URLs so a browser can -// drive them with built-in `WebSocket` + `JSON`, no client library: -// -// ws://:9000/topic/chatter std_msgs/msg/String -// ws://:9000/service/add_two_ints example_interfaces/srv/AddTwoInts -// -// Run inside WSL (where ROS 2 is sourced): -// node demo/rosocket/server.cjs -// -// To exercise the service, start the existing AddTwoInts example in a -// second terminal (it implements `/add_two_ints`): -// node example/services/service/service-example.cjs -// -// Then open demo/rosocket/index.html on the Windows host browser. WSL2 -// forwards localhost so `ws://localhost:9000` works as-is. See README.md -// for fallback instructions. - -const rclnodejs = require('../../index.js').default; -const { startRosocket } = require('../../rosocket'); - -const PORT = Number(process.env.PORT) || 9000; -const HOST = process.env.HOST || '0.0.0.0'; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('rosocket_demo_node'); - rclnodejs.spin(node); - - const bridge = await startRosocket({ - node, - port: PORT, - host: HOST, - topicTypes: { - '/chatter': 'std_msgs/msg/String', - }, - serviceTypes: { - '/add_two_ints': 'example_interfaces/srv/AddTwoInts', - }, - }); - - const displayHost = HOST === '0.0.0.0' || HOST === '::' ? 'localhost' : HOST; - const baseUrl = `ws://${displayHost}:${bridge.port}`; - console.log( - `[rosocket-demo] listening on ${baseUrl} (bind=${HOST}:${bridge.port})` - ); - console.log('[rosocket-demo] endpoints:'); - console.log(` ${baseUrl}/topic/chatter`); - console.log(` ${baseUrl}/service/add_two_ints`); - console.log('Open demo/rosocket/index.html in the host browser to try it.'); - - const shutdown = () => { - bridge.close().finally(() => { - try { - rclnodejs.shutdown(); - } catch (_) {} - process.exit(0); - }); - }; - process.on('SIGINT', shutdown); - process.on('SIGTERM', shutdown); -} - -main().catch((e) => { - console.error(e.stack || e.message); - process.exit(1); -}); diff --git a/demo/rosocket/server.mjs b/demo/rosocket/server.mjs new file mode 100644 index 00000000..ed99d57c --- /dev/null +++ b/demo/rosocket/server.mjs @@ -0,0 +1,69 @@ +// Copyright (c) 2026 RobotWebTools Contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 + +// rosocket demo server. +// +// Bridges two ROS 2 endpoints to plain WebSocket URLs so a browser can +// drive them with built-in `WebSocket` + `JSON`, no client library: +// +// ws://:9000/topic/chatter std_msgs/msg/String +// ws://:9000/service/add_two_ints example_interfaces/srv/AddTwoInts +// +// Run inside WSL (where ROS 2 is sourced): +// node demo/rosocket/server.mjs +// +// To exercise the service, start the existing AddTwoInts example in a +// second terminal (it implements `/add_two_ints`): +// node example/services/service/service-example.mjs +// +// Then open demo/rosocket/index.html on the Windows host browser. WSL2 +// forwards localhost so `ws://localhost:9000` works as-is. See README.md +// for fallback instructions. + +import rclnodejs from '../../index.js'; +import { startRosocket } from '../../rosocket/index.js'; + +const PORT = Number(process.env.PORT) || 9000; +const HOST = process.env.HOST || '0.0.0.0'; + +await rclnodejs.init(); +const node = rclnodejs.createNode('rosocket_demo_node'); +rclnodejs.spin(node); + +const bridge = await startRosocket({ + node, + port: PORT, + host: HOST, + topicTypes: { + '/chatter': 'std_msgs/msg/String', + }, + serviceTypes: { + '/add_two_ints': 'example_interfaces/srv/AddTwoInts', + }, +}); + +const displayHost = HOST === '0.0.0.0' || HOST === '::' ? 'localhost' : HOST; +const baseUrl = `ws://${displayHost}:${bridge.port}`; +console.log( + `[rosocket-demo] listening on ${baseUrl} (bind=${HOST}:${bridge.port})` +); +console.log('[rosocket-demo] endpoints:'); +console.log(` ${baseUrl}/topic/chatter`); +console.log(` ${baseUrl}/service/add_two_ints`); +console.log('Open demo/rosocket/index.html in the host browser to try it.'); + +const shutdown = () => { + bridge.close().finally(() => { + try { + rclnodejs.shutdown(); + } catch (_) {} + process.exit(0); + }); +}; +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/demo/typescript/README.md b/demo/typescript/README.md index c365a2a1..cb8f1622 100644 --- a/demo/typescript/README.md +++ b/demo/typescript/README.md @@ -42,7 +42,7 @@ Asynchronous actions with progress feedback and cancellation ### Modern Development -- **ES2020+ syntax** with async/await patterns +- **ES2022+ syntax** with async/await patterns - **Modular architecture** with clean separation of concerns - **Error boundaries** with comprehensive exception handling - **Graceful shutdown** handling for SIGINT/SIGTERM diff --git a/demo/typescript/actions/README.md b/demo/typescript/actions/README.md index 6fb67905..4ae41c7e 100644 --- a/demo/typescript/actions/README.md +++ b/demo/typescript/actions/README.md @@ -36,8 +36,6 @@ demo/typescript/actions/ ├── src/ │ ├── client.ts # Action client implementation │ └── server.ts # Action server implementation -├── types/ -│ └── rclnodejs.d.ts # Type definitions ├── package.json # Project configuration ├── tsconfig.json # TypeScript configuration ├── .gitignore # Git ignore rules diff --git a/demo/typescript/actions/tsconfig.json b/demo/typescript/actions/tsconfig.json index aed65ddb..aea5e2b1 100644 --- a/demo/typescript/actions/tsconfig.json +++ b/demo/typescript/actions/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "ES2020", - "module": "CommonJS", - "lib": ["ES2020", "DOM"], + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM"], "outDir": "./dist", "rootDir": "./src", "strict": true, @@ -16,7 +17,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "types": ["node"], - "typeRoots": ["./types", "./node_modules/@types"] + "typeRoots": ["./node_modules/@types"] }, "include": [ "src/**/*" diff --git a/demo/typescript/services/README.md b/demo/typescript/services/README.md index 542cd66e..23dd7f88 100644 --- a/demo/typescript/services/README.md +++ b/demo/typescript/services/README.md @@ -199,7 +199,7 @@ Starting TypeScript Service Client Demo... This demo includes: -- **Local Type Definitions**: Custom TypeScript definitions for rclnodejs in `types/rclnodejs.d.ts` +- **Package Type Definitions**: Uses the in-tree rclnodejs TypeScript declarations (no local stub types) - **Service Types**: Type definitions for `example_interfaces/srv/AddTwoInts` - **Strict Type Checking**: Full TypeScript strict mode enabled - **Build Pipeline**: Automated compilation with shebang fixing for executable JavaScript @@ -292,7 +292,7 @@ Modify the service callback to implement your own business logic: ``` Type errors in service definitions ``` - **Solution**: Check the type definitions in `types/rclnodejs.d.ts` match your usage. + **Solution**: Ensure the in-tree rclnodejs package is built so its TypeScript declarations are available. ### Debugging Tips diff --git a/demo/typescript/services/tsconfig.json b/demo/typescript/services/tsconfig.json index be68180b..1ee0c04e 100644 --- a/demo/typescript/services/tsconfig.json +++ b/demo/typescript/services/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "ES2020", - "module": "CommonJS", - "lib": ["ES2020"], + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, @@ -16,7 +17,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "types": ["node"], - "typeRoots": ["./types", "./node_modules/@types"] + "typeRoots": ["./node_modules/@types"] }, "include": [ "src/**/*", diff --git a/demo/typescript/topics/README.md b/demo/typescript/topics/README.md index 8fec0e41..173b3a55 100644 --- a/demo/typescript/topics/README.md +++ b/demo/typescript/topics/README.md @@ -105,30 +105,21 @@ For development, you can run TypeScript files directly with ts-node: ## TypeScript Setup -This demo includes a self-contained TypeScript configuration that allows compilation without requiring the full rclnodejs installation to be built first. The key components are: - -### Local Type Definitions - -The `types/rclnodejs.d.ts` file provides basic TypeScript declarations for rclnodejs, enabling: - -- ✅ TypeScript compilation without dependencies -- ✅ Type checking and IntelliSense support -- ✅ Proper interface definitions for common ROS2 operations +This demo builds against the in-tree **rclnodejs** package +(`"rclnodejs": "file:../../.."`), so it uses the package's own TypeScript +declarations — there are no local stub types. ### TypeScript Configuration The `tsconfig.json` is configured to: -- Use local type definitions from the `types/` directory +- Use modern `NodeNext` module resolution, matching how Node.js resolves the + package's `exports` map +- Target `ES2022`, consistent with the rclnodejs package itself - Compile TypeScript to JavaScript in the `dist/` directory - Generate source maps and declaration files - Enable strict type checking -### Development vs Runtime - -- **Compilation**: Uses local type definitions (no rclnodejs dependency) -- **Runtime**: Requires actual rclnodejs installation and ROS 2 environment - ## Expected Output ### Publisher Output: diff --git a/demo/typescript/topics/tsconfig.json b/demo/typescript/topics/tsconfig.json index ab3cb478..a2535303 100644 --- a/demo/typescript/topics/tsconfig.json +++ b/demo/typescript/topics/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "module": "commonjs", - "moduleResolution": "node", - "target": "es2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "es2022", "outDir": "./dist", "rootDir": "./src", "strict": true, @@ -12,9 +12,8 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "lib": ["es2020"], + "lib": ["es2022"], "typeRoots": [ - "./types", "./node_modules/@types" ] }, diff --git a/demo/web/javascript/README.md b/demo/web/javascript/README.md index 46290481..f49446e8 100644 --- a/demo/web/javascript/README.md +++ b/demo/web/javascript/README.md @@ -14,12 +14,12 @@ cd demo/web/javascript ```bash source /opt/ros//setup.bash -node runtime.cjs +node runtime.mjs # rclnodejs/web : ws://localhost:9000/capability # also http://localhost:9001/capability (call/publish, curl-able) ``` -`runtime.cjs` exposes a tiny `/add_two_ints` service + 1 Hz +`runtime.mjs` exposes a tiny `/add_two_ints` service + 1 Hz `/web_demo_tick` publisher so every panel has live data. **Shell 2 — static-file server (hosts `index.html` + maps `/sdk/*` to @@ -27,7 +27,7 @@ the in-repo [`web/`](../../../web/) folder so the page can `import` the SDK from a plain URL):** ```bash -node static.cjs +node static.mjs # Static files : http://localhost:8080/ ``` @@ -67,18 +67,18 @@ curl -sS -X POST http://localhost:9001/capability/call/add_two_ints \ Subscribe stays on WebSocket. -## Without the bundled `runtime.cjs` +## Without the bundled `runtime.mjs` -`runtime.cjs` bundles the rclnodejs/web runtime and the demo's sample +`runtime.mjs` bundles the rclnodejs/web runtime and the demo's sample ROS 2 nodes (the `/add_two_ints` service + the `/web_demo_tick` publisher) into one process so the demo runs out of the box. In a real project you already have those ROS 2 nodes running elsewhere, -so you only need the runtime. **Replace shell 1's `node runtime.cjs` -with the CLI** — shell 2 (`node static.cjs`) and the browser code are +so you only need the runtime. **Replace shell 1's `node runtime.mjs` +with the CLI** — shell 2 (`node static.mjs`) and the browser code are unchanged: ```bash -# shell 1 (instead of `node runtime.cjs`); the `-p rclnodejs` tells npx +# shell 1 (instead of `node runtime.mjs`); the `-p rclnodejs` tells npx # the `rclnodejs-web` binary lives inside the `rclnodejs` package: npx -p rclnodejs rclnodejs-web web.json diff --git a/demo/web/javascript/runtime.cjs b/demo/web/javascript/runtime.cjs deleted file mode 100644 index 1b598407..00000000 --- a/demo/web/javascript/runtime.cjs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2026 RobotWebTools Contributors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// rclnodejs/web demo — runtime side (rclnodejs/web runtime + the demo's -// ROS 2 nodes; named `runtime.cjs` to avoid being confused with the -// page-side `static.cjs`). -// -// 1. Source ROS 2 (`source /opt/ros//setup.bash`) -// 2. From this folder run `node runtime.cjs` -// 3. In another shell run `node static.cjs` to host -// `index.html` on http://localhost:8080/ — same split as the -// TypeScript demo's `tsx server.ts` + `vite`. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; -// In a downstream project this is the public, supported import: -// const { createRuntime, WebSocketTransport, HttpTransport } = -// require('rclnodejs/web/server'); -// Inside this in-repo demo we use the relative path so the file runs -// straight out of a fresh git clone, no `npm install` required. -const { - createRuntime, - WebSocketTransport, - HttpTransport, -} = require('../../../lib/runtime'); - -const RUNTIME_PORT = Number(process.env.RUNTIME_PORT || 9000); -const HTTP_PORT = Number(process.env.HTTP_PORT || 9001); - -function displayHost(host) { - return host === '0.0.0.0' || host === '::' ? 'localhost' : host; -} - -// Render the registry as a small human-readable table: -// call /add_two_ints example_interfaces/srv/AddTwoInts -// publish /web_demo_chatter std_msgs/msg/String -// subscribe /web_demo_tick std_msgs/msg/String -function formatCapabilities(caps) { - const rows = []; - for (const verb of ['call', 'publish', 'subscribe']) { - for (const [topic, type] of Object.entries(caps[verb] || {})) { - rows.push([verb, topic, type]); - } - } - if (rows.length === 0) return ' (none)'; - const w0 = Math.max(...rows.map((r) => r[0].length)); - const w1 = Math.max(...rows.map((r) => r[1].length)); - return rows - .map(([v, t, ty]) => ` ${v.padEnd(w0)} ${t.padEnd(w1)} ${ty}`) - .join('\n'); -} - -async function main() { - // ---- Layer 1: rclnodejs core ---------------------------------------- - await rclnodejs.init(); - const node = rclnodejs.createNode('rclnodejs_web_demo_node'); - - // A real ROS 2 service the browser can call. - node.createService( - 'example_interfaces/srv/AddTwoInts', - '/add_two_ints', - (request, response) => { - const reply = response.template; - reply.sum = request.a + request.b; - response.send(reply); - } - ); - - // A real ROS 2 publisher producing a tick once a second so the - // browser's subscribe() has something to receive without the user - // having to publish first. - const tickPub = node.createPublisher('std_msgs/msg/String', '/web_demo_tick'); - let counter = 0; - setInterval(() => { - tickPub.publish({ - data: `tick ${counter++} @ ${new Date().toISOString()}`, - }); - }, 1000); - - rclnodejs.spin(node); - - // ---- Layer 2 + 3: capability runtime over WebSocket *and* HTTP ------- - // The same dispatcher / registry serves both transports — the L2 seam - // is what proves the runtime is transport-agnostic. Browser SDK picks - // a transport from the URL scheme; curl / Postman / AI agents use the - // HTTP one directly. - const runtime = createRuntime({ - node, - transports: [ - new WebSocketTransport({ - port: RUNTIME_PORT, - // '::' = dual-stack: accepts both IPv6 and IPv4-mapped - // connections. Matches Node's http server default and avoids - // the WSL2 / glibc "localhost → ::1" mismatch where - // 0.0.0.0-only servers appear unreachable from the browser. - host: '::', - }), - new HttpTransport({ - port: HTTP_PORT, - host: '::', - }), - ], - }); - runtime.expose({ - call: { '/add_two_ints': 'example_interfaces/srv/AddTwoInts' }, - publish: { '/web_demo_chatter': 'std_msgs/msg/String' }, - subscribe: { - '/web_demo_tick': 'std_msgs/msg/String', - '/web_demo_chatter': 'std_msgs/msg/String', - }, - }); - await runtime.start(); - - const caps = runtime.registry.list(); - const total = - Object.keys(caps.call || {}).length + - Object.keys(caps.publish || {}).length + - Object.keys(caps.subscribe || {}).length; - - console.log('rclnodejs/web demo running (JavaScript)'); - console.log( - ` WebSocket : ws://${displayHost('::')}:${RUNTIME_PORT}/capability` - ); - console.log( - ` HTTP : http://${displayHost('::')}:${HTTP_PORT}/capability (call / publish, curl-able)` - ); - console.log(); - console.log(`Exposed capabilities (${total}):`); - console.log(formatCapabilities(caps)); - console.log(); - console.log( - 'Static page: run `node static.cjs` in another shell, then open http://localhost:8080/' - ); - - // ---- Graceful shutdown ---------------------------------------------- - const stop = async () => { - console.log('\nstopping…'); - await runtime.stop(); - rclnodejs.shutdown(); - process.exit(0); - }; - process.once('SIGINT', stop); - process.once('SIGTERM', stop); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/demo/web/javascript/runtime.mjs b/demo/web/javascript/runtime.mjs new file mode 100644 index 00000000..27c5e378 --- /dev/null +++ b/demo/web/javascript/runtime.mjs @@ -0,0 +1,146 @@ +// Copyright (c) 2026 RobotWebTools Contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// rclnodejs/web demo — runtime side (rclnodejs/web runtime + the demo's +// ROS 2 nodes; named `runtime.mjs` to avoid being confused with the +// page-side `static.mjs`). +// +// 1. Source ROS 2 (`source /opt/ros//setup.bash`) +// 2. From this folder run `node runtime.mjs` +// 3. In another shell run `node static.mjs` to host +// `index.html` on http://localhost:8080/ — same split as the +// TypeScript demo's `tsx server.ts` + `vite`. + +import rclnodejs from '../../../index.js'; +// In a downstream project this is the public, supported import: +// import { createRuntime, WebSocketTransport, HttpTransport } from +// 'rclnodejs/web/server'; +// Inside this in-repo demo we use the relative path so the file runs +// straight out of a fresh git clone, no `npm install` required. +import { + createRuntime, + WebSocketTransport, + HttpTransport, +} from '../../../lib/runtime/index.js'; + +const RUNTIME_PORT = Number(process.env.RUNTIME_PORT || 9000); +const HTTP_PORT = Number(process.env.HTTP_PORT || 9001); + +function displayHost(host) { + return host === '0.0.0.0' || host === '::' ? 'localhost' : host; +} + +// Render the registry as a small human-readable table: +// call /add_two_ints example_interfaces/srv/AddTwoInts +// publish /web_demo_chatter std_msgs/msg/String +// subscribe /web_demo_tick std_msgs/msg/String +function formatCapabilities(caps) { + const rows = []; + for (const verb of ['call', 'publish', 'subscribe']) { + for (const [topic, type] of Object.entries(caps[verb] || {})) { + rows.push([verb, topic, type]); + } + } + if (rows.length === 0) return ' (none)'; + const w0 = Math.max(...rows.map((r) => r[0].length)); + const w1 = Math.max(...rows.map((r) => r[1].length)); + return rows + .map(([v, t, ty]) => ` ${v.padEnd(w0)} ${t.padEnd(w1)} ${ty}`) + .join('\n'); +} + +// ---- Layer 1: rclnodejs core ---------------------------------------- +await rclnodejs.init(); +const node = rclnodejs.createNode('rclnodejs_web_demo_node'); + +// A real ROS 2 service the browser can call. +node.createService( + 'example_interfaces/srv/AddTwoInts', + '/add_two_ints', + (request, response) => { + const reply = response.template; + reply.sum = request.a + request.b; + response.send(reply); + } +); + +// A real ROS 2 publisher producing a tick once a second so the +// browser's subscribe() has something to receive without the user +// having to publish first. +const tickPub = node.createPublisher('std_msgs/msg/String', '/web_demo_tick'); +let counter = 0; +setInterval(() => { + tickPub.publish({ + data: `tick ${counter++} @ ${new Date().toISOString()}`, + }); +}, 1000); + +rclnodejs.spin(node); + +// ---- Layer 2 + 3: capability runtime over WebSocket *and* HTTP ------- +// The same dispatcher / registry serves both transports — the L2 seam +// is what proves the runtime is transport-agnostic. Browser SDK picks +// a transport from the URL scheme; curl / Postman / AI agents use the +// HTTP one directly. +const runtime = createRuntime({ + node, + transports: [ + new WebSocketTransport({ + port: RUNTIME_PORT, + // '::' = dual-stack: accepts both IPv6 and IPv4-mapped + // connections. Matches Node's http server default and avoids + // the WSL2 / glibc "localhost → ::1" mismatch where + // 0.0.0.0-only servers appear unreachable from the browser. + host: '::', + }), + new HttpTransport({ + port: HTTP_PORT, + host: '::', + }), + ], +}); +runtime.expose({ + call: { '/add_two_ints': 'example_interfaces/srv/AddTwoInts' }, + publish: { '/web_demo_chatter': 'std_msgs/msg/String' }, + subscribe: { + '/web_demo_tick': 'std_msgs/msg/String', + '/web_demo_chatter': 'std_msgs/msg/String', + }, +}); +await runtime.start(); + +const caps = runtime.registry.list(); +const total = + Object.keys(caps.call || {}).length + + Object.keys(caps.publish || {}).length + + Object.keys(caps.subscribe || {}).length; + +console.log('rclnodejs/web demo running (JavaScript)'); +console.log( + ` WebSocket : ws://${displayHost('::')}:${RUNTIME_PORT}/capability` +); +console.log( + ` HTTP : http://${displayHost('::')}:${HTTP_PORT}/capability (call / publish, curl-able)` +); +console.log(); +console.log(`Exposed capabilities (${total}):`); +console.log(formatCapabilities(caps)); +console.log(); +console.log( + 'Static page: run `node static.mjs` in another shell, then open http://localhost:8080/' +); + +// ---- Graceful shutdown ---------------------------------------------- +const stop = async () => { + console.log('\nstopping…'); + await runtime.stop(); + rclnodejs.shutdown(); + process.exit(0); +}; +process.once('SIGINT', stop); +process.once('SIGTERM', stop); diff --git a/demo/web/javascript/static.cjs b/demo/web/javascript/static.mjs similarity index 89% rename from demo/web/javascript/static.cjs rename to demo/web/javascript/static.mjs index b0380e63..e28b6145 100644 --- a/demo/web/javascript/static.cjs +++ b/demo/web/javascript/static.mjs @@ -6,24 +6,25 @@ // // http://www.apache.org/licenses/LICENSE-2.0 // -// rclnodejs/web demo — static-file server (page side; named `static.cjs` -// to avoid being confused with the runtime-side `runtime.cjs`). +// rclnodejs/web demo — static-file server (page side; named `static.mjs` +// to avoid being confused with the runtime-side `runtime.mjs`). // // Serves index.html on port 8080 and maps `/sdk/*` to the in-repo // `web/` folder so the page can `import { connect } from '/sdk/index.js'` // without bundling. In a downstream project you'd `npm install rclnodejs` // and `import { connect } from 'rclnodejs/web'` instead. // -// Pair with `node runtime.cjs` (the rclnodejs/web runtime + the demo's +// Pair with `node runtime.mjs` (the rclnodejs/web runtime + the demo's // ROS 2 nodes) in another shell — the same split as the TypeScript // demo's `tsx server.ts` + `vite`. Production deployments use nginx, // a CDN, or any other static host. -'use strict'; +import path from 'node:path'; +import http from 'node:http'; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; -const path = require('path'); -const http = require('http'); -const fs = require('fs'); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const STATIC_PORT = Number(process.env.STATIC_PORT || 8080); diff --git a/demo/web/typescript/server.ts b/demo/web/typescript/server.ts index 649e1cf4..9e22d1f8 100644 --- a/demo/web/typescript/server.ts +++ b/demo/web/typescript/server.ts @@ -7,11 +7,11 @@ // http://www.apache.org/licenses/LICENSE-2.0 // // TypeScript demo server. Run with `npm run server` (which uses tsx) or -// `npx tsx server.ts`. Behaviour matches demo/web/javascript/runtime.cjs +// `npx tsx server.ts`. Behaviour matches demo/web/javascript/runtime.mjs // — same runtime + same demo nodes — except this side is written in // TypeScript so the typed SDK story is visible end to end. The static // page server is Vite (`npm run dev`), parallel to the JS demo's -// separate `node static.cjs`. +// separate `node static.mjs`. // rclnodejs is a CommonJS module without first-class ESM types; using // require keeps the server independent of how a downstream project @@ -30,7 +30,7 @@ const RUNTIME_PORT = Number(process.env.RUNTIME_PORT || 9000); const HTTP_PORT = Number(process.env.HTTP_PORT || 9001); // Render the registry as a small human-readable table — see the matching -// helper in demo/web/javascript/runtime.cjs. +// helper in demo/web/javascript/runtime.mjs. function formatCapabilities( caps: Record<'call' | 'publish' | 'subscribe', Record> ): string { diff --git a/example/actions/README.md b/example/actions/README.md index f257c708..0364d528 100644 --- a/example/actions/README.md +++ b/example/actions/README.md @@ -24,7 +24,7 @@ Actions are ideal for: The `action_client/` directory contains examples of nodes that send goals to action servers: -#### 1. Basic Action Client (`action-client-example.cjs`) +#### 1. Basic Action Client (`action-client-example.mjs`) **Purpose**: Demonstrates basic action client functionality with the Fibonacci action. @@ -40,9 +40,9 @@ The `action_client/` directory contains examples of nodes that send goals to act - Goal acceptance verification - Feedback handling during execution - Result processing with status checking -- **Run Command**: `node action_client/action-client-example.cjs` +- **Run Command**: `node action_client/action-client-example.mjs` -#### 2. Action Client with Cancellation (`action-client-cancel-example.cjs`) +#### 2. Action Client with Cancellation (`action-client-cancel-example.mjs`) **Purpose**: Demonstrates how to cancel an action goal during execution. @@ -58,9 +58,9 @@ The `action_client/` directory contains examples of nodes that send goals to act - Goal cancellation with `cancelGoal()` - Cancellation response verification - Cleanup and shutdown handling -- **Run Command**: `node action_client/action-client-cancel-example.cjs` +- **Run Command**: `node action_client/action-client-cancel-example.mjs` -#### 3. Action Client Validation (`action-client-validation-example.cjs`) +#### 3. Action Client Validation (`action-client-validation-example.mjs`) **Purpose**: Demonstrates goal validation features for action clients. @@ -80,14 +80,14 @@ The `action_client/` directory contains examples of nodes that send goals to act - **Detailed Errors**: Field-level validation issues with expected vs received types - **Strict Mode**: Detect extra fields that don't belong in the goal - **Reusable Validators**: Create validators for repeated goal validation -- **Run Command**: `node action_client/action-client-validation-example.cjs` +- **Run Command**: `node action_client/action-client-validation-example.mjs` - **Note**: Standalone example - demonstrates validation errors without requiring a running action server ### Action Server Examples The `action_server/` directory contains examples of nodes that provide action services: -#### 1. Basic Action Server (`action-server-example.cjs`) +#### 1. Basic Action Server (`action-server-example.mjs`) **Purpose**: Demonstrates basic action server implementation for computing Fibonacci sequences. @@ -103,9 +103,9 @@ The `action_server/` directory contains examples of nodes that provide action se - Execution callback with feedback publishing - Cancellation handling (`cancelCallback`) - Progress updates every second -- **Run Command**: `node action_server/action-server-example.cjs` +- **Run Command**: `node action_server/action-server-example.mjs` -#### 2. Deferred Execution Server (`action-server-defer-example.cjs`) +#### 2. Deferred Execution Server (`action-server-defer-example.mjs`) **Purpose**: Shows how to defer goal execution using timers and handle accepted callbacks. @@ -121,9 +121,9 @@ The `action_server/` directory contains examples of nodes that provide action se - Handle accepted callback (`handleAcceptedCallback`) - Timer-based execution control - Manual goal execution triggering -- **Run Command**: `node action_server/action-server-defer-example.cjs` +- **Run Command**: `node action_server/action-server-defer-example.mjs` -#### 3. Single Goal Server (`action-server-single-goal-example.cjs`) +#### 3. Single Goal Server (`action-server-single-goal-example.mjs`) **Purpose**: Demonstrates a server that only allows one active goal at a time. @@ -139,7 +139,7 @@ The `action_server/` directory contains examples of nodes that provide action se - Automatic abortion of previous goals - Goal state tracking (`isActive`) - Handle accepted callback for goal management -- **Run Command**: `node action_server/action-server-single-goal-example.cjs` +- **Run Command**: `node action_server/action-server-single-goal-example.mjs` ## How to Run the Examples @@ -156,7 +156,7 @@ The `action_server/` directory contains examples of nodes that provide action se ```bash cd example/actions - node action_server/action-server-example.cjs + node action_server/action-server-example.mjs ``` You should see: @@ -169,7 +169,7 @@ The `action_server/` directory contains examples of nodes that provide action se ```bash cd example/actions - node action_client/action-client-example.cjs + node action_client/action-client-example.mjs ``` 3. **Expected Output**: @@ -203,14 +203,14 @@ The `action_server/` directory contains examples of nodes that provide action se 1. **Start Server**: Run any action server example 2. **Start Cancellation Client**: ```bash - node action_client/action-client-cancel-example.cjs + node action_client/action-client-cancel-example.mjs ``` 3. **Expected Behavior**: Client sends goal, receives feedback for 2 seconds, then cancels #### Specialized Server Examples -- **Deferred Execution**: Use `action-server-defer-example.cjs` to see 3-second execution delay -- **Single Goal**: Use `action-server-single-goal-example.cjs` to test goal abortion behavior +- **Deferred Execution**: Use `action-server-defer-example.mjs` to see 3-second execution delay +- **Single Goal**: Use `action-server-single-goal-example.mjs` to test goal abortion behavior ## Action Components Explained diff --git a/example/actions/action_client/action-client-cancel-example.cjs b/example/actions/action_client/action-client-cancel-example.mjs similarity index 83% rename from example/actions/action_client/action-client-cancel-example.cjs rename to example/actions/action_client/action-client-cancel-example.mjs index abc70aaf..6b23213a 100644 --- a/example/actions/action_client/action-client-cancel-example.cjs +++ b/example/actions/action_client/action-client-cancel-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const Fibonacci = rclnodejs.require('test_msgs/action/Fibonacci'); class FibonacciActionClient { @@ -77,16 +79,14 @@ class FibonacciActionClient { } } -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('action_client_example_node'); - const client = new FibonacciActionClient(node); +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('action_client_example_node'); + const client = new FibonacciActionClient(node); - client.sendGoal(); + client.sendGoal(); - rclnodejs.spin(node); - }) - .catch((err) => { - console.error(err); - }); + rclnodejs.spin(node); +} catch (err) { + console.error(err); +} diff --git a/example/actions/action_client/action-client-example.cjs b/example/actions/action_client/action-client-example.mjs similarity index 81% rename from example/actions/action_client/action-client-example.cjs rename to example/actions/action_client/action-client-example.mjs index 47e33046..170e203e 100644 --- a/example/actions/action_client/action-client-example.cjs +++ b/example/actions/action_client/action-client-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const Fibonacci = rclnodejs.require('test_msgs/action/Fibonacci'); class FibonacciActionClient { @@ -68,15 +70,13 @@ class FibonacciActionClient { } } -rclnodejs - .init() - .then(async () => { - const node = rclnodejs.createNode('action_client_example_node'); - const client = new FibonacciActionClient(node); - rclnodejs.spin(node); - - await client.sendGoal(); - }) - .catch((err) => { - console.error(err); - }); +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('action_client_example_node'); + const client = new FibonacciActionClient(node); + rclnodejs.spin(node); + + await client.sendGoal(); +} catch (err) { + console.error(err); +} diff --git a/example/actions/action_client/action-client-validation-example.cjs b/example/actions/action_client/action-client-validation-example.cjs deleted file mode 100644 index 880afd59..00000000 --- a/example/actions/action_client/action-client-validation-example.cjs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('action_client_validation_example_node'); - - const Fibonacci = rclnodejs.require( - 'action_tutorials_interfaces/action/Fibonacci' - ); - - const actionClient = new rclnodejs.ActionClient( - node, - 'action_tutorials_interfaces/action/Fibonacci', - 'fibonacci', - { validateGoals: true } - ); - - const validGoal = { order: 10 }; - console.log( - 'Valid goal:', - rclnodejs.validateMessage(validGoal, Fibonacci.Goal).valid - ); - - try { - await actionClient.sendGoal({ order: 'invalid' }); - } catch (error) { - if (error instanceof rclnodejs.MessageValidationError) { - console.log('Caught validation error:', error.issues[0].problem); - } - } - - actionClient.destroy(); - node.destroy(); - rclnodejs.shutdown(); -} - -main(); diff --git a/example/actions/action_client/action-client-validation-example.mjs b/example/actions/action_client/action-client-validation-example.mjs new file mode 100644 index 00000000..99518e25 --- /dev/null +++ b/example/actions/action_client/action-client-validation-example.mjs @@ -0,0 +1,50 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +await rclnodejs.init(); +const node = rclnodejs.createNode('action_client_validation_example_node'); + +const Fibonacci = rclnodejs.require( + 'action_tutorials_interfaces/action/Fibonacci' +); + +const actionClient = new rclnodejs.ActionClient( + node, + 'action_tutorials_interfaces/action/Fibonacci', + 'fibonacci', + { validateGoals: true } +); + +const validGoal = { order: 10 }; +console.log( + 'Valid goal:', + rclnodejs.validateMessage(validGoal, Fibonacci.Goal).valid +); + +try { + await actionClient.sendGoal({ order: 'invalid' }); +} catch (error) { + if (error instanceof rclnodejs.MessageValidationError) { + console.log('Caught validation error:', error.issues[0].problem); + } +} + +actionClient.destroy(); +node.destroy(); +rclnodejs.shutdown(); diff --git a/example/actions/action_server/action-server-defer-example.cjs b/example/actions/action_server/action-server-defer-example.mjs similarity index 88% rename from example/actions/action_server/action-server-defer-example.cjs rename to example/actions/action_server/action-server-defer-example.mjs index 99e75ec8..0abb471a 100644 --- a/example/actions/action_server/action-server-defer-example.cjs +++ b/example/actions/action_server/action-server-defer-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const Fibonacci = rclnodejs.require('test_msgs/action/Fibonacci'); class FibonacciActionServer { @@ -104,15 +106,13 @@ class FibonacciActionServer { } } -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('action_server_example_node'); +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('action_server_example_node'); - new FibonacciActionServer(node); + new FibonacciActionServer(node); - rclnodejs.spin(node); - }) - .catch((err) => { - console.error(err); - }); + rclnodejs.spin(node); +} catch (err) { + console.error(err); +} diff --git a/example/actions/action_server/action-server-example.cjs b/example/actions/action_server/action-server-example.mjs similarity index 86% rename from example/actions/action_server/action-server-example.cjs rename to example/actions/action_server/action-server-example.mjs index 8e4da1a8..4021b4bb 100644 --- a/example/actions/action_server/action-server-example.cjs +++ b/example/actions/action_server/action-server-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const Fibonacci = rclnodejs.require('test_msgs/action/Fibonacci'); class FibonacciActionServer { @@ -84,15 +86,13 @@ class FibonacciActionServer { } } -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('action_server_example_node'); +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('action_server_example_node'); - new FibonacciActionServer(node); + new FibonacciActionServer(node); - rclnodejs.spin(node); - }) - .catch((err) => { - console.error(err); - }); + rclnodejs.spin(node); +} catch (err) { + console.error(err); +} diff --git a/example/actions/action_server/action-server-single-goal-example.cjs b/example/actions/action_server/action-server-single-goal-example.mjs similarity index 88% rename from example/actions/action_server/action-server-single-goal-example.cjs rename to example/actions/action_server/action-server-single-goal-example.mjs index 6ff88d2e..00a8f88c 100644 --- a/example/actions/action_server/action-server-single-goal-example.cjs +++ b/example/actions/action_server/action-server-single-goal-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const Fibonacci = rclnodejs.require('test_msgs/action/Fibonacci'); class FibonacciActionServer { @@ -104,15 +106,13 @@ class FibonacciActionServer { } } -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('action_server_example_node'); +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('action_server_example_node'); - new FibonacciActionServer(node); + new FibonacciActionServer(node); - rclnodejs.spin(node); - }) - .catch((err) => { - console.error(err); - }); + rclnodejs.spin(node); +} catch (err) { + console.error(err); +} diff --git a/example/error-handling/README.md b/example/error-handling/README.md index 5c15ffa4..4a67d8f7 100644 --- a/example/error-handling/README.md +++ b/example/error-handling/README.md @@ -34,7 +34,7 @@ RclNodeError (base) ## Examples -### 1. Type Validation (`error-handling-example.cjs` - Example 1) +### 1. Type Validation (`error-handling-example.mjs` - Example 1) **Purpose**: Demonstrates catching `TypeValidationError` when providing wrong argument types. @@ -42,7 +42,7 @@ RclNodeError (base) - **Features**: Shows `argumentName`, `expectedType`, and `providedValue` properties - **Key Properties**: `error.argumentName`, `error.expectedType`, `error.providedValue` -### 2. Range Validation (`error-handling-example.cjs` - Example 2) +### 2. Range Validation (`error-handling-example.mjs` - Example 2) **Purpose**: Demonstrates catching `RangeValidationError` for out-of-bounds values. @@ -50,7 +50,7 @@ RclNodeError (base) - **Features**: Shows `validationRule` and `providedValue` for constraint violations - **Key Properties**: `error.validationRule`, `error.providedValue`, `error.nodeName` -### 3. Service Errors (`error-handling-example.cjs` - Example 3) +### 3. Service Errors (`error-handling-example.mjs` - Example 3) **Purpose**: Demonstrates `TimeoutError` and `AbortError` handling for service operations. @@ -58,7 +58,7 @@ RclNodeError (base) - **Features**: Demonstrates timeout with `sendRequestAsync({ timeout })` and `AbortController` - **Key Properties**: `error.timeout`, `error.operationType`, `error.entityName` -### 4. Publisher Errors (`error-handling-example.cjs` - Example 4) +### 4. Publisher Errors (`error-handling-example.mjs` - Example 4) **Purpose**: Demonstrates catching `PublisherError` during publisher creation. @@ -66,7 +66,7 @@ RclNodeError (base) - **Features**: Shows error handling for topic creation failures - **Key Class**: `PublisherError` -### 5. Subscription Errors (`error-handling-example.cjs` - Example 5) +### 5. Subscription Errors (`error-handling-example.mjs` - Example 5) **Purpose**: Demonstrates catching `SubscriptionError` during subscription creation. @@ -74,7 +74,7 @@ RclNodeError (base) - **Features**: Shows error handling for subscription creation failures - **Key Class**: `SubscriptionError` -### 6. Parameter Errors (`error-handling-example.cjs` - Example 6) +### 6. Parameter Errors (`error-handling-example.mjs` - Example 6) **Purpose**: Demonstrates `ParameterTypeError` for parameter type mismatches. @@ -82,7 +82,7 @@ RclNodeError (base) - **Features**: Shows parameter validation and type checking - **Key Class**: `ParameterTypeError` -### 7. Name Validation (`error-handling-example.cjs` - Example 7) +### 7. Name Validation (`error-handling-example.mjs` - Example 7) **Purpose**: Demonstrates `NameValidationError` for invalid ROS names. @@ -90,7 +90,7 @@ RclNodeError (base) - **Features**: Shows `invalidIndex` property pointing to error location - **Key Properties**: `error.invalidIndex`, `error.message` -### 8. Error Recovery (`error-handling-example.cjs` - Example 8) +### 8. Error Recovery (`error-handling-example.mjs` - Example 8) **Purpose**: Demonstrates retry logic with structured error handling. @@ -98,7 +98,7 @@ RclNodeError (base) - **Features**: Shows differentiation between recoverable and non-recoverable errors - **Pattern**: Retry on `TimeoutError`, abort on `AbortError` or other errors -### 9. Error Serialization (`error-handling-example.cjs` - Example 9) +### 9. Error Serialization (`error-handling-example.mjs` - Example 9) **Purpose**: Demonstrates using `toJSON()` for logging and debugging. @@ -106,7 +106,7 @@ RclNodeError (base) - **Features**: Shows `toJSON()` method producing complete error information - **Method**: `error.toJSON()` -### 10. Generic Error Handler (`error-handling-example.cjs` - Example 10) +### 10. Generic Error Handler (`error-handling-example.mjs` - Example 10) **Purpose**: Demonstrates reusable error handler for all rclnodejs errors. @@ -121,7 +121,7 @@ RclNodeError (base) 2. **Run All Examples**: ```bash - node example/error-handling/error-handling-example.cjs + node example/error-handling/error-handling-example.mjs ``` 3. **Expected Output**: Demonstrates all 10 error handling patterns with clear success indicators diff --git a/example/error-handling/error-handling-example.cjs b/example/error-handling/error-handling-example.mjs similarity index 97% rename from example/error-handling/error-handling-example.cjs rename to example/error-handling/error-handling-example.mjs index e2549ad9..fec06499 100644 --- a/example/error-handling/error-handling-example.cjs +++ b/example/error-handling/error-handling-example.mjs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; /** * Example 1: Type Validation Errors @@ -295,7 +296,7 @@ async function example10_genericHandler() { rclnodejs.shutdown(); } -async function main() { +try { await example1_typeValidation(); await example2_rangeValidation(); await example3_serviceErrors(); @@ -308,6 +309,6 @@ async function main() { await example10_genericHandler(); console.log('\n✅ All examples completed'); +} catch (error) { + console.error(error); } - -main().catch(console.error); diff --git a/example/graph/README.md b/example/graph/README.md index 8fca619f..51e32135 100644 --- a/example/graph/README.md +++ b/example/graph/README.md @@ -22,7 +22,7 @@ Graph introspection allows you to: ## Graph Example -### ROS Graph Discovery (`ros-graph-example.cjs`) +### ROS Graph Discovery (`ros-graph-example.mjs`) **Purpose**: Demonstrates comprehensive ROS 2 graph introspection capabilities. @@ -61,7 +61,7 @@ This example creates a complete ROS 2 system with multiple nodes and then intros #### Run Command ```bash -node ros-graph-example.cjs +node ros-graph-example.mjs ``` ## Sample Output diff --git a/example/graph/ros-graph-example.cjs b/example/graph/ros-graph-example.cjs deleted file mode 100644 index f179a624..00000000 --- a/example/graph/ros-graph-example.cjs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2021 Wayne Parrott, All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../index.js').default; - -console.log( - 'This example creates the following nodes and outputs the corresponding ROS2 graph:' -); -console.log(' publisher_node'); -console.log(' subscriber_node'); -console.log(' service_node'); -console.log(' ros_graph_display_node'); - -rclnodejs - .init() - .then(() => { - const ns = 'ns1'; - - let publisherNode = new rclnodejs.Node('publisher_node', ns); - publisherNode.createPublisher('std_msgs/msg/String', 'topic'); - - let subscriberNode = new rclnodejs.Node('subscriber_node', ns); - subscriberNode.createSubscription('std_msgs/msg/String', 'topic', () => {}); - - let serviceNode = new rclnodejs.Node('service_node', ns); - serviceNode.createService( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints', - (request, response) => { - let result = response.template; - result.sum = request.a + request.b; - response.send(result); - } - ); - - let clientNode = new rclnodejs.Node('client_node', 'ns2'); - clientNode.createClient( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints' - ); - let node = rclnodejs.createNode('ros_graph_display_node', ns); - let nodeNamesAndNameSpaces = node.getNodeNamesAndNamespaces(); - - console.log('NODES'); - console.log(node.getNodeNames()); - console.log(nodeNamesAndNameSpaces); - - console.log('TOPICS & TYPES'); - console.log(node.getTopicNamesAndTypes()); - - console.log('SERVICES & TYPES'); - console.log(node.getServiceNamesAndTypes()); - - console.log('PUBLISHERS BY NODE'); - console.log( - JSON.stringify( - nodeNamesAndNameSpaces.map((nameNs) => { - return { - node: { - name: nameNs.name, - namespace: nameNs.namespace, - }, - info: node.getPublisherNamesAndTypesByNode( - nameNs.name, - nameNs.namespace - ), - }; - }), - undefined, - ' ' - ) - ); - - console.log('SUBSCRIPTIONS BY NODE'); - console.log( - JSON.stringify( - nodeNamesAndNameSpaces.map((nameNs) => { - return { - node: { - name: nameNs.name, - namespace: nameNs.namespace, - }, - info: node.getSubscriptionNamesAndTypesByNode( - nameNs.name, - nameNs.namespace - ), - }; - }), - undefined, - ' ' - ) - ); - - console.log('SERVICES BY NODE'); - console.log( - JSON.stringify( - nodeNamesAndNameSpaces.map((nameNs) => { - return { - node: { - name: nameNs.name, - namespace: nameNs.namespace, - }, - info: node.getServiceNamesAndTypesByNode( - nameNs.name, - nameNs.namespace - ), - }; - }), - undefined, - ' ' - ) - ); - - console.log('CLIENTS BY NODE'); - console.log( - JSON.stringify( - node.getClientNamesAndTypesByNode('client_node', '/ns2'), - undefined, - ' ' - ) - ); - }) - .catch((e) => { - console.log(`Error: ${e}`); - }); diff --git a/example/graph/ros-graph-example.mjs b/example/graph/ros-graph-example.mjs new file mode 100644 index 00000000..8c5921f2 --- /dev/null +++ b/example/graph/ros-graph-example.mjs @@ -0,0 +1,135 @@ +// Copyright (c) 2021 Wayne Parrott, All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../index.js'; + +console.log( + 'This example creates the following nodes and outputs the corresponding ROS2 graph:' +); +console.log(' publisher_node'); +console.log(' subscriber_node'); +console.log(' service_node'); +console.log(' ros_graph_display_node'); + +try { + await rclnodejs.init(); + + const ns = 'ns1'; + + let publisherNode = new rclnodejs.Node('publisher_node', ns); + publisherNode.createPublisher('std_msgs/msg/String', 'topic'); + + let subscriberNode = new rclnodejs.Node('subscriber_node', ns); + subscriberNode.createSubscription('std_msgs/msg/String', 'topic', () => {}); + + let serviceNode = new rclnodejs.Node('service_node', ns); + serviceNode.createService( + 'example_interfaces/srv/AddTwoInts', + 'add_two_ints', + (request, response) => { + let result = response.template; + result.sum = request.a + request.b; + response.send(result); + } + ); + + let clientNode = new rclnodejs.Node('client_node', 'ns2'); + clientNode.createClient('example_interfaces/srv/AddTwoInts', 'add_two_ints'); + let node = rclnodejs.createNode('ros_graph_display_node', ns); + let nodeNamesAndNameSpaces = node.getNodeNamesAndNamespaces(); + + console.log('NODES'); + console.log(node.getNodeNames()); + console.log(nodeNamesAndNameSpaces); + + console.log('TOPICS & TYPES'); + console.log(node.getTopicNamesAndTypes()); + + console.log('SERVICES & TYPES'); + console.log(node.getServiceNamesAndTypes()); + + console.log('PUBLISHERS BY NODE'); + console.log( + JSON.stringify( + nodeNamesAndNameSpaces.map((nameNs) => { + return { + node: { + name: nameNs.name, + namespace: nameNs.namespace, + }, + info: node.getPublisherNamesAndTypesByNode( + nameNs.name, + nameNs.namespace + ), + }; + }), + undefined, + ' ' + ) + ); + + console.log('SUBSCRIPTIONS BY NODE'); + console.log( + JSON.stringify( + nodeNamesAndNameSpaces.map((nameNs) => { + return { + node: { + name: nameNs.name, + namespace: nameNs.namespace, + }, + info: node.getSubscriptionNamesAndTypesByNode( + nameNs.name, + nameNs.namespace + ), + }; + }), + undefined, + ' ' + ) + ); + + console.log('SERVICES BY NODE'); + console.log( + JSON.stringify( + nodeNamesAndNameSpaces.map((nameNs) => { + return { + node: { + name: nameNs.name, + namespace: nameNs.namespace, + }, + info: node.getServiceNamesAndTypesByNode( + nameNs.name, + nameNs.namespace + ), + }; + }), + undefined, + ' ' + ) + ); + + console.log('CLIENTS BY NODE'); + console.log( + JSON.stringify( + node.getClientNamesAndTypesByNode('client_node', '/ns2'), + undefined, + ' ' + ) + ); +} catch (e) { + console.log(`Error: ${e}`); +} diff --git a/example/lifecycle/README.md b/example/lifecycle/README.md index 2cf36489..bdd2a619 100644 --- a/example/lifecycle/README.md +++ b/example/lifecycle/README.md @@ -38,7 +38,7 @@ State transitions are triggered by these transitions: ## Lifecycle Example -### Lifecycle Node with Countdown (`lifecycle-node-example.cjs`) +### Lifecycle Node with Countdown (`lifecycle-node-example.mjs`) **Purpose**: Demonstrates a complete lifecycle node implementation with automated state management. @@ -81,7 +81,7 @@ This example creates a lifecycle node that: #### Run Command ```bash -node lifecycle-node-example.cjs +node lifecycle-node-example.mjs ``` ## Sample Output diff --git a/example/lifecycle/lifecycle-node-example.cjs b/example/lifecycle/lifecycle-node-example.mjs similarity index 93% rename from example/lifecycle/lifecycle-node-example.cjs rename to example/lifecycle/lifecycle-node-example.mjs index 5a89fe67..a2cc7d10 100644 --- a/example/lifecycle/lifecycle-node-example.cjs +++ b/example/lifecycle/lifecycle-node-example.mjs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; const NODE_NAME = 'test_node'; const TOPIC = 'test'; @@ -117,10 +118,6 @@ class App { } } -async function main() { - let app = new App(); - await app.init(); - app.start(); -} - -main(); +let app = new App(); +await app.init(); +app.start(); diff --git a/example/message-introspector/README.md b/example/message-introspector/README.md index cd4796ad..de78c6b5 100644 --- a/example/message-introspector/README.md +++ b/example/message-introspector/README.md @@ -6,14 +6,14 @@ This directory contains an example demonstrating the `MessageIntrospector` class The `MessageIntrospector` class provides a simple way to understand the structure of ROS 2 messages without directly using `loader.loadInterface`. It's useful for debugging, generating documentation, and building dynamic UIs based on message structure. -## MessageIntrospector Example (`message-introspector-example.cjs`) +## MessageIntrospector Example (`message-introspector-example.mjs`) **Purpose**: Demonstrates how to inspect message structure, fields, and default values. ### Run Command ```bash -node message-introspector-example.cjs +node message-introspector-example.mjs ``` ### Expected Output diff --git a/example/message-introspector/message-introspector-example.cjs b/example/message-introspector/message-introspector-example.cjs deleted file mode 100644 index 484131dc..00000000 --- a/example/message-introspector/message-introspector-example.cjs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../index.js').default; - -/** - * This example demonstrates the MessageIntrospector class for - * inspecting ROS 2 message structure without using loader.loadInterface. - */ -async function main() { - await rclnodejs.init(); - - const Twist = new rclnodejs.MessageIntrospector('geometry_msgs/msg/Twist'); - const String = new rclnodejs.MessageIntrospector('std_msgs/msg/String'); - const JointState = new rclnodejs.MessageIntrospector( - 'sensor_msgs/msg/JointState' - ); - - console.log('Twist fields:', Twist.fields); - console.log('Twist defaults:', Twist.defaults); - - console.log('String fields:', String.fields); - console.log('String defaults:', String.defaults); - - console.log('JointState fields:', JointState.fields); - - console.log('Twist schema msgName:', Twist.schema.msgName); - - await rclnodejs.shutdown(); -} - -main(); diff --git a/example/message-introspector/message-introspector-example.mjs b/example/message-introspector/message-introspector-example.mjs new file mode 100644 index 00000000..d5726cd8 --- /dev/null +++ b/example/message-introspector/message-introspector-example.mjs @@ -0,0 +1,42 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../index.js'; + +/** + * This example demonstrates the MessageIntrospector class for + * inspecting ROS 2 message structure without using loader.loadInterface. + */ +await rclnodejs.init(); + +const Twist = new rclnodejs.MessageIntrospector('geometry_msgs/msg/Twist'); +const String = new rclnodejs.MessageIntrospector('std_msgs/msg/String'); +const JointState = new rclnodejs.MessageIntrospector( + 'sensor_msgs/msg/JointState' +); + +console.log('Twist fields:', Twist.fields); +console.log('Twist defaults:', Twist.defaults); + +console.log('String fields:', String.fields); +console.log('String defaults:', String.defaults); + +console.log('JointState fields:', JointState.fields); + +console.log('Twist schema msgName:', Twist.schema.msgName); + +await rclnodejs.shutdown(); diff --git a/example/parameter/README.md b/example/parameter/README.md index 7c9d548d..64ff3ea3 100644 --- a/example/parameter/README.md +++ b/example/parameter/README.md @@ -22,7 +22,7 @@ Parameters are ideal for: ### Local Parameters (On Current Node) -#### 1. Parameter Declaration (`parameter-declaration-example.cjs`) +#### 1. Parameter Declaration (`parameter-declaration-example.mjs`) **Purpose**: Demonstrates how to declare and use parameters in a ROS 2 node. @@ -41,9 +41,9 @@ Parameters are ideal for: - **Name**: `param1` - **Type**: `PARAMETER_STRING` - **Default Value**: `"hello world"` -- **Run Command**: `node parameter-declaration-example.cjs` +- **Run Command**: `node parameter-declaration-example.mjs` -#### 2. Parameter Override (`parameter-override-example.cjs`) +#### 2. Parameter Override (`parameter-override-example.mjs`) **Purpose**: Shows how to override parameter values using command-line arguments. @@ -63,11 +63,11 @@ Parameters are ideal for: - **Type**: `PARAMETER_STRING` - **Default Value**: `"hello world"` - **Override Value**: `"hello ros2"` (via command line) -- **Run Command**: `node parameter-override-example.cjs` +- **Run Command**: `node parameter-override-example.mjs` ### Remote Parameter Access (On Other Nodes) -#### 3. ParameterClient Basic (`parameter-client-basic-example.cjs`) +#### 3. ParameterClient Basic (`parameter-client-basic-example.mjs`) **Purpose**: Demonstrates accessing and modifying parameters on a remote node using `ParameterClient`. @@ -84,9 +84,9 @@ Parameters are ideal for: - Batch parameter retrieval - Automatic type inference for parameter values - **Target Node**: `turtlesim` (run: `ros2 run turtlesim turtlesim_node`) -- **Run Command**: `node parameter-client-basic-example.cjs` +- **Run Command**: `node parameter-client-basic-example.mjs` -#### 4. ParameterClient Advanced (`parameter-client-advanced-example.cjs`) +#### 4. ParameterClient Advanced (`parameter-client-advanced-example.mjs`) **Purpose**: Comprehensive example showing all ParameterClient features and capabilities. @@ -108,9 +108,9 @@ Parameters are ideal for: - Automatic BigInt conversion for integers - Type inference demonstrations - Lifecycle management -- **Run Command**: `node parameter-client-advanced-example.cjs` +- **Run Command**: `node parameter-client-advanced-example.mjs` -#### 5. ParameterWatcher (`parameter-watcher-example.cjs`) +#### 5. ParameterWatcher (`parameter-watcher-example.mjs`) **Purpose**: Demonstrates watching parameter changes on a remote node in real-time. @@ -126,7 +126,7 @@ Parameters are ideal for: - Built on top of ParameterClient - Simple EventEmitter API - **Target Node**: `turtlesim` (run: `ros2 run turtlesim turtlesim_node`) -- **Run Command**: `node parameter-watcher-example.cjs` +- **Run Command**: `node parameter-watcher-example.mjs` - **Test Changes**: In another terminal, run `ros2 param set /turtlesim background_r 200` **ParameterClient Key Features**: @@ -184,7 +184,7 @@ Parameters are ideal for: ```bash cd example/parameter -node parameter-declaration-example.cjs +node parameter-declaration-example.mjs ``` **Expected Output**: @@ -212,7 +212,7 @@ ParameterDescriptor { ```bash cd example/parameter -node parameter-override-example.cjs +node parameter-override-example.mjs ``` **Expected Output**: @@ -250,7 +250,7 @@ Then in another terminal: ```bash cd example/parameter -node parameter-client-basic-example.cjs +node parameter-client-basic-example.mjs ``` **Expected Output**: @@ -264,7 +264,7 @@ Updated background_b: 200n ```bash cd example/parameter -node parameter-client-advanced-example.cjs +node parameter-client-advanced-example.mjs ``` **Expected Output**: @@ -297,7 +297,7 @@ Then in another terminal: ```bash cd example/parameter -node parameter-watcher-example.cjs +node parameter-watcher-example.mjs ``` **Expected Output**: diff --git a/example/parameter/parameter-client-advanced-example.cjs b/example/parameter/parameter-client-advanced-example.cjs deleted file mode 100644 index ccc00106..00000000 --- a/example/parameter/parameter-client-advanced-example.cjs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../index.js').default; - -const { ParameterType, Parameter, ParameterDescriptor } = rclnodejs; - -async function main() { - await rclnodejs.init(); - - const targetNode = rclnodejs.createNode('target_node'); - const clientNode = rclnodejs.createNode('client_node'); - - targetNode.declareParameter( - new Parameter('max_speed', ParameterType.PARAMETER_DOUBLE, 10.5), - new ParameterDescriptor( - 'max_speed', - ParameterType.PARAMETER_DOUBLE, - 'Maximum speed in m/s' - ) - ); - - targetNode.declareParameter( - new Parameter('debug_mode', ParameterType.PARAMETER_BOOL, false), - new ParameterDescriptor( - 'debug_mode', - ParameterType.PARAMETER_BOOL, - 'Enable debug logging' - ) - ); - - targetNode.declareParameter( - new Parameter('retry_count', ParameterType.PARAMETER_INTEGER, BigInt(3)), - new ParameterDescriptor( - 'retry_count', - ParameterType.PARAMETER_INTEGER, - 'Number of retries' - ) - ); - - rclnodejs.spin(targetNode); - rclnodejs.spin(clientNode); - - const paramClient = clientNode.createParameterClient('target_node'); - - try { - await paramClient.waitForService(10000); - - const { names } = await paramClient.listParameters(); - console.log('Available parameters:', names); - - const maxSpeed = await paramClient.getParameter('max_speed'); - console.log(`max_speed = ${maxSpeed.value}`); - - const params = await paramClient.getParameters([ - 'max_speed', - 'debug_mode', - 'retry_count', - ]); - console.log( - 'Retrieved parameters:', - params.map((p) => p.name) - ); - - await paramClient.setParameter('max_speed', 15.0); - await paramClient.setParameters([ - { name: 'debug_mode', value: true }, - { name: 'retry_count', value: 5 }, - ]); - - const descriptors = await paramClient.describeParameters(['max_speed']); - console.log(`max_speed descriptor:`, descriptors[0]); - - try { - await paramClient.getParameter('max_speed', { timeout: 1 }); - } catch (error) { - // Expected timeout with 1ms - } - } catch (error) { - console.error('Error:', error.message); - } finally { - clientNode.destroy(); - targetNode.destroy(); - rclnodejs.shutdown(); - } -} - -main(); diff --git a/example/parameter/parameter-client-advanced-example.mjs b/example/parameter/parameter-client-advanced-example.mjs new file mode 100644 index 00000000..bfaf5b96 --- /dev/null +++ b/example/parameter/parameter-client-advanced-example.mjs @@ -0,0 +1,98 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../index.js'; + +const { ParameterType, Parameter, ParameterDescriptor } = rclnodejs; + +await rclnodejs.init(); + +const targetNode = rclnodejs.createNode('target_node'); +const clientNode = rclnodejs.createNode('client_node'); + +targetNode.declareParameter( + new Parameter('max_speed', ParameterType.PARAMETER_DOUBLE, 10.5), + new ParameterDescriptor( + 'max_speed', + ParameterType.PARAMETER_DOUBLE, + 'Maximum speed in m/s' + ) +); + +targetNode.declareParameter( + new Parameter('debug_mode', ParameterType.PARAMETER_BOOL, false), + new ParameterDescriptor( + 'debug_mode', + ParameterType.PARAMETER_BOOL, + 'Enable debug logging' + ) +); + +targetNode.declareParameter( + new Parameter('retry_count', ParameterType.PARAMETER_INTEGER, BigInt(3)), + new ParameterDescriptor( + 'retry_count', + ParameterType.PARAMETER_INTEGER, + 'Number of retries' + ) +); + +rclnodejs.spin(targetNode); +rclnodejs.spin(clientNode); + +const paramClient = clientNode.createParameterClient('target_node'); + +try { + await paramClient.waitForService(10000); + + const { names } = await paramClient.listParameters(); + console.log('Available parameters:', names); + + const maxSpeed = await paramClient.getParameter('max_speed'); + console.log(`max_speed = ${maxSpeed.value}`); + + const params = await paramClient.getParameters([ + 'max_speed', + 'debug_mode', + 'retry_count', + ]); + console.log( + 'Retrieved parameters:', + params.map((p) => p.name) + ); + + await paramClient.setParameter('max_speed', 15.0); + await paramClient.setParameters([ + { name: 'debug_mode', value: true }, + { name: 'retry_count', value: 5 }, + ]); + + const descriptors = await paramClient.describeParameters(['max_speed']); + console.log(`max_speed descriptor:`, descriptors[0]); + + try { + await paramClient.getParameter('max_speed', { timeout: 1 }); + } catch (error) { + // Expected timeout with 1ms + } +} catch (error) { + console.error('Error:', error.message); +} finally { + clientNode.destroy(); + targetNode.destroy(); + rclnodejs.shutdown(); +} diff --git a/example/parameter/parameter-client-basic-example.cjs b/example/parameter/parameter-client-basic-example.mjs similarity index 57% rename from example/parameter/parameter-client-basic-example.cjs rename to example/parameter/parameter-client-basic-example.mjs index 0a725dd3..0ad7065d 100644 --- a/example/parameter/parameter-client-basic-example.cjs +++ b/example/parameter/parameter-client-basic-example.mjs @@ -12,40 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; -async function main() { - await rclnodejs.init(); +await rclnodejs.init(); - const node = rclnodejs.createNode('param_client_node'); +const node = rclnodejs.createNode('param_client_node'); - const paramClient = node.createParameterClient('turtlesim', { - timeout: 5000, - }); +const paramClient = node.createParameterClient('turtlesim', { + timeout: 5000, +}); - try { - const available = await paramClient.waitForService(10000); - - if (!available) { - console.log('Turtlesim node not available. Please run:'); - console.log(' ros2 run turtlesim turtlesim_node'); - return; - } +try { + const available = await paramClient.waitForService(10000); + if (!available) { + console.log('Turtlesim node not available. Please run:'); + console.log(' ros2 run turtlesim turtlesim_node'); + } else { const param = await paramClient.getParameter('background_b'); console.log(`Current background_b: ${param.value}`); await paramClient.setParameter('background_b', 200); const updated = await paramClient.getParameter('background_b'); console.log(`Updated background_b: ${updated.value}`); - } catch (error) { - console.error('Error:', error.message); - } finally { - node.destroy(); - rclnodejs.shutdown(); } +} catch (error) { + console.error('Error:', error.message); +} finally { + node.destroy(); + rclnodejs.shutdown(); } - -main(); diff --git a/example/parameter/parameter-declaration-example.cjs b/example/parameter/parameter-declaration-example.mjs similarity index 56% rename from example/parameter/parameter-declaration-example.cjs rename to example/parameter/parameter-declaration-example.mjs index ab85c11e..aeb7b03d 100644 --- a/example/parameter/parameter-declaration-example.cjs +++ b/example/parameter/parameter-declaration-example.mjs @@ -12,39 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; const ParameterType = rclnodejs.ParameterType; const Parameter = rclnodejs.Parameter; const ParameterDescriptor = rclnodejs.ParameterDescriptor; -async function main() { - await rclnodejs.init(); +await rclnodejs.init(); - const node = rclnodejs.createNode('my_node'); +const node = rclnodejs.createNode('my_node'); - const parameter = new Parameter( - 'param1', - ParameterType.PARAMETER_STRING, - 'hello world' - ); - const parameterDescriptor = new ParameterDescriptor( - 'param1', - ParameterType.PARAMETER_STRING - ); +const parameter = new Parameter( + 'param1', + ParameterType.PARAMETER_STRING, + 'hello world' +); +const parameterDescriptor = new ParameterDescriptor( + 'param1', + ParameterType.PARAMETER_STRING +); - node.declareParameter(parameter, parameterDescriptor); - console.log(`Declared parameter: ${parameter.name}`); - - if (!node.hasParameter('param1')) { - console.error(`Unable to find parameter: ${parameter.name}`); - return; - } +node.declareParameter(parameter, parameterDescriptor); +console.log(`Declared parameter: ${parameter.name}`); +if (!node.hasParameter('param1')) { + console.error(`Unable to find parameter: ${parameter.name}`); +} else { console.log('Parameter details: ', node.getParameter('param1')); console.log(node.getParameterDescriptor('param1')); } - -main(); diff --git a/example/parameter/parameter-override-example.cjs b/example/parameter/parameter-override-example.cjs deleted file mode 100644 index 1fce85b1..00000000 --- a/example/parameter/parameter-override-example.cjs +++ /dev/null @@ -1,54 +0,0 @@ -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../index.js').default; - -const ParameterType = rclnodejs.ParameterType; -const Parameter = rclnodejs.Parameter; -const ParameterDescriptor = rclnodejs.ParameterDescriptor; - -async function main() { - const NODE_NAME = 'my_node'; - - // commandline override of param1 - const argv = ['--ros-args', '-p', NODE_NAME + ':param1:=hello ros2']; - - // initialize rclnodejs with commandline argv - await rclnodejs.init(rclnodejs.Context.defaultContext(), argv); - - const node = rclnodejs.createNode(NODE_NAME); - - // define param - const parameter = new Parameter( - 'param1', - ParameterType.PARAMETER_STRING, - 'hello world' - ); - const parameterDescriptor = new ParameterDescriptor( - 'param1', - ParameterType.PARAMETER_STRING - ); - - // declare param1 - node.declareParameter(parameter, parameterDescriptor); - console.log(`Declared parameter: ${parameter.name}`); - - if (!node.hasParameter('param1')) { - console.error(`Unable to find parameter: ${parameter.name}`); - return; - } - - console.log('Parameter overridden: ', node.getParameter('param1')); - console.log(node.getParameterDescriptor('param1')); -} - -main(); diff --git a/example/parameter/parameter-override-example.mjs b/example/parameter/parameter-override-example.mjs new file mode 100644 index 00000000..722b4e38 --- /dev/null +++ b/example/parameter/parameter-override-example.mjs @@ -0,0 +1,50 @@ +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../index.js'; + +const ParameterType = rclnodejs.ParameterType; +const Parameter = rclnodejs.Parameter; +const ParameterDescriptor = rclnodejs.ParameterDescriptor; + +const NODE_NAME = 'my_node'; + +// commandline override of param1 +const argv = ['--ros-args', '-p', NODE_NAME + ':param1:=hello ros2']; + +// initialize rclnodejs with commandline argv +await rclnodejs.init(rclnodejs.Context.defaultContext(), argv); + +const node = rclnodejs.createNode(NODE_NAME); + +// define param +const parameter = new Parameter( + 'param1', + ParameterType.PARAMETER_STRING, + 'hello world' +); +const parameterDescriptor = new ParameterDescriptor( + 'param1', + ParameterType.PARAMETER_STRING +); + +// declare param1 +node.declareParameter(parameter, parameterDescriptor); +console.log(`Declared parameter: ${parameter.name}`); + +if (!node.hasParameter('param1')) { + console.error(`Unable to find parameter: ${parameter.name}`); +} else { + console.log('Parameter overridden: ', node.getParameter('param1')); + console.log(node.getParameterDescriptor('param1')); +} diff --git a/example/parameter/parameter-watcher-example.cjs b/example/parameter/parameter-watcher-example.mjs similarity index 55% rename from example/parameter/parameter-watcher-example.cjs rename to example/parameter/parameter-watcher-example.mjs index c178ce27..8a008968 100644 --- a/example/parameter/parameter-watcher-example.cjs +++ b/example/parameter/parameter-watcher-example.mjs @@ -12,35 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; -async function main() { - await rclnodejs.init(); +await rclnodejs.init(); - const node = rclnodejs.createNode('watcher_node'); +const node = rclnodejs.createNode('watcher_node'); - const watcher = node.createParameterWatcher('turtlesim', [ - 'background_r', - 'background_g', - ]); +const watcher = node.createParameterWatcher('turtlesim', [ + 'background_r', + 'background_g', +]); - watcher.on('change', (params) => { - params.forEach((p) => { - console.log(`${p.name} changed to ${p.value.integer_value}`); - }); +watcher.on('change', (params) => { + params.forEach((p) => { + console.log(`${p.name} changed to ${p.value.integer_value}`); }); +}); - try { - const available = await watcher.start(10000); - - if (!available) { - console.log('Turtlesim node not available. Please run:'); - console.log(' ros2 run turtlesim turtlesim_node'); - return; - } +try { + const available = await watcher.start(10000); + if (!available) { + console.log('Turtlesim node not available. Please run:'); + console.log(' ros2 run turtlesim turtlesim_node'); + } else { console.log('Watching:', watcher.watchedParameters); watcher.addParameter('background_b'); @@ -53,11 +51,9 @@ async function main() { ); rclnodejs.spin(node); - } catch (error) { - console.error('Error:', error.message); - node.destroy(); - rclnodejs.shutdown(); } +} catch (error) { + console.error('Error:', error.message); + node.destroy(); + rclnodejs.shutdown(); } - -main(); diff --git a/example/rate/README.md b/example/rate/README.md index 52eb7e7a..96fd50ce 100644 --- a/example/rate/README.md +++ b/example/rate/README.md @@ -22,7 +22,7 @@ Rate control is essential for: ## Rate Example -### Rate-Limited Loop with High-Frequency Publisher (`rate-example.cjs`) +### Rate-Limited Loop with High-Frequency Publisher (`rate-example.mjs`) **Purpose**: Demonstrates rate control in a scenario with mismatched publication and processing frequencies. @@ -62,7 +62,7 @@ This example creates a sophisticated timing demonstration that shows: #### Run Command ```bash -node rate-example.cjs +node rate-example.mjs ``` ## Sample Output diff --git a/example/rate/rate-example.cjs b/example/rate/rate-example.mjs similarity index 57% rename from example/rate/rate-example.cjs rename to example/rate/rate-example.mjs index cafce0d8..a28203ed 100644 --- a/example/rate/rate-example.cjs +++ b/example/rate/rate-example.mjs @@ -10,9 +10,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../index.js').default; +import rclnodejs from '../../index.js'; /** * This example demonstrates a rate limited loop running at @@ -25,25 +26,21 @@ const rclnodejs = require('../../index.js').default; * * @return {undefined} */ -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('test_node'); - const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); - const subscriptions = node.createSubscription( - 'std_msgs/msg/String', - 'topic', - undefined, - (msg) => console.log(`Received(${Date.now()}): ${msg.data}`) - ); - const rate = await node.createRate(0.5); +await rclnodejs.init(); +const node = rclnodejs.createNode('test_node'); +const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); +const subscriptions = node.createSubscription( + 'std_msgs/msg/String', + 'topic', + undefined, + (msg) => console.log(`Received(${Date.now()}): ${msg.data}`) +); +const rate = await node.createRate(0.5); - setInterval(() => publisher.publish(`hello ${Date.now()}`), 10); +setInterval(() => publisher.publish(`hello ${Date.now()}`), 10); - let forever = true; - while (forever) { - await rate.sleep(); - rclnodejs.spinOnce(node, 1000); - } +let forever = true; +while (forever) { + await rate.sleep(); + rclnodejs.spinOnce(node, 1000); } - -main(); diff --git a/example/rosidl/README.md b/example/rosidl/README.md index e441acbf..1e476564 100644 --- a/example/rosidl/README.md +++ b/example/rosidl/README.md @@ -23,7 +23,7 @@ ROSIDL parsing is useful for: ## ROSIDL Examples -### 1. Message Parsing (`rosidl-parse-msg-example.cjs`) +### 1. Message Parsing (`rosidl-parse-msg-example.mjs`) **Purpose**: Demonstrates parsing ROS 2 message definition files (.msg). @@ -63,7 +63,7 @@ float32 a # Alpha (transparency) component #### Run Command ```bash -node rosidl-parse-msg-example.cjs +node rosidl-parse-msg-example.mjs ``` #### Expected Output @@ -105,7 +105,7 @@ fields includes: } ``` -### 2. Service Parsing (`rosidl-parse-srv-example.cjs`) +### 2. Service Parsing (`rosidl-parse-srv-example.mjs`) **Purpose**: Demonstrates parsing ROS 2 service definition files (.srv). @@ -147,7 +147,7 @@ string message # Informational message #### Run Command ```bash -node rosidl-parse-srv-example.cjs +node rosidl-parse-srv-example.mjs ``` #### Expected Output @@ -183,7 +183,7 @@ srv response fields includes: } ``` -### 3. Action Parsing (`rosidl-parse-action-example.cjs`) +### 3. Action Parsing (`rosidl-parse-action-example.mjs`) **Purpose**: Demonstrates parsing ROS 2 action definition files (.action). @@ -227,7 +227,7 @@ int32[] partial_sequence # Partial sequence (feedback) #### Run Command ```bash -node rosidl-parse-action-example.cjs +node rosidl-parse-action-example.mjs ``` #### Expected Output diff --git a/example/rosidl/rosidl-parse-action-example.cjs b/example/rosidl/rosidl-parse-action-example.mjs similarity index 52% rename from example/rosidl/rosidl-parse-action-example.cjs rename to example/rosidl/rosidl-parse-action-example.mjs index 28b23b78..cf82dafa 100644 --- a/example/rosidl/rosidl-parse-action-example.cjs +++ b/example/rosidl/rosidl-parse-action-example.mjs @@ -10,34 +10,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +import parser from '../../rosidl_parser/rosidl_parser.cjs'; const rosInstallPath = process.env.AMENT_PREFIX_PATH; const packageName = 'test_msgs'; const packagePath = rosInstallPath + '/share/test_msgs/action/Fibonacci.action'; -const parser = require('../../rosidl_parser/rosidl_parser.cjs'); -parser - .parseActionFile(packageName, packagePath) - .then((spec) => { - console.log(`action name: ${spec.actionName}`); - console.log(`pkg name: ${spec.pkgName}`); +try { + const spec = await parser.parseActionFile(packageName, packagePath); - console.log('action goal fields includes:'); - spec.goal.fields.forEach((field) => { - console.log(field); - }); + console.log(`action name: ${spec.actionName}`); + console.log(`pkg name: ${spec.pkgName}`); - console.log('action result fields includes:'); - spec.result.fields.forEach((field) => { - console.log(field); - }); + console.log('action goal fields includes:'); + spec.goal.fields.forEach((field) => { + console.log(field); + }); + + console.log('action result fields includes:'); + spec.result.fields.forEach((field) => { + console.log(field); + }); - console.log('action feedback fields includes:'); - spec.feedback.fields.forEach((field) => { - console.log(field); - }); - }) - .catch((e) => { - console.log(e); + console.log('action feedback fields includes:'); + spec.feedback.fields.forEach((field) => { + console.log(field); }); +} catch (e) { + console.log(e); +} diff --git a/example/rosidl/rosidl-parse-msg-example.cjs b/example/rosidl/rosidl-parse-msg-example.mjs similarity index 69% rename from example/rosidl/rosidl-parse-msg-example.cjs rename to example/rosidl/rosidl-parse-msg-example.mjs index a592aada..e58f6498 100644 --- a/example/rosidl/rosidl-parse-msg-example.cjs +++ b/example/rosidl/rosidl-parse-msg-example.mjs @@ -12,24 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - -const parser = require('../../rosidl_parser/rosidl_parser.cjs'); +import parser from '../../rosidl_parser/rosidl_parser.cjs'; const rosInstallPath = process.env.AMENT_PREFIX_PATH; const packageName = 'std_msgs'; const packagePath = rosInstallPath + '/share/std_msgs/msg/ColorRGBA.msg'; -parser - .parseMessageFile(packageName, packagePath) - .then((spec) => { - console.log(`msg name: ${spec.msgName}`); +try { + const spec = await parser.parseMessageFile(packageName, packagePath); + + console.log(`msg name: ${spec.msgName}`); - console.log('fields includes:'); - spec.fields.forEach((field) => { - console.log(field); - }); - }) - .catch((e) => { - console.log(e); + console.log('fields includes:'); + spec.fields.forEach((field) => { + console.log(field); }); +} catch (e) { + console.log(e); +} diff --git a/example/rosidl/rosidl-parse-srv-example.cjs b/example/rosidl/rosidl-parse-srv-example.mjs similarity index 59% rename from example/rosidl/rosidl-parse-srv-example.cjs rename to example/rosidl/rosidl-parse-srv-example.mjs index f2384a09..6e476e0b 100644 --- a/example/rosidl/rosidl-parse-srv-example.cjs +++ b/example/rosidl/rosidl-parse-srv-example.mjs @@ -12,30 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - -const parser = require('../../rosidl_parser/rosidl_parser.cjs'); +import parser from '../../rosidl_parser/rosidl_parser.cjs'; const rosInstallPath = process.env.AMENT_PREFIX_PATH; const packageName = 'std_srvs'; const packagePath = rosInstallPath + '/share/std_srvs/srv/SetBool.srv'; -parser - .parseServiceFile(packageName, packagePath) - .then((spec) => { - console.log(`srv name: ${spec.srvName}`); - console.log(`pkg name: ${spec.pkgName}`); +try { + const spec = await parser.parseServiceFile(packageName, packagePath); + + console.log(`srv name: ${spec.srvName}`); + console.log(`pkg name: ${spec.pkgName}`); - console.log('srv request fields includes:'); - spec.request.fields.forEach((field) => { - console.log(field); - }); + console.log('srv request fields includes:'); + spec.request.fields.forEach((field) => { + console.log(field); + }); - console.log('srv response fields includes:'); - spec.response.fields.forEach((field) => { - console.log(field); - }); - }) - .catch((e) => { - console.log(e); + console.log('srv response fields includes:'); + spec.response.fields.forEach((field) => { + console.log(field); }); +} catch (e) { + console.log(e); +} diff --git a/example/services/README.md b/example/services/README.md index 3f52e5b3..8f509509 100644 --- a/example/services/README.md +++ b/example/services/README.md @@ -15,7 +15,7 @@ ROS 2 services provide a request-response communication pattern where clients se ### AddTwoInts Service -#### Service Server (`service/service-example.cjs`) +#### Service Server (`service/service-example.mjs`) **Purpose**: Demonstrates creating a service server that adds two integers. @@ -29,9 +29,9 @@ ROS 2 services provide a request-response communication pattern where clients se - **Features**: - Service introspection (ROS 2 Iron+) for monitoring service calls - Proper response handling using `response.template` and `response.send()` -- **Run Command**: `node example/services/service/service-example.cjs` +- **Run Command**: `node example/services/service/service-example.mjs` -#### Service Client (`client/client-example.cjs`) +#### Service Client (`client/client-example.mjs`) **Purpose**: Demonstrates creating a service client that sends requests to the AddTwoInts service. @@ -46,9 +46,9 @@ ROS 2 services provide a request-response communication pattern where clients se - Service availability checking with `waitForService()` - Service introspection configuration (ROS 2 Iron+) - Asynchronous request handling with callbacks -- **Run Command**: `node example/services/client/client-example.cjs` +- **Run Command**: `node example/services/client/client-example.mjs` -#### Async Service Client (`client/async-client-example.cjs`) +#### Async Service Client (`client/async-client-example.mjs`) **Purpose**: Demonstrates modern async/await patterns for service communication, solving callback hell and providing cleaner error handling. @@ -68,9 +68,9 @@ ROS 2 services provide a request-response communication pattern where clients se - **Error Types**: Specific error types (`TimeoutError`, `AbortError`) for better error handling (async only) - **Backward Compatible**: Works alongside existing callback-based `sendRequest()` - **TypeScript Ready**: Full type safety with comprehensive TypeScript definitions -- **Run Command**: `node example/services/client/async-client-example.cjs` +- **Run Command**: `node example/services/client/async-client-example.mjs` -#### Service Client Validation (`client/client-validation-example.cjs`) +#### Service Client Validation (`client/client-validation-example.mjs`) **Purpose**: Demonstrates request validation features for service clients. @@ -89,7 +89,7 @@ ROS 2 services provide a request-response communication pattern where clients se - **Dynamic Toggle**: Enable/disable validation with `willValidateRequest` property - **Detailed Errors**: Field-level validation issues with expected vs received types - **Strict Mode**: Detect extra fields that don't belong in the request -- **Run Command**: `node example/services/client/client-validation-example.cjs` +- **Run Command**: `node example/services/client/client-validation-example.mjs` - **Note**: Standalone example - demonstrates validation errors without requiring a running service **Key API Differences**: @@ -129,7 +129,7 @@ try { ### GetMap Service -#### Service Server (`service/getmap-service-example.cjs`) +#### Service Server (`service/getmap-service-example.mjs`) **Purpose**: Demonstrates creating a service server that provides occupancy grid map data. @@ -145,9 +145,9 @@ try { - Realistic navigation map data for robotics applications - Service introspection support (ROS 2 Iron+) - Detailed logging of map properties -- **Run Command**: `node example/services/service/getmap-service-example.cjs` +- **Run Command**: `node example/services/service/getmap-service-example.mjs` -#### Service Client (`client/getmap-client-example.cjs`) +#### Service Client (`client/getmap-client-example.mjs`) **Purpose**: Demonstrates creating a service client that requests map data from the GetMap service. @@ -163,7 +163,7 @@ try { - Map data analysis (cell distribution, metadata extraction) - Educational output showing map properties - Visual representation of map data -- **Run Command**: `node example/services/client/getmap-client-example.cjs` +- **Run Command**: `node example/services/client/getmap-client-example.mjs` ## How to Run the Examples @@ -175,7 +175,7 @@ try { ```bash cd /path/to/rclnodejs - node example/services/service/service-example.cjs + node example/services/service/service-example.mjs ``` You should see: @@ -188,7 +188,7 @@ try { ```bash cd /path/to/rclnodejs - node example/services/client/client-example.cjs + node example/services/client/client-example.mjs ``` 4. **Expected Output**: @@ -216,14 +216,14 @@ try { ```bash cd /path/to/rclnodejs - node example/services/service/service-example.cjs + node example/services/service/service-example.mjs ``` 3. **Start the Async Client**: In another terminal, run: ```bash cd /path/to/rclnodejs - node example/services/client/async-client-example.cjs + node example/services/client/async-client-example.mjs ``` 4. **Expected Output**: @@ -251,7 +251,7 @@ try { ```bash cd /path/to/rclnodejs - node example/services/service/getmap-service-example.cjs + node example/services/service/getmap-service-example.mjs ``` You should see: @@ -266,7 +266,7 @@ try { ```bash cd /path/to/rclnodejs - node example/services/client/getmap-client-example.cjs + node example/services/client/getmap-client-example.mjs ``` 4. **Expected Output**: diff --git a/example/services/client/async-client-example.cjs b/example/services/client/async-client-example.cjs deleted file mode 100644 index b1eb5d2e..00000000 --- a/example/services/client/async-client-example.cjs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('async_client_example_node'); - const client = node.createClient( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints' - ); - - if ( - rclnodejs.DistroUtils.getDistroId() > - rclnodejs.DistroUtils.getDistroId('humble') - ) { - // To view service events use the following command: - // ros2 topic echo "/add_two_ints/_service_event" - client.configureIntrospection( - node.getClock(), - rclnodejs.QoS.profileSystemDefault, - rclnodejs.ServiceIntrospectionStates.METADATA - ); - } - - const request = { - a: BigInt(Math.floor(Math.random() * 100)), - b: BigInt(Math.floor(Math.random() * 100)), - }; - - let result = await client.waitForService(1000); - if (!result) { - console.log('Error: service not available'); - rclnodejs.shutdown(); - return; - } - - rclnodejs.spin(node); - - console.log(`Sending: ${typeof request}`, request); - - try { - const response = await client.sendRequestAsync(request, { timeout: 5000 }); - console.log(`Result: ${typeof response}`, response); - } catch (error) { - console.log(`Error: ${error.message}`); - } finally { - rclnodejs.shutdown(); - } -} - -main(); diff --git a/example/services/client/async-client-example.mjs b/example/services/client/async-client-example.mjs index 7508addf..bb1b2cbb 100644 --- a/example/services/client/async-client-example.mjs +++ b/example/services/client/async-client-example.mjs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ESM variant of services/client/async-client-example.js. -// // From an installed package you would write `import rclnodejs from 'rclnodejs'`; // run from this checkout we import the source entry point directly. diff --git a/example/services/client/client-example.cjs b/example/services/client/client-example.cjs deleted file mode 100644 index 89e4eb1f..00000000 --- a/example/services/client/client-example.cjs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('client_example_node'); - const client = node.createClient( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints' - ); - - if ( - rclnodejs.DistroUtils.getDistroId() > - rclnodejs.DistroUtils.getDistroId('humble') - ) { - // To view service events use the following command: - // ros2 topic echo "/add_two_ints/_service_event" - client.configureIntrospection( - node.getClock(), - rclnodejs.QoS.profileSystemDefault, - rclnodejs.ServiceIntrospectionStates.METADATA - ); - } - - const request = { - a: BigInt(Math.floor(Math.random() * 100)), - b: BigInt(Math.floor(Math.random() * 100)), - }; - - let result = await client.waitForService(1000); - if (!result) { - console.log('Error: service not available'); - rclnodejs.shutdown(); - return; - } - - console.log(`Sending: ${typeof request}`, request); - client.sendRequest(request, (response) => { - console.log(`Result: ${typeof response}`, response); - rclnodejs.shutdown(); - }); - - rclnodejs.spin(node); -} - -main(); diff --git a/example/services/client/client-example.mjs b/example/services/client/client-example.mjs new file mode 100644 index 00000000..45b8db19 --- /dev/null +++ b/example/services/client/client-example.mjs @@ -0,0 +1,58 @@ +// Copyright (c) 2017 Intel Corporation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +await rclnodejs.init(); + +const node = rclnodejs.createNode('client_example_node'); +const client = node.createClient( + 'example_interfaces/srv/AddTwoInts', + 'add_two_ints' +); + +if ( + rclnodejs.DistroUtils.getDistroId() > + rclnodejs.DistroUtils.getDistroId('humble') +) { + // To view service events use the following command: + // ros2 topic echo "/add_two_ints/_service_event" + client.configureIntrospection( + node.getClock(), + rclnodejs.QoS.profileSystemDefault, + rclnodejs.ServiceIntrospectionStates.METADATA + ); +} + +const request = { + a: BigInt(Math.floor(Math.random() * 100)), + b: BigInt(Math.floor(Math.random() * 100)), +}; + +const available = await client.waitForService(1000); +if (!available) { + console.log('Error: service not available'); + rclnodejs.shutdown(); +} else { + console.log(`Sending: ${typeof request}`, request); + client.sendRequest(request, (response) => { + console.log(`Result: ${typeof response}`, response); + rclnodejs.shutdown(); + }); + + rclnodejs.spin(node); +} diff --git a/example/services/client/client-validation-example.cjs b/example/services/client/client-validation-example.cjs deleted file mode 100644 index 2817a373..00000000 --- a/example/services/client/client-validation-example.cjs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('client_validation_example_node'); - - const client = node.createClient( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints', - { validateRequests: true } - ); - - const validRequest = { a: BigInt(5), b: BigInt(10) }; - console.log( - 'Valid request:', - rclnodejs.validateMessage(validRequest, client.typeClass.Request).valid - ); - - try { - client.sendRequest({ a: 'invalid', b: BigInt(10) }, () => {}); - } catch (error) { - if (error instanceof rclnodejs.MessageValidationError) { - console.log('Caught validation error:', error.issues[0].problem); - } - } - - node.destroy(); - rclnodejs.shutdown(); -} - -main(); diff --git a/example/services/client/client-validation-example.mjs b/example/services/client/client-validation-example.mjs new file mode 100644 index 00000000..97ba6a0a --- /dev/null +++ b/example/services/client/client-validation-example.mjs @@ -0,0 +1,45 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +await rclnodejs.init(); + +const node = rclnodejs.createNode('client_validation_example_node'); + +const client = node.createClient( + 'example_interfaces/srv/AddTwoInts', + 'add_two_ints', + { validateRequests: true } +); + +const validRequest = { a: BigInt(5), b: BigInt(10) }; +console.log( + 'Valid request:', + rclnodejs.validateMessage(validRequest, client.typeClass.Request).valid +); + +try { + client.sendRequest({ a: 'invalid', b: BigInt(10) }, () => {}); +} catch (error) { + if (error instanceof rclnodejs.MessageValidationError) { + console.log('Caught validation error:', error.issues[0].problem); + } +} + +node.destroy(); +rclnodejs.shutdown(); diff --git a/example/services/client/getmap-client-example.cjs b/example/services/client/getmap-client-example.cjs deleted file mode 100644 index a5381d30..00000000 --- a/example/services/client/getmap-client-example.cjs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2025, The Robot Web Tools Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -async function main() { - await rclnodejs.init(); - const node = rclnodejs.createNode('getmap_client_example_node'); - const client = node.createClient('nav_msgs/srv/GetMap', 'get_map'); - - if ( - rclnodejs.DistroUtils.getDistroId() > - rclnodejs.DistroUtils.getDistroId('humble') - ) { - // To view service events use the following command: - // ros2 topic echo "/get_map/_service_event" - client.configureIntrospection( - node.getClock(), - rclnodejs.QoS.profileSystemDefault, - rclnodejs.ServiceIntrospectionStates.METADATA - ); - } - - // GetMap request is empty - no parameters needed - const request = {}; - - let result = await client.waitForService(5000); - if (!result) { - console.log('Error: GetMap service not available'); - console.log('Make sure to run the service first:'); - console.log(' node example/services/service/getmap-service-example.js'); - rclnodejs.shutdown(); - return; - } - - console.log('GetMap service found. Requesting map...'); - console.log(`Sending: ${typeof request}`, request); - - client.sendRequest(request, (response) => { - console.log(`Response received: ${typeof response}`); - console.log('Map metadata:'); - console.log(' Frame ID:', response.map.header.frame_id); - console.log( - ' Timestamp:', - response.map.header.stamp.sec + '.' + response.map.header.stamp.nanosec - ); - console.log(' Resolution:', response.map.info.resolution, 'meters/pixel'); - console.log( - ' Dimensions:', - response.map.info.width + 'x' + response.map.info.height - ); - console.log( - ' Origin position:', - 'x:', - response.map.info.origin.position.x, - 'y:', - response.map.info.origin.position.y, - 'z:', - response.map.info.origin.position.z - ); - console.log(' Map data size:', response.map.data.length, 'cells'); - - // Count different cell types - const freeCells = response.map.data.filter((cell) => cell === 0).length; - const occupiedCells = response.map.data.filter( - (cell) => cell === 100 - ).length; - const unknownCells = response.map.data.filter((cell) => cell === -1).length; - - console.log(' Cell distribution:'); - console.log(' Free cells (0):', freeCells); - console.log(' Occupied cells (100):', occupiedCells); - console.log(' Unknown cells (-1):', unknownCells); - - // Display a simple ASCII representation of the map (if small enough) - if (response.map.info.width <= 20 && response.map.info.height <= 20) { - console.log('\nASCII Map Representation:'); - console.log(' . = free space, # = occupied, ? = unknown'); - for (let y = 0; y < response.map.info.height; y++) { - let row = ' '; - for (let x = 0; x < response.map.info.width; x++) { - const index = y * response.map.info.width + x; - const cell = response.map.data[index]; - if (cell === 0) row += '.'; - else if (cell === 100) row += '#'; - else row += '?'; - } - console.log(row); - } - } - - rclnodejs.shutdown(); - }); - - rclnodejs.spin(node); -} - -main().catch((e) => { - console.log(`Error: ${e}`); - rclnodejs.shutdown(); -}); diff --git a/example/services/client/getmap-client-example.mjs b/example/services/client/getmap-client-example.mjs new file mode 100644 index 00000000..07a2d607 --- /dev/null +++ b/example/services/client/getmap-client-example.mjs @@ -0,0 +1,118 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('getmap_client_example_node'); + const client = node.createClient('nav_msgs/srv/GetMap', 'get_map'); + + if ( + rclnodejs.DistroUtils.getDistroId() > + rclnodejs.DistroUtils.getDistroId('humble') + ) { + // To view service events use the following command: + // ros2 topic echo "/get_map/_service_event" + client.configureIntrospection( + node.getClock(), + rclnodejs.QoS.profileSystemDefault, + rclnodejs.ServiceIntrospectionStates.METADATA + ); + } + + // GetMap request is empty - no parameters needed + const request = {}; + + const available = await client.waitForService(5000); + if (!available) { + console.log('Error: GetMap service not available'); + console.log('Make sure to run the service first:'); + console.log(' node example/services/service/getmap-service-example.mjs'); + rclnodejs.shutdown(); + } else { + console.log('GetMap service found. Requesting map...'); + console.log(`Sending: ${typeof request}`, request); + + client.sendRequest(request, (response) => { + console.log(`Response received: ${typeof response}`); + console.log('Map metadata:'); + console.log(' Frame ID:', response.map.header.frame_id); + console.log( + ' Timestamp:', + response.map.header.stamp.sec + '.' + response.map.header.stamp.nanosec + ); + console.log( + ' Resolution:', + response.map.info.resolution, + 'meters/pixel' + ); + console.log( + ' Dimensions:', + response.map.info.width + 'x' + response.map.info.height + ); + console.log( + ' Origin position:', + 'x:', + response.map.info.origin.position.x, + 'y:', + response.map.info.origin.position.y, + 'z:', + response.map.info.origin.position.z + ); + console.log(' Map data size:', response.map.data.length, 'cells'); + + // Count different cell types + const freeCells = response.map.data.filter((cell) => cell === 0).length; + const occupiedCells = response.map.data.filter( + (cell) => cell === 100 + ).length; + const unknownCells = response.map.data.filter( + (cell) => cell === -1 + ).length; + + console.log(' Cell distribution:'); + console.log(' Free cells (0):', freeCells); + console.log(' Occupied cells (100):', occupiedCells); + console.log(' Unknown cells (-1):', unknownCells); + + // Display a simple ASCII representation of the map (if small enough) + if (response.map.info.width <= 20 && response.map.info.height <= 20) { + console.log('\nASCII Map Representation:'); + console.log(' . = free space, # = occupied, ? = unknown'); + for (let y = 0; y < response.map.info.height; y++) { + let row = ' '; + for (let x = 0; x < response.map.info.width; x++) { + const index = y * response.map.info.width + x; + const cell = response.map.data[index]; + if (cell === 0) row += '.'; + else if (cell === 100) row += '#'; + else row += '?'; + } + console.log(row); + } + } + + rclnodejs.shutdown(); + }); + + rclnodejs.spin(node); + } +} catch (e) { + console.log(`Error: ${e}`); + rclnodejs.shutdown(); +} diff --git a/example/services/service/getmap-service-example.cjs b/example/services/service/getmap-service-example.cjs deleted file mode 100644 index 11994c1c..00000000 --- a/example/services/service/getmap-service-example.cjs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2025, The Robot Web Tools Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - let node = rclnodejs.createNode('getmap_service_example_node'); - - // Create sample map data - const mapWidth = 10; - const mapHeight = 10; - const mapData = Int8Array.from({ length: mapWidth * mapHeight }, (v) => 0); // 0 = free space - - // Add some obstacles (100 = occupied, -1 = unknown) - mapData[22] = 100; // obstacle at position (2,2) - mapData[33] = 100; // obstacle at position (3,3) - mapData[44] = 100; // obstacle at position (4,4) - - const sampleMapResponse = { - map: { - header: { - stamp: { - sec: Math.floor(Date.now() / 1000), - nanosec: (Date.now() % 1000) * 1000000, - }, - frame_id: 'map', - }, - info: { - map_load_time: { - sec: Math.floor(Date.now() / 1000), - nanosec: (Date.now() % 1000) * 1000000, - }, - resolution: 0.05, // meters per pixel - width: mapWidth, - height: mapHeight, - origin: { - position: { - x: -2.5, - y: -2.5, - z: 0.0, - }, - orientation: { - x: 0.0, - y: 0.0, - z: 0.0, - w: 1.0, - }, - }, - }, - data: mapData, - }, - }; - - let service = node.createService( - 'nav_msgs/srv/GetMap', - 'get_map', - (request, response) => { - console.log(`Incoming GetMap request: ${typeof request}`, request); - console.log('Sending map with dimensions:', mapWidth, 'x', mapHeight); - console.log( - 'Map resolution:', - sampleMapResponse.map.info.resolution, - 'meters/pixel' - ); - console.log( - 'Number of occupied cells:', - mapData.filter((cell) => cell === 100).length - ); - - // Update timestamp to current time - const now = Date.now(); - sampleMapResponse.map.header.stamp.sec = Math.floor(now / 1000); - sampleMapResponse.map.header.stamp.nanosec = (now % 1000) * 1000000; - - let result = response.template; - result.map = sampleMapResponse.map; - console.log( - `Sending response: ${typeof result}`, - 'Map data size:', - result.map.data.length, - '\n--' - ); - response.send(result); - } - ); - - if ( - rclnodejs.DistroUtils.getDistroId() > - rclnodejs.DistroUtils.getDistroId('humble') - ) { - console.log('Introspection configured'); - // To view service events use the following command: - // ros2 topic echo "/get_map/_service_event" - service.configureIntrospection( - node.getClock(), - rclnodejs.QoS.profileSystemDefault, - rclnodejs.ServiceIntrospectionStates.CONTENTS - ); - } - - console.log('GetMap service is ready. Waiting for requests...'); - console.log( - 'Service provides a', - mapWidth + 'x' + mapHeight, - 'occupancy grid map' - ); - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(`Error: ${e}`); - }); diff --git a/example/services/service/getmap-service-example.mjs b/example/services/service/getmap-service-example.mjs new file mode 100644 index 00000000..153ef2ab --- /dev/null +++ b/example/services/service/getmap-service-example.mjs @@ -0,0 +1,125 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('getmap_service_example_node'); + + // Create sample map data + const mapWidth = 10; + const mapHeight = 10; + const mapData = Int8Array.from({ length: mapWidth * mapHeight }, () => 0); // 0 = free space + + // Add some obstacles (100 = occupied, -1 = unknown) + mapData[22] = 100; // obstacle at position (2,2) + mapData[33] = 100; // obstacle at position (3,3) + mapData[44] = 100; // obstacle at position (4,4) + + const sampleMapResponse = { + map: { + header: { + stamp: { + sec: Math.floor(Date.now() / 1000), + nanosec: (Date.now() % 1000) * 1000000, + }, + frame_id: 'map', + }, + info: { + map_load_time: { + sec: Math.floor(Date.now() / 1000), + nanosec: (Date.now() % 1000) * 1000000, + }, + resolution: 0.05, // meters per pixel + width: mapWidth, + height: mapHeight, + origin: { + position: { + x: -2.5, + y: -2.5, + z: 0.0, + }, + orientation: { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + }, + }, + }, + data: mapData, + }, + }; + + const service = node.createService( + 'nav_msgs/srv/GetMap', + 'get_map', + (request, response) => { + console.log(`Incoming GetMap request: ${typeof request}`, request); + console.log('Sending map with dimensions:', mapWidth, 'x', mapHeight); + console.log( + 'Map resolution:', + sampleMapResponse.map.info.resolution, + 'meters/pixel' + ); + console.log( + 'Number of occupied cells:', + mapData.filter((cell) => cell === 100).length + ); + + // Update timestamp to current time + const now = Date.now(); + sampleMapResponse.map.header.stamp.sec = Math.floor(now / 1000); + sampleMapResponse.map.header.stamp.nanosec = (now % 1000) * 1000000; + + const result = response.template; + result.map = sampleMapResponse.map; + console.log( + `Sending response: ${typeof result}`, + 'Map data size:', + result.map.data.length, + '\n--' + ); + response.send(result); + } + ); + + if ( + rclnodejs.DistroUtils.getDistroId() > + rclnodejs.DistroUtils.getDistroId('humble') + ) { + console.log('Introspection configured'); + // To view service events use the following command: + // ros2 topic echo "/get_map/_service_event" + service.configureIntrospection( + node.getClock(), + rclnodejs.QoS.profileSystemDefault, + rclnodejs.ServiceIntrospectionStates.CONTENTS + ); + } + + console.log('GetMap service is ready. Waiting for requests...'); + console.log( + 'Service provides a', + mapWidth + 'x' + mapHeight, + 'occupancy grid map' + ); + rclnodejs.spin(node); +} catch (e) { + console.log(`Error: ${e}`); +} diff --git a/example/services/service/service-example.cjs b/example/services/service/service-example.cjs deleted file mode 100644 index 98b12133..00000000 --- a/example/services/service/service-example.cjs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - let node = rclnodejs.createNode('service_example_node'); - - let service = node.createService( - 'example_interfaces/srv/AddTwoInts', - 'add_two_ints', - (request, response) => { - console.log(`Incoming request: ${typeof request}`, request); - let result = response.template; - result.sum = request.a + request.b; - console.log(`Sending response: ${typeof result}`, result, '\n--'); - response.send(result); - } - ); - - if ( - rclnodejs.DistroUtils.getDistroId() > - rclnodejs.DistroUtils.getDistroId('humble') - ) { - console.log('Introspection configured'); - // To view service events use the following command: - // ros2 topic echo "/add_two_ints/_service_event" - service.configureIntrospection( - node.getClock(), - rclnodejs.QoS.profileSystemDefault, - rclnodejs.ServiceIntrospectionStates.CONTENTS - ); - } - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(`Error: ${e}`); - }); diff --git a/example/services/service/service-example.mjs b/example/services/service/service-example.mjs index c7650ac7..bc55e277 100644 --- a/example/services/service/service-example.mjs +++ b/example/services/service/service-example.mjs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ESM variant of services/service/service-example.js. -// // From an installed package you would write `import rclnodejs from 'rclnodejs'`; // run from this checkout we import the source entry point directly. diff --git a/example/timer/timer-example.cjs b/example/timer/timer-example.cjs deleted file mode 100644 index 5fbe27c5..00000000 --- a/example/timer/timer-example.cjs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../index.js').default; - -rclnodejs - .init() - .then(() => { - let node = rclnodejs.createNode('timer_example_node'); - - let timer = node.createTimer(BigInt(1000000000), () => { - console.log('One second elapsed!'); - - console.log('Cancel this timer.'); - timer.cancel(); - - if (timer.isCanceled()) { - console.log('The timer has been canceled successfully.'); - } - - console.log('Reset the timer.'); - timer.reset(); - console.log( - 'The next call will be ' + timer.timeUntilNextCall() + 'ms later.' - ); - - console.log('Shutting down...'); - rclnodejs.shutdown(); - }); - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); diff --git a/example/timer/timer-example.mjs b/example/timer/timer-example.mjs index 56d15bd8..8fdf68ee 100644 --- a/example/timer/timer-example.mjs +++ b/example/timer/timer-example.mjs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ESM variant of timer-example.js. -// // From an installed package you would write `import rclnodejs from 'rclnodejs'`; // run from this checkout we import the source entry point directly. diff --git a/example/topics/README.md b/example/topics/README.md index 0832f2e1..75f2ece5 100644 --- a/example/topics/README.md +++ b/example/topics/README.md @@ -10,66 +10,66 @@ ROS 2 topics are a fundamental communication pattern that allows nodes to exchan The `publisher/` directory contains examples of nodes that publish messages to topics: -### 1. Basic Publisher (`publisher-example.cjs`) +### 1. Basic Publisher (`publisher-example.mjs`) **Purpose**: Demonstrates basic string message publishing. - **Message Type**: `std_msgs/msg/String` - **Topic**: `topic` - **Functionality**: Publishes "Hello ROS" messages every second -- **Run Command**: `node publisher/publisher-example.cjs` +- **Run Command**: `node publisher/publisher-example.mjs` -### 2. Content Filter Publisher (`publisher-content-filter-example.cjs`) +### 2. Content Filter Publisher (`publisher-content-filter-example.mjs`) **Purpose**: Publishes temperature data for content filtering demonstrations. - **Message Type**: `sensor_msgs/msg/Temperature` - **Topic**: `temperature` - **Functionality**: Publishes random temperature values (0-100°C) every 100ms with header information -- **Run Command**: `node publisher/publisher-content-filter-example.cjs` -- **Pair**: Works with `subscription-content-filter-example.cjs` +- **Run Command**: `node publisher/publisher-content-filter-example.mjs` +- **Pair**: Works with `subscription-content-filter-example.mjs` -### 3. Message Publisher (`publisher-message-example.cjs`) +### 3. Message Publisher (`publisher-message-example.mjs`) **Purpose**: Demonstrates publishing complex structured messages. - **Message Type**: `sensor_msgs/msg/JointState` - **Topic**: `JointState` - **Functionality**: Publishes joint state information with header, names, positions, velocities, and efforts -- **Run Command**: `node publisher/publisher-message-example.cjs` -- **Pair**: Works with `subscription-message-example.cjs` +- **Run Command**: `node publisher/publisher-message-example.mjs` +- **Pair**: Works with `subscription-message-example.mjs` -### 4. MultiArray Publisher (`publisher-multiarray-example.cjs`) +### 4. MultiArray Publisher (`publisher-multiarray-example.mjs`) **Purpose**: Shows how to publish multi-dimensional array data. - **Message Type**: `std_msgs/msg/Int32MultiArray` - **Topic**: `Int32MultiArray` - **Functionality**: Publishes 3D array data (2×3×3) with proper layout information -- **Run Command**: `node publisher/publisher-multiarray-example.cjs` -- **Pair**: Works with `subscription-multiarray-example.cjs` +- **Run Command**: `node publisher/publisher-multiarray-example.mjs` +- **Pair**: Works with `subscription-multiarray-example.mjs` -### 5. QoS Publisher (`publisher-qos-example.cjs`) +### 5. QoS Publisher (`publisher-qos-example.mjs`) **Purpose**: Demonstrates Quality of Service (QoS) configuration for publishers. - **Message Type**: `std_msgs/msg/String` - **Topic**: `topic` - **Functionality**: Publishes messages with custom QoS settings (system default policies) -- **Run Command**: `node publisher/publisher-qos-example.cjs` -- **Pair**: Works with `subscription-qos-example.cjs` +- **Run Command**: `node publisher/publisher-qos-example.mjs` +- **Pair**: Works with `subscription-qos-example.mjs` -### 6. Raw Message Publisher (`publisher-raw-message.cjs`) +### 6. Raw Message Publisher (`publisher-raw-message.mjs`) **Purpose**: Shows how to publish raw binary data. - **Message Type**: `test_msgs/msg/BasicTypes` - **Topic**: `chatter` - **Functionality**: Publishes raw Buffer data ("Hello ROS World") -- **Run Command**: `node publisher/publisher-raw-message.cjs` -- **Pair**: Works with `subscription-raw-message.cjs` +- **Run Command**: `node publisher/publisher-raw-message.mjs` +- **Pair**: Works with `subscription-raw-message.mjs` -### 7. Publisher Validation (`publisher-validation-example.cjs`) +### 7. Publisher Validation (`publisher-validation-example.mjs`) **Purpose**: Demonstrates message validation features for publishers. @@ -87,24 +87,24 @@ The `publisher/` directory contains examples of nodes that publish messages to t - Catch invalid messages before publishing - Dynamic validation toggle with `willValidateMessage` property - Detailed error reports with field-level issues -- **Run Command**: `node publisher/publisher-validation-example.cjs` +- **Run Command**: `node publisher/publisher-validation-example.mjs` - **Note**: Standalone example - no subscriber required ## Subscriber Examples The `subscriber/` directory contains examples of nodes that subscribe to topics: -### 1. Basic Subscriber (`subscription-example.cjs`) +### 1. Basic Subscriber (`subscription-example.mjs`) **Purpose**: Demonstrates basic message subscription. - **Message Type**: `std_msgs/msg/String` - **Topic**: `topic` - **Functionality**: Receives and logs string messages -- **Run Command**: `node subscriber/subscription-example.cjs` -- **Pair**: Works with `publisher-example.cjs` +- **Run Command**: `node subscriber/subscription-example.mjs` +- **Pair**: Works with `publisher-example.mjs` -### 2. Content Filter Subscriber (`subscription-content-filter-example.cjs`) +### 2. Content Filter Subscriber (`subscription-content-filter-example.mjs`) **Purpose**: Demonstrates content filtering to receive only relevant messages. @@ -112,20 +112,20 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `temperature` - **Functionality**: Only receives temperature messages above 50°C using content filters - **Features**: ROS 2 Humble+ content filtering with expression `temperature > %0` -- **Run Command**: `node subscriber/subscription-content-filter-example.cjs` -- **Pair**: Works with `publisher-content-filter-example.cjs` +- **Run Command**: `node subscriber/subscription-content-filter-example.mjs` +- **Pair**: Works with `publisher-content-filter-example.mjs` -### 3. Message Subscriber (`subscription-message-example.cjs`) +### 3. Message Subscriber (`subscription-message-example.mjs`) **Purpose**: Receives complex structured messages. - **Message Type**: `sensor_msgs/msg/JointState` - **Topic**: `JointState` - **Functionality**: Receives and logs joint state information -- **Run Command**: `node subscriber/subscription-message-example.cjs` -- **Pair**: Works with `publisher-message-example.cjs` +- **Run Command**: `node subscriber/subscription-message-example.mjs` +- **Pair**: Works with `publisher-message-example.mjs` -### 4. MultiArray Subscriber (`subscription-multiarray-example.cjs`) +### 4. MultiArray Subscriber (`subscription-multiarray-example.mjs`) **Purpose**: Demonstrates receiving and parsing multi-dimensional arrays. @@ -133,20 +133,20 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `Int32MultiArray` - **Functionality**: Receives 3D arrays and iterates through all elements with proper indexing - **Features**: Shows how to parse layout information and access array elements -- **Run Command**: `node subscriber/subscription-multiarray-example.cjs` -- **Pair**: Works with `publisher-multiarray-example.cjs` +- **Run Command**: `node subscriber/subscription-multiarray-example.mjs` +- **Pair**: Works with `publisher-multiarray-example.mjs` -### 5. QoS Subscriber (`subscription-qos-example.cjs`) +### 5. QoS Subscriber (`subscription-qos-example.mjs`) **Purpose**: Demonstrates QoS configuration for subscribers. - **Message Type**: `std_msgs/msg/String` - **Topic**: `topic` - **Functionality**: Receives messages with system default QoS profile -- **Run Command**: `node subscriber/subscription-qos-example.cjs` -- **Pair**: Works with `publisher-qos-example.cjs` +- **Run Command**: `node subscriber/subscription-qos-example.mjs` +- **Pair**: Works with `publisher-qos-example.mjs` -### 6. Raw Message Subscriber (`subscription-raw-message.cjs`) +### 6. Raw Message Subscriber (`subscription-raw-message.mjs`) **Purpose**: Shows how to receive raw binary data. @@ -154,10 +154,10 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `chatter` - **Functionality**: Receives raw Buffer data and converts to UTF-8 string - **Features**: Uses `{ isRaw: true }` option -- **Run Command**: `node subscriber/subscription-raw-message.cjs` -- **Pair**: Works with `publisher-raw-message.cjs` +- **Run Command**: `node subscriber/subscription-raw-message.mjs` +- **Pair**: Works with `publisher-raw-message.mjs` -### 7. Service Event Subscriber (`subscription-service-event-example.cjs`) +### 7. Service Event Subscriber (`subscription-service-event-example.mjs`) **Purpose**: Demonstrates subscribing to service events. @@ -165,9 +165,9 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `/add_two_ints/_service_event` - **Functionality**: Monitors service call events for the AddTwoInts service - **Features**: ROS 2 service introspection capabilities -- **Run Command**: `node subscriber/subscription-service-event-example.cjs` +- **Run Command**: `node subscriber/subscription-service-event-example.mjs` -### 8. Serialization Modes Subscriber (`subscription-serialization-modes-example.cjs`) +### 8. Serialization Modes Subscriber (`subscription-serialization-modes-example.mjs`) **Purpose**: Demonstrates different serialization modes for message handling. @@ -175,9 +175,9 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `scan` - **Functionality**: Shows how 'default', 'plain', and 'json' modes affect message serialization - **Features**: Message serialization control for web applications and JSON compatibility -- **Run Command**: `node subscriber/subscription-serialization-modes-example.cjs` +- **Run Command**: `node subscriber/subscription-serialization-modes-example.mjs` -### 9. JSON Utilities Subscriber (`subscription-json-utilities-example.cjs`) +### 9. JSON Utilities Subscriber (`subscription-json-utilities-example.mjs`) **Purpose**: Demonstrates manual message conversion utilities. @@ -185,9 +185,9 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - **Topic**: `scan` - **Functionality**: Shows how to use toJSONSafe and toJSONString utilities for manual conversion - **Features**: Manual conversion of TypedArrays, BigInt, and special values for JSON serialization -- **Run Command**: `node subscriber/subscription-json-utilities-example.cjs` +- **Run Command**: `node subscriber/subscription-json-utilities-example.mjs` -### 10. Observable Subscriber (`subscription-observable-example.cjs`) +### 10. Observable Subscriber (`subscription-observable-example.mjs`) **Purpose**: Demonstrates RxJS Observable subscriptions for reactive programming. @@ -199,20 +199,20 @@ The `subscriber/` directory contains examples of nodes that subscribe to topics: - Message transformation with `map()` - Content filtering with `filter()` - Batching with `bufferCount()` -- **Run Command**: `node subscriber/subscription-observable-example.cjs` -- **Pair**: Works with `publisher-example.cjs` +- **Run Command**: `node subscriber/subscription-observable-example.mjs` +- **Pair**: Works with `publisher-example.mjs` ## Validator Example The `validator/` directory contains validation utilities: -### Validator (`validator-example.cjs`) +### Validator (`validator-example.mjs`) **Purpose**: Demonstrates ROS 2 name validation functions. - **Functionality**: Validates topic names, node names, namespaces, and full topic names - **Features**: Uses rclnodejs validator utilities -- **Run Command**: `node validator/validator-example.cjs` +- **Run Command**: `node validator/validator-example.mjs` ## Paired Examples @@ -220,13 +220,13 @@ Several examples work together to demonstrate complete communication: | Publisher | Subscriber | Description | | ------------------------------------- | ---------------------------------------- | ------------------------------- | -| `publisher-example.cjs` | `subscription-example.cjs` | Basic string messaging | -| `publisher-content-filter-example.cjs` | `subscription-content-filter-example.cjs` | Temperature data with filtering | -| `publisher-message-example.cjs` | `subscription-message-example.cjs` | Complex structured messages | -| `publisher-multiarray-example.cjs` | `subscription-multiarray-example.cjs` | Multi-dimensional array data | -| `publisher-qos-example.cjs` | `subscription-qos-example.cjs` | QoS configuration | -| `publisher-raw-message.cjs` | `subscription-raw-message.cjs` | Raw binary data | -| `publisher-example.cjs` | `subscription-observable-example.cjs` | RxJS Observable subscription | +| `publisher-example.mjs` | `subscription-example.mjs` | Basic string messaging | +| `publisher-content-filter-example.mjs` | `subscription-content-filter-example.mjs` | Temperature data with filtering | +| `publisher-message-example.mjs` | `subscription-message-example.mjs` | Complex structured messages | +| `publisher-multiarray-example.mjs` | `subscription-multiarray-example.mjs` | Multi-dimensional array data | +| `publisher-qos-example.mjs` | `subscription-qos-example.mjs` | QoS configuration | +| `publisher-raw-message.mjs` | `subscription-raw-message.mjs` | Raw binary data | +| `publisher-example.mjs` | `subscription-observable-example.mjs` | RxJS Observable subscription | ## How to Run Examples @@ -234,11 +234,11 @@ Several examples work together to demonstrate complete communication: 2. **Navigate**: Change to the example/topics directory 3. **Run Publisher**: Start the publisher in one terminal ```bash - node publisher/publisher-example.cjs + node publisher/publisher-example.mjs ``` 4. **Run Subscriber**: Start the corresponding subscriber in another terminal ```bash - node subscriber/subscription-example.cjs + node subscriber/subscription-example.mjs ``` ## Key Concepts Demonstrated diff --git a/example/topics/publisher/publisher-content-filter-example.mjs b/example/topics/publisher/publisher-content-filter-example.mjs new file mode 100644 index 00000000..0877c6cc --- /dev/null +++ b/example/topics/publisher/publisher-content-filter-example.mjs @@ -0,0 +1,46 @@ +// Copyright (c) 2023 Wayne Parrott. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +await rclnodejs.init(); +const node = new rclnodejs.Node('publisher_content_filter_example_node'); +const publisher = node.createPublisher( + 'sensor_msgs/msg/Temperature', + 'temperature' +); + +let count = 0; +setInterval(function () { + let temperature = (Math.random() * 100).toFixed(2); + + publisher.publish({ + header: { + stamp: { + sec: 123456, + nanosec: 789, + }, + frame_id: 'main frame', + }, + temperature: temperature, + variance: 0, + }); + + console.log(`Publish temerature message-${++count}: ${temperature} degrees`); +}, 100); + +node.spin(); diff --git a/example/topics/publisher/publisher-example.cjs b/example/topics/publisher/publisher-example.cjs deleted file mode 100644 index e6b1e252..00000000 --- a/example/topics/publisher/publisher-example.cjs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs.init().then(() => { - const node = rclnodejs.createNode('publisher_example_node'); - const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); - - let counter = 0; - setInterval(() => { - console.log(`Publishing message: Hello ROS ${counter}`); - publisher.publish(`Hello ROS ${counter++}`); - }, 1000); - - rclnodejs.spin(node); -}); diff --git a/example/topics/publisher/publisher-example.mjs b/example/topics/publisher/publisher-example.mjs index d492cf87..b3892213 100644 --- a/example/topics/publisher/publisher-example.mjs +++ b/example/topics/publisher/publisher-example.mjs @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ESM variant of publisher-example.js. -// // From an installed package you would write `import rclnodejs from 'rclnodejs'`; -// run from this checkout we import the source entry point directly. Note the -// top-level `await` — an ES module feature that removes the `.then()` nesting. +// run from this checkout we import the source entry point directly. import rclnodejs from '../../../index.js'; diff --git a/example/topics/publisher/publisher-message-example.cjs b/example/topics/publisher/publisher-message-example.cjs deleted file mode 100644 index 5d497c5f..00000000 --- a/example/topics/publisher/publisher-message-example.cjs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('publisher_message_example_node'); - const publisher = node.createPublisher( - 'sensor_msgs/msg/JointState', - 'JointState' - ); - let count = 0; - - setInterval(function () { - publisher.publish({ - header: { - stamp: { - sec: 123456, - nanosec: 789, - }, - frame_id: 'main frame', - }, - name: ['Tom', 'Jerry'], - position: [1, 2], - velocity: [2, 3], - effort: [4, 5, 6], - }); - console.log(`Publish ${++count} messages.`); - }, 1000); - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); diff --git a/example/topics/publisher/publisher-content-filter-example.cjs b/example/topics/publisher/publisher-message-example.mjs similarity index 58% rename from example/topics/publisher/publisher-content-filter-example.cjs rename to example/topics/publisher/publisher-message-example.mjs index 0866aa35..ca2221db 100644 --- a/example/topics/publisher/publisher-content-filter-example.cjs +++ b/example/topics/publisher/publisher-message-example.mjs @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Wayne Parrott. All rights reserved. +// Copyright (c) 2017 Intel Corporation. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,22 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; -async function main() { +try { await rclnodejs.init(); - const node = new rclnodejs.Node('publisher_content_filter_example_node'); + const node = rclnodejs.createNode('publisher_message_example_node'); const publisher = node.createPublisher( - 'sensor_msgs/msg/Temperature', - 'temperature' + 'sensor_msgs/msg/JointState', + 'JointState' ); - let count = 0; - setInterval(function () { - let temperature = (Math.random() * 100).toFixed(2); + setInterval(function () { publisher.publish({ header: { stamp: { @@ -36,16 +35,15 @@ async function main() { }, frame_id: 'main frame', }, - temperature: temperature, - variance: 0, + name: ['Tom', 'Jerry'], + position: [1, 2], + velocity: [2, 3], + effort: [4, 5, 6], }); + console.log(`Publish ${++count} messages.`); + }, 1000); - console.log( - `Publish temerature message-${++count}: ${temperature} degrees` - ); - }, 100); - - node.spin(); + rclnodejs.spin(node); +} catch (e) { + console.log(e); } - -main(); diff --git a/example/topics/publisher/publisher-multiarray-example.cjs b/example/topics/publisher/publisher-multiarray-example.cjs deleted file mode 100644 index cb1abf10..00000000 --- a/example/topics/publisher/publisher-multiarray-example.cjs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('publisher_multiarray_example_node'); - const publisher = node.createPublisher( - 'std_msgs/msg/Int32MultiArray', - 'Int32MultiArray' - ); - - let count = 0; - setInterval(function () { - // Please reference the usage of multi-array at - // https://github.com/ros2/common_interfaces/blob/master/std_msgs/msg/MultiArrayLayout.msg - publisher.publish({ - layout: { - dim: [ - { - label: 'height', - size: 2, - stride: 2 * 3 * 3, - }, - { - label: 'weight', - size: 3, - stride: 3 * 3, - }, - { - label: 'channel', - size: 3, - stride: 3, - }, - ], - data_offset: 0, - }, - data: [ - 1, 2, 3, 4, 5, 6, 10, 20, 30, 40, 50, 60, 101, 102, 103, 104, 105, - 106, - ], - }); - console.log(`Publish ${++count} messages.`); - }, 1000); - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); diff --git a/example/topics/publisher/publisher-multiarray-example.mjs b/example/topics/publisher/publisher-multiarray-example.mjs new file mode 100644 index 00000000..ceae8b96 --- /dev/null +++ b/example/topics/publisher/publisher-multiarray-example.mjs @@ -0,0 +1,63 @@ +// Copyright (c) 2017 Intel Corporation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('publisher_multiarray_example_node'); + const publisher = node.createPublisher( + 'std_msgs/msg/Int32MultiArray', + 'Int32MultiArray' + ); + + let count = 0; + setInterval(function () { + // Please reference the usage of multi-array at + // https://github.com/ros2/common_interfaces/blob/master/std_msgs/msg/MultiArrayLayout.msg + publisher.publish({ + layout: { + dim: [ + { + label: 'height', + size: 2, + stride: 2 * 3 * 3, + }, + { + label: 'weight', + size: 3, + stride: 3 * 3, + }, + { + label: 'channel', + size: 3, + stride: 3, + }, + ], + data_offset: 0, + }, + data: [ + 1, 2, 3, 4, 5, 6, 10, 20, 30, 40, 50, 60, 101, 102, 103, 104, 105, 106, + ], + }); + console.log(`Publish ${++count} messages.`); + }, 1000); + + rclnodejs.spin(node); +} catch (e) { + console.log(e); +} diff --git a/example/topics/publisher/publisher-qos-example.cjs b/example/topics/publisher/publisher-qos-example.cjs deleted file mode 100644 index 72d3a915..00000000 --- a/example/topics/publisher/publisher-qos-example.cjs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; -const { QoS } = rclnodejs; - -rclnodejs.init().then(() => { - const node = rclnodejs.createNode('publisher_qos_example_node'); - - let qos = new QoS(); - qos.history = QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT; - qos.reliability = - QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_SYSTEM_DEFAULT; - qos.durability = - QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_SYSTEM_DEFAULT; - qos.depth = 1; - qos.avoidRosNameSpaceConventions = false; - - const publisher = node.createPublisher('std_msgs/msg/String', 'topic', { - qos, - }); - - let counter = 0; - setInterval(function () { - console.log(`Publishing message: Hello ROS ${counter}`); - publisher.publish(`Hello ROS ${counter++}`); - }, 1000); - - rclnodejs.spin(node); -}); diff --git a/example/topics/publisher/publisher-qos-example.mjs b/example/topics/publisher/publisher-qos-example.mjs new file mode 100644 index 00000000..e33d27ec --- /dev/null +++ b/example/topics/publisher/publisher-qos-example.mjs @@ -0,0 +1,44 @@ +// Copyright (c) 2017 Intel Corporation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +const { QoS } = rclnodejs; + +await rclnodejs.init(); + +const node = rclnodejs.createNode('publisher_qos_example_node'); + +let qos = new QoS(); +qos.history = QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT; +qos.reliability = + QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_SYSTEM_DEFAULT; +qos.durability = QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_SYSTEM_DEFAULT; +qos.depth = 1; +qos.avoidRosNameSpaceConventions = false; + +const publisher = node.createPublisher('std_msgs/msg/String', 'topic', { + qos, +}); + +let counter = 0; +setInterval(function () { + console.log(`Publishing message: Hello ROS ${counter}`); + publisher.publish(`Hello ROS ${counter++}`); +}, 1000); + +rclnodejs.spin(node); diff --git a/example/topics/publisher/publisher-raw-message.cjs b/example/topics/publisher/publisher-raw-message.cjs deleted file mode 100644 index b4c93126..00000000 --- a/example/topics/publisher/publisher-raw-message.cjs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2020 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('publisher_message_example_node'); - - // We have to make sure the message type of publisher and subscription is - // the same, although it seems meaningless when sending raw messages. - const publisher = node.createPublisher( - 'test_msgs/msg/BasicTypes', - 'chatter' - ); - let count = 0; - - setInterval(function () { - publisher.publish(Buffer.from('Hello ROS World')); - console.log(`Publish ${++count} messages.`); - }, 1000); - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); diff --git a/example/topics/publisher/publisher-raw-message.mjs b/example/topics/publisher/publisher-raw-message.mjs new file mode 100644 index 00000000..15ee1908 --- /dev/null +++ b/example/topics/publisher/publisher-raw-message.mjs @@ -0,0 +1,37 @@ +// Copyright (c) 2020 Intel Corporation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('publisher_message_example_node'); + + // We have to make sure the message type of publisher and subscription is + // the same, although it seems meaningless when sending raw messages. + const publisher = node.createPublisher('test_msgs/msg/BasicTypes', 'chatter'); + let count = 0; + + setInterval(function () { + publisher.publish(Buffer.from('Hello ROS World')); + console.log(`Publish ${++count} messages.`); + }, 1000); + + rclnodejs.spin(node); +} catch (e) { + console.log(e); +} diff --git a/example/topics/publisher/publisher-validation-example.cjs b/example/topics/publisher/publisher-validation-example.cjs deleted file mode 100644 index c741c73b..00000000 --- a/example/topics/publisher/publisher-validation-example.cjs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs.init().then(() => { - const node = rclnodejs.createNode('publisher_validation_example_node'); - - const publisher = node.createPublisher('std_msgs/msg/String', 'topic', { - validateMessages: true, - }); - - publisher.publish({ data: 'Hello ROS' }); - console.log('Published valid message'); - - try { - publisher.publish({ data: 12345 }); - } catch (error) { - if (error instanceof rclnodejs.MessageValidationError) { - console.log('Caught validation error:', error.issues[0].problem); - } - } - - node.destroy(); - rclnodejs.shutdown(); -}); diff --git a/example/topics/publisher/publisher-validation-example.mjs b/example/topics/publisher/publisher-validation-example.mjs new file mode 100644 index 00000000..217feaa4 --- /dev/null +++ b/example/topics/publisher/publisher-validation-example.mjs @@ -0,0 +1,40 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +await rclnodejs.init(); + +const node = rclnodejs.createNode('publisher_validation_example_node'); + +const publisher = node.createPublisher('std_msgs/msg/String', 'topic', { + validateMessages: true, +}); + +publisher.publish({ data: 'Hello ROS' }); +console.log('Published valid message'); + +try { + publisher.publish({ data: 12345 }); +} catch (error) { + if (error instanceof rclnodejs.MessageValidationError) { + console.log('Caught validation error:', error.issues[0].problem); + } +} + +node.destroy(); +rclnodejs.shutdown(); diff --git a/example/topics/subscriber/subscription-content-filter-example.cjs b/example/topics/subscriber/subscription-content-filter-example.cjs deleted file mode 100644 index 1802c59c..00000000 --- a/example/topics/subscriber/subscription-content-filter-example.cjs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2023 Wayne Parrott. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -/** - * This example demonstrates the use of content-filtering - * topics (subscriptions) that were introduced in ROS 2 Humble. - * See the following resources for content-filtering in ROS: - * @see {@link Node#options} - * @see {@link Node#createSubscription} - * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|DDS 1.4 specification, Annex B} - * - * Use publisher-content-filter-example.js to generate example messages. - * - * To see all published messages (filterd + unfiltered) run this - * from commandline: - * - * ros2 topic echo temperature - * - * @return {undefined} - */ -async function main() { - await rclnodejs.init(); - const node = new rclnodejs.Node('subscription_message_example_node'); - - let param = 50; - - // create a content-filter to limit incoming messages to - // only those with temperature > paramC. - const options = rclnodejs.Node.getDefaultOptions(); - options.contentFilter = { - expression: 'temperature > %0', - parameters: [param], - }; - - let count = 0; - let subscription; - try { - subscription = node.createSubscription( - 'sensor_msgs/msg/Temperature', - 'temperature', - options, - (temperatureMsg) => { - console.log(`Received temperature message-${++count}: -${temperatureMsg.temperature}C`); - if (count % 5 === 0) { - if (subscription.hasContentFilter()) { - console.log('Clearing filter'); - subscription.clearContentFilter(); - } else if (param < 100) { - param += 10; - console.log('Update topic content-filter, temperature > ', param); - const contentFilter = { - expression: 'temperature > %0', - parameters: [param], - }; - subscription.setContentFilter(contentFilter); - } - console.log( - 'Content-filtering enabled: ', - subscription.hasContentFilter() - ); - } - } - ); - - if (!subscription.hasContentFilter()) { - console.log('Content-filtering is not enabled on subscription.'); - } - } catch (error) { - console.error('Unable to create content-filtering subscription.'); - console.error( - 'Please ensure your content-filter expression and parameters are well-formed.' - ); - } - - node.spin(); -} - -main(); diff --git a/example/topics/subscriber/subscription-content-filter-example.mjs b/example/topics/subscriber/subscription-content-filter-example.mjs new file mode 100644 index 00000000..af592e6f --- /dev/null +++ b/example/topics/subscriber/subscription-content-filter-example.mjs @@ -0,0 +1,91 @@ +// Copyright (c) 2023 Wayne Parrott. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +/** + * This example demonstrates the use of content-filtering + * topics (subscriptions) that were introduced in ROS 2 Humble. + * See the following resources for content-filtering in ROS: + * @see {@link Node#options} + * @see {@link Node#createSubscription} + * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|DDS 1.4 specification, Annex B} + * + * Use publisher-content-filter-example.mjs to generate example messages. + * + * To see all published messages (filterd + unfiltered) run this + * from commandline: + * + * ros2 topic echo temperature + * + * @return {undefined} + */ +await rclnodejs.init(); +const node = new rclnodejs.Node('subscription_message_example_node'); + +let param = 50; + +// create a content-filter to limit incoming messages to +// only those with temperature > paramC. +const options = rclnodejs.Node.getDefaultOptions(); +options.contentFilter = { + expression: 'temperature > %0', + parameters: [param], +}; + +let count = 0; +let subscription; +try { + subscription = node.createSubscription( + 'sensor_msgs/msg/Temperature', + 'temperature', + options, + (temperatureMsg) => { + console.log(`Received temperature message-${++count}: +${temperatureMsg.temperature}C`); + if (count % 5 === 0) { + if (subscription.hasContentFilter()) { + console.log('Clearing filter'); + subscription.clearContentFilter(); + } else if (param < 100) { + param += 10; + console.log('Update topic content-filter, temperature > ', param); + const contentFilter = { + expression: 'temperature > %0', + parameters: [param], + }; + subscription.setContentFilter(contentFilter); + } + console.log( + 'Content-filtering enabled: ', + subscription.hasContentFilter() + ); + } + } + ); + + if (!subscription.hasContentFilter()) { + console.log('Content-filtering is not enabled on subscription.'); + } +} catch (error) { + console.error('Unable to create content-filtering subscription.'); + console.error( + 'Please ensure your content-filter expression and parameters are well-formed.' + ); +} + +node.spin(); diff --git a/example/topics/subscriber/subscription-example.mjs b/example/topics/subscriber/subscription-example.mjs index ac0783fc..0ca6fea7 100644 --- a/example/topics/subscriber/subscription-example.mjs +++ b/example/topics/subscriber/subscription-example.mjs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ESM variant of subscription-example.js. -// // From an installed package you would write `import rclnodejs from 'rclnodejs'`; // run from this checkout we import the source entry point directly. diff --git a/example/topics/subscriber/subscription-json-utilities-example.cjs b/example/topics/subscriber/subscription-json-utilities-example.mjs similarity index 85% rename from example/topics/subscriber/subscription-json-utilities-example.cjs rename to example/topics/subscriber/subscription-json-utilities-example.mjs index 06fc2986..76a7d4c9 100644 --- a/example/topics/subscriber/subscription-json-utilities-example.cjs +++ b/example/topics/subscriber/subscription-json-utilities-example.mjs @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; /** * This example demonstrates the JSON utility functions for manual message conversion. * These utilities are useful when you need to convert messages on-demand * rather than using the serializationMode subscription option. */ -async function main() { +try { await rclnodejs.init(); const node = new rclnodejs.Node('json_utilities_example_node'); @@ -36,6 +37,6 @@ async function main() { }); node.spin(); +} catch (error) { + console.error(error); } - -main().catch(console.error); diff --git a/example/topics/subscriber/subscription-raw-message.cjs b/example/topics/subscriber/subscription-message-example.mjs similarity index 59% rename from example/topics/subscriber/subscription-raw-message.cjs rename to example/topics/subscriber/subscription-message-example.mjs index 7e753649..352ebe4a 100644 --- a/example/topics/subscriber/subscription-raw-message.cjs +++ b/example/topics/subscriber/subscription-message-example.mjs @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Intel Corporation. All rights reserved. +// Copyright (c) 2017 Intel Corporation. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,21 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; -rclnodejs.init().then(() => { +try { + await rclnodejs.init(); const node = rclnodejs.createNode('subscription_message_example_node'); + let count = 0; node.createSubscription( - 'test_msgs/msg/BasicTypes', - 'chatter', - { isRaw: true }, - (msg) => { - console.log(`Received message: ${msg.toString('utf8')}`); + 'sensor_msgs/msg/JointState', + 'JointState', + (state) => { + console.log(`Received message No. ${++count}: `, state); } ); rclnodejs.spin(node); -}); +} catch (e) { + console.log(e); +} diff --git a/example/topics/subscriber/subscription-multiarray-example.cjs b/example/topics/subscriber/subscription-multiarray-example.cjs deleted file mode 100644 index ffe8d350..00000000 --- a/example/topics/subscriber/subscription-multiarray-example.cjs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('subscription_multiarray_example_node'); - - let counter = 0; - node.createSubscription( - 'std_msgs/msg/Int32MultiArray', - 'Int32MultiArray', - (multiArray) => { - // Please reference the usage of multi-array at - // https://github.com/ros2/common_interfaces/blob/master/std_msgs/msg/MultiArrayLayout.msg - console.log('Message: ', counter++, multiArray); - console.log('Iterate the multi array:'); - const dim = multiArray.layout.dim; - const height = dim[0].size; - const weight = dim[1].size; - const weightStride = dim[1].stride; - const channel = dim[2].size; - const channelStride = dim[2].stride; - - const offset = multiArray.layout.data_offset; - - for (let i = 0; i < height; i++) { - for (let j = 0; j < weight; j++) { - for (let k = 0; k < channel; k++) { - console.log( - `multiArray(${i},${j},${k}) = ${ - multiArray.data[ - offset + weightStride * i + channelStride * j + k - ] - }` - ); - } - } - } - console.log(''); - } - ); - - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); diff --git a/example/topics/subscriber/subscription-multiarray-example.mjs b/example/topics/subscriber/subscription-multiarray-example.mjs new file mode 100644 index 00000000..c6a5f665 --- /dev/null +++ b/example/topics/subscriber/subscription-multiarray-example.mjs @@ -0,0 +1,62 @@ +// Copyright (c) 2017 Intel Corporation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +try { + await rclnodejs.init(); + const node = rclnodejs.createNode('subscription_multiarray_example_node'); + + let counter = 0; + node.createSubscription( + 'std_msgs/msg/Int32MultiArray', + 'Int32MultiArray', + (multiArray) => { + // Please reference the usage of multi-array at + // https://github.com/ros2/common_interfaces/blob/master/std_msgs/msg/MultiArrayLayout.msg + console.log('Message: ', counter++, multiArray); + console.log('Iterate the multi array:'); + const dim = multiArray.layout.dim; + const height = dim[0].size; + const weight = dim[1].size; + const weightStride = dim[1].stride; + const channel = dim[2].size; + const channelStride = dim[2].stride; + + const offset = multiArray.layout.data_offset; + + for (let i = 0; i < height; i++) { + for (let j = 0; j < weight; j++) { + for (let k = 0; k < channel; k++) { + console.log( + `multiArray(${i},${j},${k}) = ${ + multiArray.data[ + offset + weightStride * i + channelStride * j + k + ] + }` + ); + } + } + } + console.log(''); + } + ); + + rclnodejs.spin(node); +} catch (e) { + console.log(e); +} diff --git a/example/topics/subscriber/subscription-observable-example.cjs b/example/topics/subscriber/subscription-observable-example.cjs deleted file mode 100644 index a3f8bf56..00000000 --- a/example/topics/subscriber/subscription-observable-example.cjs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; -const { throttleTime, map, filter, bufferCount } = require('rxjs'); - -async function main() { - await rclnodejs.init(); - - const node = rclnodejs.createNode('observable_subscription_example_node'); - - // Basic observable subscription - const obsSub = node.createObservableSubscription( - 'std_msgs/msg/String', - 'topic' - ); - - // Example 1: Throttled messages (max 2/sec) - obsSub.observable - .pipe( - throttleTime(500), - map((msg) => msg.data) - ) - .subscribe((data) => { - console.log('[Throttled]', data); - }); - - // Example 2: Filtered messages (only containing "ROS") - // Note: RxJS filter() operates at the application level after messages are received. - // For more efficient filtering at the DDS middleware level (reducing network traffic), - // use the contentFilter option. See: tutorials/content-filtering-subscription.md - obsSub.observable - .pipe( - map((msg) => msg.data), - filter((data) => data.includes('ROS')) - ) - .subscribe((data) => { - console.log('[Filtered]', data); - }); - - // Example 3: Batched messages (every 3 messages) - obsSub.observable - .pipe( - map((msg) => msg.data), - bufferCount(3) - ) - .subscribe((batch) => { - console.log('[Batched]', batch.length, 'messages'); - }); - - console.log('Observable subscription created on /topic'); - console.log( - 'Run: ros2 topic pub /topic std_msgs/msg/String "{data: Hello ROS}" -r 5' - ); - - rclnodejs.spin(node); -} - -main(); diff --git a/example/topics/subscriber/subscription-observable-example.mjs b/example/topics/subscriber/subscription-observable-example.mjs new file mode 100644 index 00000000..2496790d --- /dev/null +++ b/example/topics/subscriber/subscription-observable-example.mjs @@ -0,0 +1,69 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; +import { throttleTime, map, filter, bufferCount } from 'rxjs'; + +await rclnodejs.init(); + +const node = rclnodejs.createNode('observable_subscription_example_node'); + +// Basic observable subscription +const obsSub = node.createObservableSubscription( + 'std_msgs/msg/String', + 'topic' +); + +// Example 1: Throttled messages (max 2/sec) +obsSub.observable + .pipe( + throttleTime(500), + map((msg) => msg.data) + ) + .subscribe((data) => { + console.log('[Throttled]', data); + }); + +// Example 2: Filtered messages (only containing "ROS") +// Note: RxJS filter() operates at the application level after messages are received. +// For more efficient filtering at the DDS middleware level (reducing network traffic), +// use the contentFilter option. See: tutorials/content-filtering-subscription.md +obsSub.observable + .pipe( + map((msg) => msg.data), + filter((data) => data.includes('ROS')) + ) + .subscribe((data) => { + console.log('[Filtered]', data); + }); + +// Example 3: Batched messages (every 3 messages) +obsSub.observable + .pipe( + map((msg) => msg.data), + bufferCount(3) + ) + .subscribe((batch) => { + console.log('[Batched]', batch.length, 'messages'); + }); + +console.log('Observable subscription created on /topic'); +console.log( + 'Run: ros2 topic pub /topic std_msgs/msg/String "{data: Hello ROS}" -r 5' +); + +rclnodejs.spin(node); diff --git a/example/topics/subscriber/subscription-qos-example.cjs b/example/topics/subscriber/subscription-qos-example.cjs deleted file mode 100644 index f479be7f..00000000 --- a/example/topics/subscriber/subscription-qos-example.cjs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; -const { QoS } = rclnodejs; - -rclnodejs.init().then(() => { - const node = rclnodejs.createNode('subscription_qos_example_node'); - - node.createSubscription( - 'std_msgs/msg/String', - 'topic', - { qos: QoS.profileSystemDefault }, - (msg) => { - console.log(`Received message: ${typeof msg}`, msg); - } - ); - - rclnodejs.spin(node); -}); diff --git a/example/topics/subscriber/subscription-example.cjs b/example/topics/subscriber/subscription-qos-example.mjs similarity index 59% rename from example/topics/subscriber/subscription-example.cjs rename to example/topics/subscriber/subscription-qos-example.mjs index 02d55e1e..25b5d06c 100644 --- a/example/topics/subscriber/subscription-example.cjs +++ b/example/topics/subscriber/subscription-qos-example.mjs @@ -12,16 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; -rclnodejs.init().then(() => { - const node = rclnodejs.createNode('subscription_example_node'); +const { QoS } = rclnodejs; - node.createSubscription('std_msgs/msg/String', 'topic', (msg) => { +await rclnodejs.init(); + +const node = rclnodejs.createNode('subscription_qos_example_node'); + +node.createSubscription( + 'std_msgs/msg/String', + 'topic', + { qos: QoS.profileSystemDefault }, + (msg) => { console.log(`Received message: ${typeof msg}`, msg); - }); + } +); - rclnodejs.spin(node); -}); +rclnodejs.spin(node); diff --git a/example/topics/subscriber/subscription-message-example.cjs b/example/topics/subscriber/subscription-raw-message.mjs similarity index 50% rename from example/topics/subscriber/subscription-message-example.cjs rename to example/topics/subscriber/subscription-raw-message.mjs index e11096ac..d32780e6 100644 --- a/example/topics/subscriber/subscription-message-example.cjs +++ b/example/topics/subscriber/subscription-raw-message.mjs @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Intel Corporation. All rights reserved. +// Copyright (c) 2020 Intel Corporation. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,26 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; -rclnodejs - .init() - .then(() => { - const node = rclnodejs.createNode('subscription_message_example_node'); - let count = 0; +await rclnodejs.init(); - node.createSubscription( - 'sensor_msgs/msg/JointState', - 'JointState', - (state) => { - console.log(`Received message No. ${++count}: `, state); - } - ); +const node = rclnodejs.createNode('subscription_message_example_node'); - rclnodejs.spin(node); - }) - .catch((e) => { - console.log(e); - }); +node.createSubscription( + 'test_msgs/msg/BasicTypes', + 'chatter', + { isRaw: true }, + (msg) => { + console.log(`Received message: ${msg.toString('utf8')}`); + } +); + +rclnodejs.spin(node); diff --git a/example/topics/subscriber/subscription-serialization-modes-example.cjs b/example/topics/subscriber/subscription-serialization-modes-example.cjs deleted file mode 100644 index ba622598..00000000 --- a/example/topics/subscriber/subscription-serialization-modes-example.cjs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -const rclnodejs = require('../../../index.js').default; - -/** - * This example demonstrates the use of serialization modes for subscriptions. - * Serialization modes allow you to control how TypedArrays are handled in messages: - * - 'default': Use native rclnodejs behavior (respects enableTypedArray setting) - * - 'plain': Convert TypedArrays to regular arrays - * - 'json': Fully JSON-safe (converts TypedArrays, BigInt, Infinity, etc.) - */ -async function main() { - await rclnodejs.init(); - const node = new rclnodejs.Node('serialization_modes_example_node'); - - // Default mode: 'default' - uses native rclnodejs behavior - node.createSubscription( - 'sensor_msgs/msg/LaserScan', - '/laser_scan', - { serializationMode: 'default' }, - (msg) => { - console.log( - `[TYPED] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}` - ); - } - ); - - // Plain mode: converts TypedArrays to regular arrays - node.createSubscription( - 'sensor_msgs/msg/LaserScan', - '/laser_scan', - { serializationMode: 'plain' }, - (msg) => { - console.log( - `[PLAIN] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}` - ); - } - ); - - // JSON mode: fully JSON-safe - node.createSubscription( - 'sensor_msgs/msg/LaserScan', - '/laser_scan', - { serializationMode: 'json' }, - (msg) => { - console.log( - `[JSON] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}, JSON-safe: ${canStringifyJSON(msg)}` - ); - } - ); - - node.spin(); -} - -function canStringifyJSON(obj) { - try { - JSON.stringify(obj); - return true; - } catch { - return false; - } -} - -main().catch(console.error); diff --git a/example/topics/subscriber/subscription-serialization-modes-example.mjs b/example/topics/subscriber/subscription-serialization-modes-example.mjs new file mode 100644 index 00000000..161c49ae --- /dev/null +++ b/example/topics/subscriber/subscription-serialization-modes-example.mjs @@ -0,0 +1,75 @@ +// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; + +/** + * This example demonstrates the use of serialization modes for subscriptions. + * Serialization modes allow you to control how TypedArrays are handled in messages: + * - 'default': Use native rclnodejs behavior (respects enableTypedArray setting) + * - 'plain': Convert TypedArrays to regular arrays + * - 'json': Fully JSON-safe (converts TypedArrays, BigInt, Infinity, etc.) + */ +function canStringifyJSON(obj) { + try { + JSON.stringify(obj); + return true; + } catch { + return false; + } +} + +await rclnodejs.init(); +const node = new rclnodejs.Node('serialization_modes_example_node'); + +// Default mode: 'default' - uses native rclnodejs behavior +node.createSubscription( + 'sensor_msgs/msg/LaserScan', + '/laser_scan', + { serializationMode: 'default' }, + (msg) => { + console.log( + `[TYPED] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}` + ); + } +); + +// Plain mode: converts TypedArrays to regular arrays +node.createSubscription( + 'sensor_msgs/msg/LaserScan', + '/laser_scan', + { serializationMode: 'plain' }, + (msg) => { + console.log( + `[PLAIN] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}` + ); + } +); + +// JSON mode: fully JSON-safe +node.createSubscription( + 'sensor_msgs/msg/LaserScan', + '/laser_scan', + { serializationMode: 'json' }, + (msg) => { + console.log( + `[JSON] ranges: ${msg.ranges ? msg.ranges.constructor.name : 'undefined'}, JSON-safe: ${canStringifyJSON(msg)}` + ); + } +); + +node.spin(); diff --git a/example/topics/subscriber/subscription-service-event-example.cjs b/example/topics/subscriber/subscription-service-event-example.mjs similarity index 55% rename from example/topics/subscriber/subscription-service-event-example.cjs rename to example/topics/subscriber/subscription-service-event-example.mjs index b52832fec..2116e811 100644 --- a/example/topics/subscriber/subscription-service-event-example.cjs +++ b/example/topics/subscriber/subscription-service-event-example.mjs @@ -12,24 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. -const rclnodejs = require('../../../index.js').default; +import rclnodejs from '../../../index.js'; -async function main() { - await rclnodejs.init(); - const node = new rclnodejs.Node('subscription_service_event_example_node'); - let count = 0; +await rclnodejs.init(); +const node = new rclnodejs.Node('subscription_service_event_example_node'); +let count = 0; - node.createSubscription( - 'example_interfaces/srv/AddTwoInts_Event', - '/add_two_ints/_service_event', - (event) => { - console.log(`Received event No. ${++count}: `, event); - } - ); +node.createSubscription( + 'example_interfaces/srv/AddTwoInts_Event', + '/add_two_ints/_service_event', + (event) => { + console.log(`Received event No. ${++count}: `, event); + } +); - node.spin(); -} - -main(); +node.spin(); diff --git a/example/topics/validator/validator-example.cjs b/example/topics/validator/validator-example.mjs similarity index 84% rename from example/topics/validator/validator-example.cjs rename to example/topics/validator/validator-example.mjs index da25479f..6e42dc91 100644 --- a/example/topics/validator/validator-example.cjs +++ b/example/topics/validator/validator-example.mjs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; +// From an installed package you would write `import rclnodejs from 'rclnodejs'`; +// run from this checkout we import the source entry point directly. + +import rclnodejs from '../../../index.js'; -const rclnodejs = require('../../../index.js').default; const { validator } = rclnodejs; console.log( diff --git a/tools/jsdoc/package.json b/tools/jsdoc/package.json new file mode 100644 index 00000000..5bbefffb --- /dev/null +++ b/tools/jsdoc/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +}