RobotWebTools / ros2-web-bridge

Bridging your browser to the ROS 2.0
Apache License 2.0
202 stars 67 forks source link

Pose stamped message not published #160

Open DentOpt opened 3 years ago

DentOpt commented 3 years ago

Hi y'all,

I am using your minimum example to build a publisher for a pose stamped message. I can publishing an empty message, a twist message, but no Pose and no PoseStamped message.
It is creating their publishers as I can echo the topic, but nothing is written into the Pose and PoseStamped topics. Either my way of defining the topic message variable is wrong:

var posestamped_msg = new ROSLIB.Topic({
            header: {
              stamp: {
                secs : 0, 
                nsecs : 100
              },
              frame_id : "world"               //tf_prefix+"/"+map_frame;
            },
            pose: {
              position: {
                x : 0.0,
                y : 0.0,
                z : 0.0
              },
              orientation: {
                x : 0.0,
                y : 0.0,
                z : 0.0,
                w : 1.0
              }
            }
  });

or this is a bug in ROSLIB. You can find my example.html file bellow. It would be great if you could give me hint on how to debug this issue. Thanks in advance!

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
<script src="https://static.robotwebtools.org/roslibjs/current/roslib.js"></script>
<!--<script src="js/include/jscontroller/posestamped.js"></script> -->

<script>
  // Connecting to ROS
  // -----------------
  var ros = new ROSLIB.Ros();

  // If there is an error on the backend, an 'error' emit will be emitted.
  ros.on('error', function(error) {
    document.getElementById('connecting').style.display = 'none';
    document.getElementById('connected').style.display = 'none';
    document.getElementById('closed').style.display = 'none';
    document.getElementById('error').style.display = 'inline';
    console.log(error);
  });

  // Find out exactly when we made a connection.
  ros.on('connection', function() {
    console.log('Connection made!');
    document.getElementById('connecting').style.display = 'none';
    document.getElementById('error').style.display = 'none';
    document.getElementById('closed').style.display = 'none';
    document.getElementById('connected').style.display = 'inline';
  });

  ros.on('close', function() {
    console.log('Connection closed.');
    document.getElementById('connecting').style.display = 'none';
    document.getElementById('connected').style.display = 'none';
    document.getElementById('closed').style.display = 'inline';
  });

  // Create a connection to the rosbridge WebSocket server.
  ros.connect('ws://localhost:9090');

  // Publish a Topic
  var posestamped_publisher = new ROSLIB.Topic({
    ros : ros,
    name : '/posestamped',
    messageType : 'geometry_msgs/PoseStamped'
  });

  var posestamped_msg = new ROSLIB.Topic({
            header: {
              stamp: {
                secs : 0, 
                nsecs : 100
              },
              frame_id : "world"               //tf_prefix+"/"+map_frame;
            },
            pose: {
              position: {
                x : 0.0,
                y : 0.0,
                z : 0.0
              },
              orientation: {
                x : 0.0,
                y : 0.0,
                z : 0.0,
                w : 1.0
              }
            }
  });

  // Publish a Topic
  var pose_publisher = new ROSLIB.Topic({
    ros : ros,
    name : '/pose',
    messageType : 'geometry_msgs/Pose'
  });

  var pose_msg = new ROSLIB.Topic({
    position: {
      x : 0.0,
      y : 0.0,
      z : 0.0
    },
    orientation: {
      x : 0.0,
      y : 0.0,
      z : 0.0,
      w : 1.0
    }        
  });

  var twist_publisher = new ROSLIB.Topic({
    ros : ros,
    name : '/twist',
    messageType : 'geometry_msgs/Twist'
  });

  var twist_msg = new ROSLIB.Message({
    linear: {
      x: 0,
      y: 1,
      z: 0
    },
    angular: {
      x: 0,
      y: 1,
      z: 0
    }
  });

  var empty_publisher = new ROSLIB.Topic({
    ros : ros,
    name : '/empty',
    messageType : 'std_msgs/Empty'
  });

  var empty_msg = new ROSLIB.Message({});

  var count = 0;
  setInterval(() => {
    var msg = 'hello from ros2bridge ' + count++;

    posestamped_publisher.publish(posestamped_msg);
    pose_publisher.publish(pose_msg);
    twist_publisher.publish(twist_msg);
    empty_publisher.publish(empty_msg);
    document.getElementById("publisher").innerText = count;
  }, 1000);
