Skip to content

Commit 6b96e9c

Browse files
committed
Add RMW serialize and deserialize functions
1 parent 688791e commit 6b96e9c

File tree

10 files changed

+283
-2
lines changed

10 files changed

+283
-2
lines changed

binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
'./src/rcl_names_bindings.cpp',
3636
'./src/rcl_node_bindings.cpp',
3737
'./src/rcl_publisher_bindings.cpp',
38+
'./src/rcl_serialization_bindings.cpp',
3839
'./src/rcl_service_bindings.cpp',
3940
'./src/rcl_subscription_bindings.cpp',
4041
'./src/rcl_time_point_bindings.cpp',

index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ const {
5353
getActionNamesAndTypes,
5454
} = require('./lib/action/graph.js');
5555
const ServiceIntrospectionStates = require('./lib/service_introspection.js');
56+
const {
57+
serializeMessage,
58+
deserializeMessage,
59+
} = require('./lib/serialization.js');
5660

5761
/**
5862
* Get the version of the generator that was used for the currently present interfaces.
@@ -182,6 +186,20 @@ let rcl = {
182186

183187
/** {@link getActionNamesAndTypes} function */
184188
getActionNamesAndTypes: getActionNamesAndTypes,
189+
/**
190+
* Serialize a message to a buffer.
191+
* @param {Object} message - The message to serialize.
192+
* @return {Buffer} The serialized message as a Buffer.
193+
*/
194+
serializeMessage: serializeMessage,
195+
196+
/**
197+
* Deserialize a message from a buffer.
198+
* @param {Buffer} buffer - The buffer containing the serialized message.
199+
* @param {Object} typeClass - The class of the message type to deserialize into.
200+
* @return {Object} The deserialized message object.
201+
*/
202+
deserializeMessage: deserializeMessage,
185203

