RethinkRobotics-opensource / rosnodejs

Client library for writing ROS nodes in JavaScript with nodejs

Home Page:http://wiki.ros.org/rosnodejs/overview

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is there any possible reason that I might get unexpected numbers from rosnodejs?

trusktr opened this issue · comments

Hello again!

I'm curious: is rosnodejs compatible with any version of ROS? What about melodic (and sensor_msgs/PoinCloud2 messages)?

I'm getting weird numbers out of the data byte array, and I can't tell if it is my fault or not. Just wondering in case there's some reason rosnodejs could be giving wrong numbers (though I imagine that probably isn't the case).

Other people on my team are using native ROS stuff (like RViz) and they see data just fine. I'm the only one having issues, in JavaScript (and thus only one using rosnodejs).

I'm curious, are there any scenarios that you know of in which rosnodejs may give unexpected results?


I've got some code like this:

    const callback = async (msg: {data: Buffer; point_step: number}) => {
      const pointBuffer = new DataView(msg.data.buffer)

      const step = msg.point_step
      const pointCount = msg.data.length / step
      let timestampSec = 0, timestampUsec = 0, intensity = 0, ring = 0, x = 0, y = 0, z = 0

      for (let pointIndex = 0; pointIndex < pointCount; pointIndex += 1) {
        timestampSec = pointBuffer.getUint32(pointIndex * step + 0, true)
        timestampUsec = pointBuffer.getUint32(pointIndex * step + 4, true)
        x = pointBuffer.getFloat32(pointIndex * step + 8, true)
        y = pointBuffer.getFloat32(pointIndex * step + 12, true)
        z = pointBuffer.getFloat32(pointIndex * step + 16, true)
        intensity = pointBuffer.getUint8(pointIndex * step + 20)
        ring = pointBuffer.getUint8(pointIndex * step + 21)

        console.log(`${timestampSec}, ${timestampUsec}, ${x}, ${y}, ${z}, ${intensity}, ${ring}`)

        // ...
      }
    }

    const rosnodejs = (await import('rosnodejs')).default
    const node: any = await rosnodejs.initNode('vella_visualizer' /*, {onTheFly: true}*/)
    const {PointCloud2} = rosnodejs.require('sensor_msgs').msg
    const sub = node.subscribe('/hub1/vlp32_pcl', PointCloud2, callback, {})

where the + 8, + 12, etc, are based on the offset values of the PointField info of the fields array in the PointCloud2 messages.

I've double checked with others on my team, the offsets are correct, and I am calling the correct get methods of DataView (f.e. getFloat32). There is no issue with the code as written, as far as we can tell.

However the numbers make no sense, they are often tiny (e-40) or huge (e40). They are supposed to be within 1 to 30 meters (lidar data). Again, only I have this issue in the JS app, other people view the data just fine with other ROS utilities.

I've no idea what's wrong, and trying to get some ideas of what to look into.

Looks like a Node.js issue. If I we use the msg.data.readFloatLE(offset) method (from Buffer) it works, while the above new DataView(msg.data.buffer).readFloat32(offset, true) method does not.

As far as I'm aware the message serialization should be compatible with all versions of ROS. Melodic had very few changes compared to earlier versions - the main change I'm aware of has to do with transforms, but nothing about their serialization.

I have not used DataView before. It seems unlikely it would provide different results, but I have generally used the node.js Buffer api.

You might need to include the byteOffset and byteLength fields to the DataView constructor.

You might need to include the byteOffset and byteLength fields to the DataView constructor.

Thank you! Looks like that may be the case, as msg.data.byteLength has a different value than new DataView(msg.data.buffer).byteLength.

Is there a manual handling of ArrayBuffer in rosnodejs that would lead to the underlying ArrayBuffer having a different length than Buffer?

I would imagine this would be the case if (like the idea in the other threads) a common ArrayBuffer was being used behind the scenes for each Buffer returned to the user, but as you mentioned that currently isn't the case then I hadn't assumed there would be any length difference.

byteOffset and byteLength fields to the DataView constructor.

I'm curious now, although I can just use the Buffer methods now that I'm aware of those: how would I know what values to pass into DataView for those parameters? EDIT: msg.data.byteOffset should do the trick.

Being into WebGL and browser APIs, and being somewhat already familiar with ArrayBuffer, DataView, TypedArray, Uint8Array, etc, I simply thought I could use the DataView methods I've used before with so I went with that approach without much second thought.

Another team member then looked up Node.js Buffer, saw those other methods, gave them a try, and it worked. This highlighted the issue you hinted at with byteOffset.

The message data itself is assembled from chunks of data coming off the wire that may belong to different underlying buffers. Those buffers are all combined into a new buffer representing each message in its entirety - we could possibly copy into an existing buffer instead of making a new one as you suggested, though it may be hard in general to know if the buffer is free to write over or not.

The uint8 array deserialization returns a buffer that references the underlying aggregated message data.

it may be hard in general to know if the buffer is free to write over or not.

That's totally true. In that case the docs can say to be careful, and to rely on the message data synchronously rather than storing it and expecting to use it later (copy it to a new buffer in that case).

In my use case I'm just piping it to WebGL structures right in the callback synchronously.

I'm going to close this for now as reusing the buffer should be covered in #149 and the deserialization issue seems resolved.