</script>
</head>

<body>
  <h1>Simple Publisher Example</h1>
  <p>This example will pubilish a topic named "example_topic".</p>
  <div id="statusIndicator">
    <p id="connecting">
      Connecting to rosbridge...
    </p>
    <p id="connected" style="color:#00D600; display:none">
      Connected
    </p>
    <p id="error" style="color:#FF0000; display:none">
      Error in the backend!
    </p>
    <p id="closed" style="display:none">
      Connection closed.
    </p>
  </div>
  <div>
    <p>
      <b>Publish message: </b>
      <span id="publisher"></span>
    </p>
  </div>
</body>
</html>
mis-eu commented 3 years ago

I can confirm the same problem, the /topic is listed, but the PoseStamped message echo does not appear

Could anyone please verify PoseStamped JSON structure below is valid? (slightly linted from above post, with added sequence int)

  let PoseStamped = new ROSLIB.Message({
            header: {
              seq: 1,
              stamp: {
                  secs: 0,
                  nsecs: 100
              },
              frame_id: "world"               
            },
            pose: {
                position: {
                    x: 0.0,
                    y: 0.0,
                    z: 0.0
                },
                orientation: {
                    x: 0.0,
                    y: 0.0,
                    z: 0.0,
                    w: 1.0
                }
            }
        });
minggangw commented 3 years ago

Sorry for being late, would you please try

DEBUG=ros2-web-bridge* node bin/rosbridge.js

to see any useful debug information?

mis-eu commented 3 years ago

Thanks very much for your response @minggangw