186204
/**
187205
* Create and initialize a node.

lib/node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1816,7 +1816,7 @@ class Node extends rclnodejs.ShadowNode {
18161816
return result;
18171817
});
18181818

1819-
Type.destoryRawROS(message);
1819+
Type.destroyRawROS(message);
18201820
}
18211821

18221822
_addActionClient(actionClient) {

lib/serialization.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2025, The Robot Web Tools Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const rclnodejs = require('bindings')('rclnodejs');
18+
19+
class Serialization {
20+
static serializeMessage(message, typeClass) {
21+
if (!(message instanceof typeClass)) {
22+
throw new TypeError('Message must be a valid ros2 message type');
23+
}
24+
return rclnodejs.serialize(
25+
typeClass.type().pkgName,
26+
typeClass.type().subFolder,
27+
typeClass.type().interfaceName,
28+
message.serialize()
29+
);
30+
}
31+
32+
static deserializeMessage(buffer, typeClass) {
33+
if (!(buffer instanceof Buffer)) {
34+
throw new TypeError('Buffer is required for deserialization');
35+
}
36+
const rosMsg = new typeClass();
37+
rclnodejs.deserialize(
38+
typeClass.type().pkgName,
39+
typeClass.type().subFolder,
40+
typeClass.type().interfaceName,
41+
buffer,
42+
rosMsg.toRawROS()
43+
);
44+
return rosMsg;
45+
}
46+
}
47+
48+
module.exports = Serialization;

rosidl_gen/templates/message.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ class {{=objectWrapper}} {
506506
{{~}}
507507
}
508508

509-
static destoryRawROS(msg) {
509+
static destroyRawROS(msg) {
510510
{{=objectWrapper}}.freeStruct(msg.refObject);
511511
}
512512

src/addon.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "rcl_names_bindings.h"
3131
#include "rcl_node_bindings.h"
3232
#include "rcl_publisher_bindings.h"
33+
#include "rcl_serialization_bindings.h"
3334
#include "rcl_service_bindings.h"
3435
#include "rcl_subscription_bindings.h"
3536
#include "rcl_time_point_bindings.h"
@@ -88,6 +89,7 @@ Napi::Object InitModule(Napi::Env env, Napi::Object exports) {
8889
rclnodejs::InitEventHandleBindings(env, exports);
8990
#endif
9091
rclnodejs::InitLifecycleBindings(env, exports);
92+
rclnodejs::InitSerializationBindings(env, exports);
9193
rclnodejs::ShadowNode::Init(env, exports);
9294
rclnodejs::RclHandle::Init(env, exports);
9395

src/rcl_serialization_bindings.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) 2025, The Robot Web Tools Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "rcl_serialization_bindings.h"
16+
17+
#include <rmw/rmw.h>
18+
#include <rmw/serialized_message.h>
19+
#include <rosidl_runtime_c/message_type_support_struct.h>
20+
21+
#include <string>
22+
23+
#include "rcl_utilities.h"
24+
25+
namespace {
26+
27+
struct SerializedMessage {
28+
explicit SerializedMessage(Napi::Env env, rcutils_allocator_t allocator)
29+
: env(env) {
30+
rcl_msg = rmw_get_zero_initialized_serialized_message();
31+
rcutils_ret_t rcutils_ret =
32+
rmw_serialized_message_init(&rcl_msg, 0u, &allocator);
33+
if (RCUTILS_RET_OK != rcutils_ret) {
34+
Napi::Error::New(env, "failed to initialize serialized message")
35+
.ThrowAsJavaScriptException();
36+
}
37+
}
38+
39+
~SerializedMessage() {
40+
rcutils_ret_t ret = rmw_serialized_message_fini(&rcl_msg);
41+
if (RCUTILS_RET_OK != ret) {
42+
Napi::Error::New(env,
43+
"failed to fini rcl_serialized_msg_t in destructor.")
44+
.ThrowAsJavaScriptException();
45+
rcutils_reset_error();
46+
}
47+
}
48+
49+
rcl_serialized_message_t rcl_msg;
50+
Napi::Env env;
51+
};
52+
53+
} // namespace
54+
55+
namespace rclnodejs {
56+
57+
Napi::Value Serialize(const Napi::CallbackInfo& info) {
58+
Napi::Env env = info.Env();
59+
60+
std::string package_name = info[0].As<Napi::String>().Utf8Value();
61+
std::string message_sub_folder = info[1].As<Napi::String>().Utf8Value();
62+
std::string message_name = info[2].As<Napi::String>().Utf8Value();
63+
void* ros_msg = info[3].As<Napi::Buffer<char>>().Data();
64+
const rosidl_message_type_support_t* ts =
65+
GetMessageTypeSupport(package_name, message_sub_folder, message_name);
66+
67+
// Create a serialized message object.
68+
SerializedMessage serialized_msg(env, rcutils_get_default_allocator());
69+
70+
// Serialize
71+
rmw_ret_t rmw_ret = rmw_serialize(ros_msg, ts, &serialized_msg.rcl_msg);
72+
if (RMW_RET_OK != rmw_ret) {
73+
Napi::Error::New(env, "Failed to serialize ROS message")
74+
.ThrowAsJavaScriptException();
75+
return env.Undefined();
76+
}
77+
Napi::Buffer<char> buffer = Napi::Buffer<char>::Copy(
78+
env, reinterpret_cast<const char*>(serialized_msg.rcl_msg.buffer),
79+
serialized_msg.rcl_msg.buffer_length);
80+
return buffer;
81+
}
82+
83+
Napi::Value Deserialize(const Napi::CallbackInfo& info) {
84+
Napi::Env env = info.Env();
85+
86+
std::string package_name = info[0].As<Napi::String>().Utf8Value();
87+
std::string message_sub_folder = info[1].As<Napi::String>().Utf8Value();
88+
std::string message_name = info[2].As<Napi::String>().Utf8Value();
89+
const rosidl_message_type_support_t* ts =
90+
GetMessageTypeSupport(package_name, message_sub_folder, message_name);
91+
Napi::Buffer<char> serialized = info[3].As<Napi::Buffer<char>>();
92+
void* msg_taken = info[4].As<Napi::Buffer<char>>().Data();
93+
94+
// Create a serialized message object
95+
rcl_serialized_message_t serialized_msg =
96+
rmw_get_zero_initialized_serialized_message();
97+
// Just copy pointer to avoid extra allocation and copy
98+
serialized_msg.buffer_capacity = serialized.Length();
99+
serialized_msg.buffer_length = serialized.Length();
100+
serialized_msg.buffer = reinterpret_cast<uint8_t*>(serialized.Data());
101+
102+
// Deserialize
103+
rmw_ret_t rmw_ret = rmw_deserialize(&serialized_msg, ts, msg_taken);
104+
105+
if (RMW_RET_OK != rmw_ret) {
106+
Napi::Error::New(env, "failed to deserialize ROS message")
107+
.ThrowAsJavaScriptException();
108+
}
109+
110+
return env.Undefined();
111+
}
112+
113+
Napi::Object InitSerializationBindings(Napi::Env env, Napi::Object exports) {
114+
exports.Set("serialize", Napi::Function::New(env, Serialize));
115+
exports.Set("deserialize", Napi::Function::New(env, Deserialize));
116+
return exports;
117+
}
118+
119+
} // namespace rclnodejs

src/rcl_serialization_bindings.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025, The Robot Web Tools Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef SRC_RCL_SERIALIZATION_BINDINGS_H_
16+
#define SRC_RCL_SERIALIZATION_BINDINGS_H_
17+
18+
#include <napi.h>
19+
20+
namespace rclnodejs {
21+
22+
Napi::Object InitSerializationBindings(Napi::Env env, Napi::Object exports);
23+
24+
}
25+
26+
#endif // SRC_RCL_SERIALIZATION_BINDINGS_H_

test/test-serialization.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2025, The Robot Web Tools Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const assert = require('assert');
16+
const rclnodejs = require('../index.js');
17+
const { serializeMessage, deserializeMessage } = require('../index.js');
18+
19+
describe('rclnodejs publisher test suite', function () {
20+
[
21+
{
22+
type: 'std_msgs/msg/String',
23+
value: 'Hello ROS 2.0 Serialization!',
24+
},
25+
{
26+
type: 'std_msgs/msg/MultiArrayDimension',
27+
value: { label: 'label name 0', size: 256, stride: 4 },
28+
},
29+
{
30+
type: 'geometry_msgs/msg/Point',
31+
value: { x: 1.5, y: 2.75, z: 3.0 },
32+
},
33+
].forEach((testCase) => {
34+
it('Test serialize a message of type ' + testCase.type, function () {
35+
const MyMessage = rclnodejs.require(testCase.type);
36+
const rosMsg = new MyMessage(testCase.value);
37+
const buffer = serializeMessage(rosMsg, MyMessage);
38+
39+
assert(buffer instanceof Buffer);
40+
const deserializedRosMsg = deserializeMessage(buffer, MyMessage);
41+
assert.deepStrictEqual(
42+
deserializedRosMsg.toPlainObject(),
43+
rosMsg.toPlainObject()
44+
);
45+
});
46+
});
47+
});

types/index.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/// <reference path="./base.d.ts" />
22

33
declare module 'rclnodejs' {
4+
type Class = new (...args: any[]) => any;
5+
46
/**
57
* Create a node.
68
*
@@ -187,4 +189,22 @@ declare module 'rclnodejs' {
187189
* @returns An array of the names and types.
188190
*/
189191
function getActionNamesAndTypes(node: Node): NamesAndTypesQueryResult;
192+
193+
/**
194+
* Serialize a message to a Buffer.
195+
*
196+
* @param message - The message to be serialized.
197+
* @param typeClass - The type class of the message.
198+
* @returns A Buffer containing the serialized message.
199+
*/
200+
function serializeMessage(message: object, typeClass: Class): Buffer;
201+
202+
/**
203+
* Deserialize a message from a Buffer.
204+
*
205+
* @param buffer - The Buffer containing the serialized message.
206+
* @param typeClass - The type class of the message.
207+
* @returns An Object representing the deserialized message.
208+
*/
209+
function deserializeMessage(buffer: Buffer, typeClass: Class): object;
190210
}

0 commit comments

Comments
 (0)