rclnodejs
Version:
ROS2.0 JavaScript client with Node.js
522 lines (472 loc) • 14.9 kB
JavaScript
// Copyright (c) 2018 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 fse = require('../lib/utils.js');
const path = require('path');
const parser = require('../rosidl_parser/rosidl_parser.js');
const actionMsgs = require('./action_msgs.js');
const DistroUtils = require('../lib/distro.js');
const generateMessage = require('./templates/message-template.js');
const generateService = require('./templates/service-template.js');
const generateAction = require('./templates/action-template.js');
const generateServiceEvent = require('./templates/service-event-template.js');
const isDebug = !!process.argv.find((arg) => arg === '--debug');
/**
* Output generated code to disk. Do not overwrite
* an existing file. If file already exists do nothing.
* @param {string} dir
* @param {string} fileName
* @param {string} code
*/
async function writeGeneratedCode(dir, fileName, code) {
await fse.mkdirs(dir);
await fse.writeFile(path.join(dir, fileName), code.replace(/^\s*\n/gm, ''));
}
async function generateServiceJSStruct(
serviceInfo,
dir,
isActionService = true
) {
dir = path.join(dir, `${serviceInfo.pkgName}`);
const fileName =
serviceInfo.pkgName +
'__' +
serviceInfo.subFolder +
'__' +
serviceInfo.interfaceName +
'.js';
const generatedSrvCode = generateService({ serviceInfo: serviceInfo });
// We are going to only generate the service JavaScript file if it meets one
// of the followings:
// 1. It's a action's request/response service.
// 2. For pre-Humble ROS 2 releases, because it doesn't support service
// introspection.
if (
isActionService ||
DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')
) {
return writeGeneratedCode(dir, fileName, generatedSrvCode);
}
return writeGeneratedCode(dir, fileName, generatedSrvCode).then(() => {
return generateServiceEventMsg(serviceInfo, dir);
});
}
async function generateServiceEventMsg(serviceInfo, dir) {
const fileName = serviceInfo.interfaceName + '.msg';
const generatedEvent = generateServiceEvent({ serviceInfo: serviceInfo });
return writeGeneratedCode(dir, fileName, generatedEvent).then(() => {
serviceInfo.interfaceName += '_Event';
serviceInfo.filePath = path.join(dir, fileName);
return generateServiceEventJSStruct(serviceInfo, dir);
});
}
async function generateServiceEventJSStruct(msgInfo, dir) {
const spec = await parser.parseMessageFile(msgInfo.pkgName, msgInfo.filePath);
// Pass `msgInfo.subFolder` to the `spec`, because some .srv files of the
// package may not be put under srv/ folder, e.g., slam_toolbox.
spec.subFolder = msgInfo.subFolder;
// Remove the `.msg` files generated in `generateServiceEventMsg()` to avoid
// being found later.
fse.removeSync(msgInfo.filePath);
const eventFileName =
msgInfo.pkgName +
'__' +
msgInfo.subFolder +
'__' +
msgInfo.interfaceName +
'.js';
// Set `msgInfo.isServiceEvent` to true, so when generating the JavaScript
// message files for the service event leveraging message.dot, it will use
// "__srv__" to require the JS files for the request/response of a specific
// service, e.g.,
// const AddTwoInts_RequestWrapper = require('../../generated/example_interfaces/example_interfaces__srv__AddTwoInts_Request.js');
// const AddTwoInts_ResponseWrapper = require('../../generated/example_interfaces/example_interfaces__srv__AddTwoInts_Response.js');
msgInfo.isServiceEvent = true;
const generatedCode = generateMessage({
messageInfo: msgInfo,
spec: spec,
json: JSON.stringify(spec, null, ' '),
isDebug: isDebug,
});
return writeGeneratedCode(dir, eventFileName, generatedCode);
}
async function generateMessageJSStruct(messageInfo, dir) {
const spec = await parser.parseMessageFile(
messageInfo.pkgName,
messageInfo.filePath
);
await generateMessageJSStructFromSpec(messageInfo, dir, spec);
}
function generateMessageJSStructFromSpec(messageInfo, dir, spec) {
dir = path.join(dir, `${spec.baseType.pkgName}`);
const fileName =
spec.baseType.pkgName +
'__' +
messageInfo.subFolder +
'__' +
spec.msgName +
'.js';
const generatedCode = generateMessage({
messageInfo: messageInfo,
spec: spec,
json: JSON.stringify(spec, null, ' '),
isDebug: isDebug,
});
return writeGeneratedCode(dir, fileName, generatedCode);
}
async function generateActionJSStruct(actionInfo, dir) {
const spec = await parser.parseActionFile(
actionInfo.pkgName,
actionInfo.filePath
);
const goalMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_Goal`,
},
dir,
spec.goal
);
const resultMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_Result`,
},
dir,
spec.result
);
const feedbackMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_Feedback`,
},
dir,
spec.feedback
);
const sendGoalRequestSpec = actionMsgs.createSendGoalRequestSpec(
actionInfo.pkgName,
actionInfo.interfaceName
);
const sendGoalRequestMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_SendGoal_Request`,
},
dir,
sendGoalRequestSpec
);
const sendGoalResponseSpec = actionMsgs.createSendGoalResponseSpec(
actionInfo.pkgName,
actionInfo.interfaceName
);
const sendGoalResponseMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_SendGoal_Response`,
},
dir,
sendGoalResponseSpec
);
const sendGoalSrv = generateServiceJSStruct(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_SendGoal`,
},
dir
);
const getResultRequestSpec = actionMsgs.createGetResultRequestSpec(
actionInfo.pkgName,
actionInfo.interfaceName
);
const getResultRequestMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_GetResult_Request`,
},
dir,
getResultRequestSpec
);
const getResultResponseSpec = actionMsgs.createGetResultResponseSpec(
actionInfo.pkgName,
actionInfo.interfaceName
);
const getResultResponseMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_GetResult_Response`,
},
dir,
getResultResponseSpec
);
const getResultSrv = generateServiceJSStruct(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_GetResult`,
},
dir
);
const feedbackMessageSpec = actionMsgs.createFeedbackMessageSpec(
actionInfo.pkgName,
actionInfo.interfaceName
);
const feedbackMessageMsg = generateMessageJSStructFromSpec(
{
pkgName: actionInfo.pkgName,
subFolder: actionInfo.subFolder,
interfaceName: `${actionInfo.interfaceName}_FeedbackMessage`,
},
dir,
feedbackMessageSpec
);
const fileName =
actionInfo.pkgName +
'__' +
actionInfo.subFolder +
'__' +
actionInfo.interfaceName +
'.js';
const generatedCode = generateAction({ actionInfo: actionInfo });
dir = path.join(dir, actionInfo.pkgName);
const action = writeGeneratedCode(dir, fileName, generatedCode);
await Promise.all([
goalMsg,
resultMsg,
feedbackMsg,
sendGoalRequestMsg,
sendGoalResponseMsg,
sendGoalSrv,
getResultRequestMsg,
getResultResponseMsg,
getResultSrv,
feedbackMessageMsg,
action,
]);
}
async function generateJSStructFromIDL(pkg, dir) {
const results = [];
pkg.messages.forEach((messageInfo) => {
results.push(generateMessageJSStruct(messageInfo, dir));
});
pkg.services.forEach((serviceInfo) => {
results.push(
generateServiceJSStruct(serviceInfo, dir, /*isActionService=*/ false)
);
});
pkg.actions.forEach((actionInfo) => {
results.push(generateActionJSStruct(actionInfo, dir));
});
// Handle .idl files directly (parsed via rosidl_parser, no .msg conversion)
if (pkg.idls) {
pkg.idls.forEach((idlInfo) => {
results.push(generateJSStructFromIdlFile(idlInfo, dir));
});
}
await Promise.all(results);
}
/**
* Parse an .idl file directly and generate the appropriate JS struct.
* This uses rosidl_parser to parse the IDL and produces the same JSON
* spec format, so the same templates can be used.
*/
async function generateJSStructFromIdlFile(idlInfo, dir) {
const result = await parser.parseIdlFile(idlInfo.filePath);
const { type, spec } = result;
if (type === 'message') {
await generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: idlInfo.interfaceName,
},
dir,
spec
);
} else if (type === 'service') {
// Generate request and response message JS structs
const requestMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_Request`,
},
dir,
spec.request
);
const responseMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_Response`,
},
dir,
spec.response
);
// Generate service JS struct
const serviceInfo = {
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: idlInfo.interfaceName,
};
const srv = generateServiceJSStruct(
serviceInfo,
dir,
/*isActionService=*/ false
);
await Promise.all([requestMsg, responseMsg, srv]);
} else if (type === 'action') {
// Generate goal, result, feedback message JS structs
const goalMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_Goal`,
},
dir,
spec.goal
);
const resultMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_Result`,
},
dir,
spec.result
);
const feedbackMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_Feedback`,
},
dir,
spec.feedback
);
// Generate derived action messages (SendGoal, GetResult, FeedbackMessage)
const sendGoalRequestSpec = actionMsgs.createSendGoalRequestSpec(
idlInfo.pkgName,
idlInfo.interfaceName
);
const sendGoalRequestMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_SendGoal_Request`,
},
dir,
sendGoalRequestSpec
);
const sendGoalResponseSpec = actionMsgs.createSendGoalResponseSpec(
idlInfo.pkgName,
idlInfo.interfaceName
);
const sendGoalResponseMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_SendGoal_Response`,
},
dir,
sendGoalResponseSpec
);
const sendGoalSrv = generateServiceJSStruct(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_SendGoal`,
},
dir
);
const getResultRequestSpec = actionMsgs.createGetResultRequestSpec(
idlInfo.pkgName,
idlInfo.interfaceName
);
const getResultRequestMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_GetResult_Request`,
},
dir,
getResultRequestSpec
);
const getResultResponseSpec = actionMsgs.createGetResultResponseSpec(
idlInfo.pkgName,
idlInfo.interfaceName
);
const getResultResponseMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_GetResult_Response`,
},
dir,
getResultResponseSpec
);
const getResultSrv = generateServiceJSStruct(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_GetResult`,
},
dir
);
const feedbackMessageSpec = actionMsgs.createFeedbackMessageSpec(
idlInfo.pkgName,
idlInfo.interfaceName
);
const feedbackMessageMsg = generateMessageJSStructFromSpec(
{
pkgName: idlInfo.pkgName,
subFolder: idlInfo.subFolder,
interfaceName: `${idlInfo.interfaceName}_FeedbackMessage`,
},
dir,
feedbackMessageSpec
);
const fileName =
idlInfo.pkgName +
'__' +
idlInfo.subFolder +
'__' +
idlInfo.interfaceName +
'.js';
const generatedCode = generateAction({ actionInfo: idlInfo });
const actionDir = path.join(dir, idlInfo.pkgName);
const action = writeGeneratedCode(actionDir, fileName, generatedCode);
await Promise.all([
goalMsg,
resultMsg,
feedbackMsg,
sendGoalRequestMsg,
sendGoalResponseMsg,
sendGoalSrv,
getResultRequestMsg,
getResultResponseMsg,
getResultSrv,
feedbackMessageMsg,
action,
]);
}
}
module.exports = generateJSStructFromIDL;