Bridge Response: {"op":"status","level":"error","msg":"publish: TypeError: Invalid argument: frame_id in Header","id"

to which I've tried both: frame_id : "map" and frame_id : "world"

without success

~/ros2-web-bridge$ DEBUG=ros2-web-bridge* node bin/rosbridge.js ros2-web-bridge:index ROS2 node started +0ms ros2-web-bridge:index Starting server on port 9090 +1ms Websocket started on ws://localhost:9090 ros2-web-bridge:Bridge Status level set to error (0) +0ms ros2-web-bridge:Bridge Web bridge de9db5da-ecd4-4149-98ae-d8f3b6af7173 is created +0ms (node:24134) DeprecationWarning: Deep requiring like `const uuidv4 = require('uuid/v4');` is deprecated as of uuid@7.x. Please require the top-level module when using the Node.js CommonJS module or use ECMAScript Modules when bundling for the browser. See https://github.com/uuidjs/uuid#deep-requires-now-deprecated for more information. ros2-web-bridge:Bridge JSON command received: {"op":"advertise","id":"advertise:/move_base_simple/goal:1","type":"geometry_msgs/PoseStamped","topic":"/move_base_simple/goal","latch":false,"queue_size":100} +991ms ros2-web-bridge:Bridge advertise a topic: /move_base_simple/goal +1ms ros2-web-bridge:ResourceProvider Publisher has been created, and the topic name is /move_base_simple/goal. +0ms ros2-web-bridge:Bridge Suppressed: {"op":"status","level":"none","msg":"OK","id":"advertise:/move_base_simple/goal:1"} +8ms ros2-web-bridge:Bridge JSON command received: {"op":"publish","id":"publish:/move_base_simple/goal:2","topic":"/move_base_simple/goal","msg":{"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true},"latch":false} +35ms ros2-web-bridge:Bridge Publish a topic named /move_base_simple/goal with {"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true} +0ms ros2-web-bridge:Bridge Response: {"op":"status","level":"error","msg":"publish: TypeError: Invalid argument: frame_id in Header","id":"publish:/move_base_simple/goal:2"} +1ms ros2-web-bridge:Bridge JSON command received: {"op":"advertise","id":"advertise:/pose:3","type":"geometry_msgs/Pose","topic":"/pose","latch":false,"queue_size":100} +1ms ros2-web-bridge:Bridge advertise a topic: /pose +0ms ros2-web-bridge:ResourceProvider Publisher has been created, and the topic name is /pose. +39ms ros2-web-bridge:Bridge Suppressed: {"op":"status","level":"none","msg":"OK","id":"advertise:/pose:3"} +1ms ros2-web-bridge:Bridge JSON command received: {"op":"publish","id":"publish:/pose:4","topic":"/pose","msg":{"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true},"latch":false} +0ms ros2-web-bridge:Bridge Publish a topic named /pose with {"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true} +0ms ros2-web-bridge:Bridge Response: {"op":"status","level":"error","msg":"publish: TypeError: Invalid argument: x in Point","id":"publish:/pose:4"} +0ms ros2-web-bridge:Bridge JSON command received: {"op":"advertise","id":"advertise:/twist:5","type":"geometry_msgs/Twist","topic":"/twist","latch":false,"queue_size":100} +1ms ros2-web-bridge:Bridge advertise a topic: /twist +0ms ros2-web-bridge:ResourceProvider Publisher has been created, and the topic name is /twist. +3ms ros2-web-bridge:Bridge Suppressed: {"op":"status","level":"none","msg":"OK","id":"advertise:/twist:5"} +2ms ros2-web-bridge:Bridge JSON command received: {"op":"publish","id":"publish:/twist:6","topic":"/twist","msg":{"linear":{"x":0,"y":1,"z":0},"angular":{"x":0,"y":1,"z":0}},"latch":false} +0ms ros2-web-bridge:Bridge Publish a topic named /twist with {"linear":{"x":0,"y":1,"z":0},"angular":{"x":0,"y":1,"z":0}} +0ms ros2-web-bridge:Bridge Suppressed: {"op":"status","level":"none","msg":"OK","id":"publish:/twist:6"} +1ms ros2-web-bridge:Bridge JSON command received: {"op":"advertise","id":"advertise:/empty:7","type":"std_msgs/Empty","topic":"/empty","latch":false,"queue_size":100} +0ms
minggangw commented 3 years ago

I noticed there was an error

ros2-web-bridge:Bridge JSON command received: {"op":"publish","id":"publish:/pose:4","topic":"/pose","msg":{"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true},"latch":false} +0ms ros2-web-bridge:Bridge Publish a topic named /pose with {"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true} +0ms ros2-web-bridge:Bridge Response: {"op":"status","level":"error","msg":"publish: TypeError: Invalid argument: x in Point","id":"publish:/pose:4"} +0ms
mis-eu commented 3 years ago

error in PoseStamped is "msg":"publish: TypeError: Invalid argument: frame_id in Header","id":"publish:/move_base_simple/goal:2"}

error in Pose is "msg":"publish: TypeError: Invalid argument: x in Point","id":"publish:/pose:4"}

do the errors originate from JSON?

/PoseStamped

  var PoseStamped = new ROSLIB.Topic({
            header: {
              stamp: {
                secs: 0,
                nsecs: 100
              },
              frame_id: "world"               //tf_prefix+"/"+map_frame;
            },
            pose: {
              position: {
                x: 0.0,
                y: 0.0,
                z: 0.0
              },
              orientation: {
                x: 0.0,
                y: 0.0,
                z: 0.0,
                w: 1.0
              }
            }
  });

/pose

var pose_publisher = new ROSLIB.Topic({
    ros : ros,
    name : '/pose',
    messageType : 'geometry_msgs/Pose'
  });

  var pose_msg = new ROSLIB.Topic({
    position: {
      x: 0.0,
      y: 0.0,
      z: 0.0
    },
    orientation: {
      x: 0.0,
      y: 0.0,
      z: 0.0,
      w: 1.0
    }
  });
minggangw commented 3 years ago

The Time.msg is ROS2 is different from that of ROS1, please see https://github.com/ros2/rcl_interfaces/blob/master/builtin_interfaces/msg/Time.msg, please reference the example to see if it will work, thanks!

mis-eu commented 3 years ago

thanks for the information!

unfortunately, the Bridge response Error remains exactly the same as before. I´ve matched the ROS2 Time.msg definition you referenced above, as well as tried variations in frame id: main frame, world frame, map frame.

No dice.

mis-eu commented 3 years ago

Sharing the complete code for verification:

```

Simple Publisher Example

This example will pubilish a topic named "example_topic".

Connecting to rosbridge...

Publish message:

```
minggangw commented 3 years ago
  ros2-web-bridge:Bridge Publish a topic named /move_base_simple/goal with {"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true} +1ms
  ros2-web-bridge:Bridge Response: {"op":"status","level":"error","msg":"publish: TypeError: Invalid argument: frame_id in Header","id":"publish:/move_base_simple/goal:77"} +0ms

The JSON string that the bridge received is

{"isAdvertised":false,"compression":"none","throttle_rate":0,"latch":false,"queue_size":100,"queue_length":0,"reconnect_on_close":true} 

I think that is why there was an error thrown, this JSON is supposed to carry PoseStamped object, apparently, it was not. (The string was sent by ROSLib through websockets)

mis-eu commented 3 years ago

@minggangw how would you verify publishing PoseStamped using clientside ROSlib / JSON ?

mis-eu commented 3 years ago

@minggangw otherwise, how can the rclnodejs module be implemented in clientside HTML, where require is not supported?

        const rclnodejs = require('rclnodejs');
        rclnodejs.init().then(() => {
          const node = rclnodejs.createNode('publisher_example_node');
          const publisher = node.createPublisher('std_msgs/msg/String', 'topic');
          publisher.publish(`Hello ROS 2 from rclnodejs`);
          rclnodejs.spin(node);
        });

I tried importing rcl as a module:


<script type="module" src="/ros2-web-bridge/examples/html/publishnode.js"></script>

import {rclnodejs} from '../../node_modules/rclnodejs/index.js'

//const rclnodejs = require('rclnodejs');
rclnodejs.init().then(() => {
          const node = rclnodejs.createNode('publisher_example_node');
          const publisher = node.createPublisher('std_msgs/msg/String', 'topic');
          publisher.publish(`Hello ROS 2 from rclnodejs`);
          rclnodejs.spin(node);
        });

but how to export it as a module within ros2-web-bridge/node_modules/rclnodejs/index.js ?

mis-eu commented 3 years ago

The Time.msg is ROS2 is different from that of ROS1, please see https://github.com/ros2/rcl_interfaces/blob/master/builtin_interfaces/msg/Time.msg, please reference the example to see if it will work, thanks!

Here is the above Example plugged into the /demo/ index.html

The problem is that even when loaded into the HTML page, the same error is thrown, making it not possible to spin the rclnode publisher:

publisher-message-example.js:19 Uncaught ReferenceError: require is not defined at publisher-message-example.js:19

Expand below for HTML

<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ROS</title>
    <meta name="keywords" content="roslibjs, ros2-web-bridge and rclnodejs" />
    <meta name="author" content="ros" />
    <link rel="stylesheet" type="text/css" href="css/demo.css" />
    <link rel="stylesheet" type="text/css" href="css/component.css" />
    <script src="https://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
    <script src="https://static.robotwebtools.org/roslibjs/current/roslib.min.js"></script>
    <script src="https://static.robotwebtools.org/EaselJS/current/easeljs.min.js"></script>
    <script src="https://static.robotwebtools.org/ros2djs/current/ros2d.min.js"></script>
</head>

<body>
    <div id="perspective" class="perspective effect-movedown">
        <div class="container">
            <div class="wrapper">

                    <div class="log" id="log"></div>

            </div>
        </div>
    </div>

    <script src="publisher-message-example.js"></script>

    <script>
        let ros = new ROSLIB.Ros();
        let bridgeHost = window.location.hostname;
        let bridgePort = 9090;

        ros.on('connection', function() {
        console.log('bridge connected');
    });
    ros.on('error', function(data) {
        console.log('error');
    });
    ros.on('closed', function() {
        console.log('bridge closed');
    });
        ros.connect('ws://' + bridgeHost + ':' + bridgePort);

        let publisher = new rclnodejs(ros, {
            divName: 'log',
        //width: 400,
        //height: 400
        });
        //let webRosController = new WebRosController(ros, ros2dmap);
        //let btnState = new ButtonState();

    </script>
    </body>
</html>
minggangw commented 3 years ago

Hi @mis-eu thanks for your continuous investigation on this issue. require is kind of CommonJS module concept, which the browser doesn't support directly. Some library, e.g. browserify, can support it in your browser. But rclnodejs will load a C++ addon that has to use a Node.js, so you cannot import the module in your browser. My suggestion is:

mis-eu commented 3 years ago

Hello @minggangw thanks very much for the extensive examples provided in rclnodejs package as well as the good rclnodejs typescript use-cases documentation.

I suspected the C++ codebase could not be made available to the Browser without a JavaScript loader.

Thanks for confirming this and the electron recommendation, though the question remains why ROSlib is not tunneling the Pose and PoseStamped JSON object correctly. 🤔💭

minggangw commented 3 years ago

Maybe you could log in the roslibjs at https://github.com/RobotWebTools/roslibjs/blob/969f568ceeb15600ebea1a5047bca5b34b7e58ad/src/util/workerSocketImpl.js#L27 to see if the JSON string is correct practically when it's being sent, and if you want to do more in ROS2 by JavaScript, using rclnodejs is a good idea in the long term :smile:

mis-eu commented 3 years ago

It is interesting that Node is throwing the same error rosbridge returned for the ROSlib JSON of PoseStamped discussed above.

Running node on the publisher-example.js updated to geometry_msgs/msg/PoseStamped also returns TypeError: Invalid argument: frame_id in Header

The publisher-example.js template only specifies the message Name and Topic, not the object format.

script: $ node publisher-example-posestamped.js

output:

Publishing message: 0
/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/std_msgs/std_msgs__msg__Header.js:71
        throw new TypeError('Invalid argument: frame_id in Header');
        ^

TypeError: Invalid argument: frame_id in Header
    at HeaderWrapper.freeze (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/std_msgs/std_msgs__msg__Header.js:71:15)
    at PoseStampedWrapper.freeze (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgs__msg__PoseStamped.js:69:32)
    at PoseStampedWrapper.serialize (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgs__msg__PoseStamped.js:75:10)
    at Publisher.publish (/home/user/ros2-web-bridge/node_modules/rclnodejs/lib/publisher.js:58:38)
    at Timeout.setInterval [as _onTimeout] (/home/user/ros2-web-bridge/publishnode_posestamped.js:26:15)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)

PoseStamped is available under installed node_modules under: ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgsmsgPoseStamped.js

``` // This file is automatically generated by Intel rclnodejs // // *** DO NOT EDIT directly // 'use strict'; const ref = require('ref-napi'); const StructType = require('ref-struct-di')(ref); const ArrayType = require('ref-array-di')(ref); const primitiveTypes = require('../../rosidl_gen/primitive_types.js'); const deallocator = require('../../rosidl_gen/deallocator.js'); const translator = require('../../rosidl_gen/message_translator.js'); const HeaderWrapper = require('../../generated/std_msgs/std_msgs__msg__Header.js'); const PoseWrapper = require('../../generated/geometry_msgs/geometry_msgs__msg__Pose.js'); const PoseStampedRefStruct = StructType({ header: HeaderWrapper.refObjectType, pose: PoseWrapper.refObjectType, }); const PoseStampedRefArray = ArrayType(PoseStampedRefStruct); const PoseStampedRefStructArray = StructType({ data: PoseStampedRefArray, size: ref.types.size_t, capacity: ref.types.size_t }); // Define the wrapper class. class PoseStampedWrapper { constructor(other) { this._wrapperFields = {}; if (typeof other === 'object' && other._refObject) { this._refObject = new PoseStampedRefStruct(other._refObject.toObject()); this._wrapperFields.header = new HeaderWrapper(other._wrapperFields.header); this._wrapperFields.pose = new PoseWrapper(other._wrapperFields.pose); } else if (typeof other !== 'undefined') { this._initMembers(); translator.constructFromPlanObject(this, other); } else { this._initMembers(); } this.freeze(); } _initMembers() { this._refObject = new PoseStampedRefStruct(); this._wrapperFields.header = new HeaderWrapper(); this._wrapperFields.pose = new PoseWrapper(); } static createFromRefObject(refObject) { let self = new PoseStampedWrapper(); self.copyRefObject(refObject); return self; } static createArray() { return new PoseStampedArrayWrapper; } static get ArrayType() { return PoseStampedArrayWrapper; } static get refObjectArrayType() { return PoseStampedRefStructArray } static get refObjectType() { return PoseStampedRefStruct; } toRawROS() { this.freeze(true); return this._refObject.ref(); } freeze(own = false, checkConsistency = false) { if (checkConsistency) { } this._wrapperFields.header.freeze(own, checkConsistency); this._refObject.header = this._wrapperFields.header.refObject; this._wrapperFields.pose.freeze(own, checkConsistency); this._refObject.pose = this._wrapperFields.pose.refObject; } serialize() { this.freeze(false, true); return this._refObject.ref(); } deserialize(refObject) { this._wrapperFields.header.copyRefObject(refObject.header); this._wrapperFields.pose.copyRefObject(refObject.pose); } toPlainObject(enableTypedArray) { return translator.toPlainObject(this, enableTypedArray); } static freeStruct(refObject) { HeaderWrapper.freeStruct(refObject.header); PoseWrapper.freeStruct(refObject.pose); } static destoryRawROS(msg) { PoseStampedWrapper.freeStruct(msg.refObject); } static type() { return {pkgName: 'geometry_msgs', subFolder: 'msg', interfaceName: 'PoseStamped'}; } static isPrimitive() { return false; } static get isROSArray() { return false; } get refObject() { return this._refObject; } get header() { return this._wrapperFields.header; } set header(value) { if (value instanceof HeaderWrapper) { this._wrapperFields.header.copy(value); } else { this._wrapperFields.header.copy(new HeaderWrapper(value)); } } get pose() { return this._wrapperFields.pose; } set pose(value) { if (value instanceof PoseWrapper) { this._wrapperFields.pose.copy(value); } else { this._wrapperFields.pose.copy(new PoseWrapper(value)); } } copyRefObject(refObject) { this._refObject = new PoseStampedRefStruct(refObject.toObject()); this._wrapperFields.header.copyRefObject(this._refObject.header); this._wrapperFields.pose.copyRefObject(this._refObject.pose); } copy(other) { this._refObject = new PoseStampedRefStruct(other._refObject.toObject()); this._wrapperFields.header.copy(other._wrapperFields.header); this._wrapperFields.pose.copy(other._wrapperFields.pose); } static get classType() { return PoseStampedWrapper; } static get ROSMessageDef() { return {"constants":[],"fields":[{"name":"header","type":{"isArray":false,"arraySize":null,"isUpperBound":false,"isDynamicArray":false,"isFixedSizeArray":false,"pkgName":"std_msgs","type":"Header","stringUpperBound":null,"isPrimitiveType":false},"default_value":null},{"name":"pose","type":{"isArray":false,"arraySize":null,"isUpperBound":false,"isDynamicArray":false,"isFixedSizeArray":false,"pkgName":"geometry_msgs","type":"Pose","stringUpperBound":null,"isPrimitiveType":false},"default_value":null}],"baseType":{"pkgName":"geometry_msgs","type":"PoseStamped","stringUpperBound":null,"isPrimitiveType":false},"msgName":"PoseStamped"}; } hasMember(name) { let memberNames = ["header","pose"]; return memberNames.indexOf(name) !== -1; } } // Define the wrapper of array class. class PoseStampedArrayWrapper { constructor(size = 0) { this._resize(size); } toRawROS() { return this._refObject.ref(); } fill(values) { const length = values.length; this._resize(length); values.forEach((value, index) => { if (value instanceof PoseStampedWrapper) { this._wrappers[index].copy(value); } else { this._wrappers[index] = new PoseStampedWrapper(value); } }); } // Put all data currently stored in `this._wrappers` into `this._refObject` freeze(own) { this._wrappers.forEach((wrapper, index) => { wrapper.freeze(own); this._refArray[index] = wrapper.refObject; }); this._refObject.size = this._wrappers.length; this._refObject.capacity = this._wrappers.length; if (this._refObject.capacity === 0) { this._refObject.data = null } else { this._refObject.data = this._refArray.buffer; } } get refObject() { return this._refObject; } get data() { return this._wrappers; } get size() { return this._wrappers.length; } set size(value) { if (typeof value != 'number') { throw new TypeError('Invalid argument: should provide a number to PoseStampedArrayWrapper.size setter'); return; } return this._resize(value); } get capacity() { return this._wrappers.length; } set capacity(value) { if (typeof value != 'number') { throw new TypeError('Invalid argument: should provide a number to PoseStampedArrayWrapper.capacity setter'); } return this._resize(value); } get refObject() { return this._refObject; } _resize(size) { if (size < 0) { throw new RangeError('Invalid argument: should provide a positive number'); return; } this._refArray = new PoseStampedRefArray(size); this._refObject = new PoseStampedRefStructArray(); this._refObject.size = size; this._refObject.capacity = size; this._wrappers = new Array(); for (let i = 0; i < size; i++) { this._wrappers.push(new PoseStampedWrapper()); } } // Copy all data from `this._refObject` into `this._wrappers` copyRefObject(refObject) { this._refObject = refObject; let refObjectArray = this._refObject.data; refObjectArray.length = this._refObject.size; this._resize(this._refObject.size); for (let index = 0; index < this._refObject.size; index++) { this._wrappers[index].copyRefObject(refObjectArray[index]); } } copy(other) { if (! (other instanceof PoseStampedArrayWrapper)) { throw new TypeError('Invalid argument: should provide "PoseStampedArrayWrapper".'); } this._resize(other.size); // Array deep copy other._wrappers.forEach((wrapper, index) => { this._wrappers[index].copy(wrapper); }); } static freeArray(refObject) { let refObjectArray = refObject.data; refObjectArray.length = refObject.size; for (let index = 0; index < refObject.size; index++) { PoseStampedWrapper.freeStruct(refObjectArray[index]); } } static get elementType() { return PoseStampedWrapper; } static get isROSArray() { return true; } static get useTypedArray() { return false; } get classType() { return PoseStampedArrayWrapper; } } module.exports = PoseStampedWrapper; /* * The following is the original spec object coming from parser: { "constants": [], "fields": [ { "name": "header", "type": { "isArray": false, "arraySize": null, "isUpperBound": false, "isDynamicArray": false, "isFixedSizeArray": false, "pkgName": "std_msgs", "type": "Header", "stringUpperBound": null, "isPrimitiveType": false }, "default_value": null }, { "name": "pose", "type": { "isArray": false, "arraySize": null, "isUpperBound": false, "isDynamicArray": false, "isFixedSizeArray": false, "pkgName": "geometry_msgs", "type": "Pose", "stringUpperBound": null, "isPrimitiveType": false }, "default_value": null } ], "baseType": { "pkgName": "geometry_msgs", "type": "PoseStamped", "stringUpperBound": null, "isPrimitiveType": false }, "msgName": "PoseStamped" } */ ```
mis-eu commented 3 years ago

and running node on that same publisher-example.js but with geometry_msgs/msg/Twist on /cmd_vel topic throws the same Invalid argument: x in Vector 3 error as with roslibjs

$ node publisher-example-posestamped.js output error:

(node:6128) UnhandledPromiseRejectionWarning: TypeError: Invalid argument: x in Vector3
    at Vector3Wrapper.freeze (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgs__msg__Vector3.js:70:15)
    at TwistWrapper.freeze (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgs__msg__Twist.js:68:32)
    at TwistWrapper.serialize (/home/user/ros2-web-bridge/node_modules/rclnodejs/generated/geometry_msgs/geometry_msgs__msg__Twist.js:74:10)
    at Publisher.publish (/home/user/ros2-web-bridge/node_modules/rclnodejs/lib/publisher.js:58:38)
    at rclnodejs.init.then (/home/user/ros2-web-bridge/publishnode_nobindings.js:19:13)
(node:6128) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:6128) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

script: publisher-example-twist.js

#!/usr/bin/env node

'use strict';

/* eslint-disable camelcase */

const rclnodejs = require('rclnodejs');
const debug = require('debug')('rclnodejs:publisher');
const Entity = require('./entity.js');

rclnodejs.init().then(() => {
  const node = rclnodejs.createNode('publisher_example_node');
  const publisher = node.createPublisher('geometry_msgs/msg/Twist', 'cmd_vel');
  publisher.publish('cmd_vel');
    console.log('cmd_vel');
  rclnodejs.spin(node);
});
minggangw commented 3 years ago

I think the case is a little mess here, let me clarify it. I tried the example below and it works, and you can verify by writing a subscriber to log the topic you received. My env is: Nodejs v12.20.0, rclnodejs v0.17.0, ROS2 Foxy patch release3.

'use strict';

/* eslint-disable camelcase */

const rclnodejs = require('rclnodejs');

rclnodejs
  .init()
  .then(() => {
    const node = rclnodejs.createNode('publisher_message_example_node');
    const publisher = node.createPublisher(
      'geometry_msgs/msg/PoseStamped',
      'PoseStamped'
    );
    let count = 0;

    setInterval(function () {
      publisher.publish({
        header: {
          stamp: {
            sec : 0, 
            nanosec : 100
          },
          frame_id : "world"               //tf_prefix+"/"+map_frame;
        },
        pose: {
          position: {
            x : 0.0,
            y : 0.0,
            z : 0.0
          },
          orientation: {
            x : 0.0,
            y : 0.0,
            z : 0.0,
            w : 1.0
          }
        }
});
      console.log(`Publish ${++count} messages.`);
    }, 1000);

    rclnodejs.spin(node);
  })
  .catch((e) => {
    console.log(e);
  });

and to subscribe the topic

'use strict';

const rclnodejs = require('../index.js');

rclnodejs
  .init()
  .then(() => {
    const node = rclnodejs.createNode('subscription_message_example_node');
    let count = 0;

    node.createSubscription(
      'geometry_msgs/msg/PoseStamped',
      'PoseStamped',
      (pose) => {
        console.log(`Received message No. ${++count}: `, pose);
      }
    );

    rclnodejs.spin(node);
  })
  .catch((e) => {
    console.log(e);
  });
mis-eu commented 3 years ago

@minggangw Thanks very much!

These validate publishing of PoseStamped also on WSL2 ROS2 env: Foxy (Debian), Node v10.19.0, rclnodejs v0.17.0

I had to make just one change in the Subscriber script, to require (rclnodejs) instead of (../index.js) as below:

'use strict';

const rclnodejs = require('rclnodjejs');

rclnodejs
  .init()
  .then(() => {
    const node = rclnodejs.createNode('subscription_message_example_node');
    let count = 0;

    node.createSubscription(
      'geometry_msgs/msg/PoseStamped',
      'PoseStamped',
      (pose) => {
        console.log(`Received message No. ${++count}: `, pose);
      }
    );

    rclnodejs.spin(node);
  })
  .catch((e) => {
    console.log(e);
  });

image image

minggangw commented 3 years ago

That's it 🚀