MATLAB Programming for Code Generation
This example shows the recommended workflow for generating a standalone executable from MATLAB® code that contains ROS interfaces.
Prerequisites
This example requires MATLAB Coder™.
You must have access to a C/C++ compiler that is configured properly. You can use
mex -setup cpp
to view and change the default compiler. For more details, see Change Default Compiler.
Overview
A subset of ROS MATLAB functions such as rossubscriber
, rospublisher
, and rosrate
support C++ code generation. To create a standalone ROS node from MATLAB code, follow these steps:
Create the entry-point function for creating a standalone application. The entry-point function must not have any inputs and must not return any outputs.
Add the
%#codegen
directive to the MATLAB function to indicate that it is intended for code generation. This directive also enables the MATLAB code analyzer to identify warnings and errors specific to MATLAB for code generation.Modify the ROS functions to use message structures.
Identify MATLAB code that does not support C++ code generation and modify the MATLAB code to use functions or constructs that support code generation.
Create a MATLAB Coder configuration object and specify the hardware as
'Robot Operating System (ROS)'
.Use
codegen
command to generate a stand-alone executable.
Generate Code for Subscriber
Consider the following MATLAB code.
function mynode(numIterations) %mynode Receive and display sensor_msgs/JointState messages rosinit; % Trajectory points to be published sub = rossubscriber("/servo"); % Display position for k = 1:numIterations msg = receive(sub); if ~isempty(msg.Position > 0) disp(msg.Position(1)) else disp("Received empty message.."); end end rosshutdown; end
This MATLAB code receives sensor_msgs/JointState
messages published to the /servo
topic and displays the first element of the position in a loop. The function has an input argument that sets the number of iterations. To create a stand-alone ROS node, modify the code as follows:
Eliminate the input argument
numIterations
using awhile
loop.Add the
%#codegen
directive.Specify the message type for the
rossubscriber
call.Use message structures instead of message classes.
Eliminate
rosinit
androsshutdown
calls that do not generate code.Replace the
disp
function which does not support code generation, withfprintf
.
Note that you execute the rosinit and rosshutdown functions only once in a MATLAB session. Avoid rosinit
and rosshutdown
functions for code intended for standalone deployment. In stand-alone deployment, individual ROS nodes are not expected to start or stop the ROS master. If you need to include rosinit
and roshutdown
calls in your MATLAB code for interpreted execution, declare them as coder.extrinsic
(MATLAB Coder) functions at the top of your entry-point function.
The entry-point function cannot take any inputs or return outputs. A standalone ROS node executable is intended to be launched outside MATLAB, such as from a system command terminal, and therefore cannot take any MATLAB inputs or return MATLAB outputs.
To eliminate the input argument numIterations
, replace the for
loop with an infinite while
loop. For stand-alone deployment, the generated node is expected to run until you terminate it externally. A good programming practice is to replace the for
loops with a while
loop for standalone deployment.
The rossubscriber
function needs the message type to be specified as a compile time constant for code generation. The function uses this information to create the return message type for receive
calls. The rossubscriber
function supports code generation for message structures only. To return a message structure, specify the name-value pair argument, "DataFormat","struct"
, when creating a subscriber.
After you modify the code, the MATLAB code for the entry-point function is as follows:
function mynode %mynode Receive and display sensor_msgs/JointState messages %#codegen % Trajectory points to publish sub = rossubscriber("/servo","sensor_msgs/JointState","DataFormat","struct"); % Display position while (1) msg = receive(sub); if ~isempty(msg.Position > 0) fprintf("Position = %f\n",msg.Position(1)) else fprintf("Received empty message..\n"); end end end
Create a MATLAB Coder configuration that uses "Robot Operating System (ROS)"
hardware. Set the HardwareImplementation.ProdHWDeviceType
parameter of the MATLAB Coder configuration object for the intended deployment hardware. For example, if you are deploying generated code to a Windows computer set HardwareImplementation.ProdHWDeviceType
to "Intel->x86-64 (Windows64)"
. To generate code, execute the following commands:
cfg = coder.config("exe"); cfg.Hardware = coder.hardware("Robot Operating System (ROS)"); cfg.Hardware.DeployTo = "Localhost"; cfg.Hardware.BuildAction = "Build and run"; cfg.HardwareImplementation.ProdHWDeviceType = "Intel->x86-64 (Windows64)"; cfg.HardwareImplementation.ProdLongLongMode = true; % Use 'long long' for Int64 or Uint64 data types codegen mynode -config cfg
You can send messages to mynode
using the following command on a ROS terminal:
rostopic pub /servo sensor_msgs/JointState -r 1 "{header:{seq: 0, stamp:{secs: 0,nsecs: 0},frame_id:""},name:["joint"],position:[150.0,100.0],velocity:[0.0,0.0],effort:[0,0]}"
Generate Code for Publisher
Consider the following MATLAB code.
function mypubnode(updateRate) %mypubnode Publish joint trajectory messages % Create publisher pub = rospublisher("/traj","trajectory_msgs/JointTrajectory"); % Create a message msg = rosmessage("trajectory_msgs/JointTrajectory"); msg.JointNames{1} = 'Left'; msg.JointNames{2} = 'Right'; trajMsg = rosmessage("trajectory_msgs/JointTrajectoryPoint"); r = rosrate(updateRate); while (1) msg.Header.Stamp = rostime("now"); x = rand; y = rand; trajMsg.Positions = [x y -x -y]; msg.Points = [trajMsg trajMsg]; send(pub,msg); waitfor(r); end end
This MATLAB code publishes trajectory_msgs/JointTrajectory
messages to the /traj
topic. You can set the message publishing rate externally. The trajectory_msgs/JointTrajectory
message is a nested message that has the following subfields:
>> rosmsg show trajectory_msgs/JointTrajectory
std_msgs/Header Header
char[] JointNames
JointTrajectoryPoint[] Points
The message can accommodate multiple joints and multiple trajectory points. If the number of joints is M and the number of trajectory points for each joint is N, trajMsg
has the dimensions M-by-1 and trajMsg.Positions
has the dimensions N-by-1. For this example, publish two trajectory points per joint.
The function has an input argument that sets the number of iterations. To create a stand-alone ROS node, modify the code as follows:
Eliminate the input argument
updateRate
by directly specifying it within function body.Add the
%#codegen
directive.Use message structures instead of message classes.
Eliminate any message fields that use cell strings to prepare for code generation.
Grow variable-size fields of message structures in the correct dimension.
To eliminate the input argument updateRate
, specify the update rate using a constant literal in the entry-point function body. To modify the updateRate
, you must re-generate code from the entry-point function.
Modify the rospublisher
, rosmessage
and rosrate
functions to use message structures. The trajectory_msgs/JointTrajectory
message structure has a field, JointNames
, which is of type cell string. Message fields of this type are not supported for code generation due to MATLAB Coder limitations. In order to generate code for mypubnode
function, avoid the use of JointNames
fields in code generation using coder.target
(MATLAB Coder) function.
The original mypubnode
function grows variable-size fields of the message structs in the wrong dimension. As an illustration, create a message of type trajectory_msgs/JointTrajectory
at the MATLAB command line. Note that the first dimension of the variable-size fields is of size 0 while the second dimension is of size 1:
msg = rosmessage('trajectory_msgs/JointTrajectory') msg = ROS JointTrajectory message with properties: MessageType: 'trajectory_msgs/JointTrajectory' Header: [1×1 Header] Points: [0×1 JointTrajectoryPoint] JointNames: {0×1 cell} Use showdetails to show the contents of the message
The fields with dimensions 0-by-1 grow in the first dimension. The original code grows the Points
and Positions
fields in the second dimension, which works in interpreted mode, but is not supported for code generation. You get the following error message if you attempt to grow variable-size fields of a message structure in the wrong dimension:
??? Attempt to write a value of type 'trajectory_msgs_JointTrajectoryPointStruct_T' into a variable defined as type 'struct_T'. Code generation does not support changing types through assignment. To investigate the cause of the type mismatch, check preceding assignments or input type specifications.
After you modify the code, the MATLAB code for the entry-point function is as follows:
function mypubnode %mypubnode Publish joint trajectory messages %#codegen % Create publisher pub = rospublisher("/traj","trajectory_msgs/JointTrajectory","DataFormat","struct"); % Create a msg msg = rosmessage("trajectory_msgs/JointTrajectory","DataFormat","struct"); if isempty(coder.target) msg.JointNames{1} = 'Left'; msg.JointNames{2} = 'Right'; end trajMsg = rosmessage("trajectory_msgs/JointTrajectoryPoint","DataFormat","struct"); r = rosrate(1); while (1) msg.Header.Stamp = rostime("now","DataFormat","struct"); x = rand; y = rand; trajMsg.Positions = [x; y; -x; -y]; % Grow variable-size fields in the correct dimension msg.Points = [trajMsg; trajMsg]; % Grow variable-size fields in the correct dimension send(pub,msg); waitfor(r); end end
Create a MATLAB Coder configuration that uses "Robot Operating System (ROS)"
hardware. Set the HardwareImplementation.ProdHWDeviceType
parameter of the MATLAB Coder configuration object for the intended deployment hardware. For example, if you are deploying generated code to a Windows computer set HardwareImplementation.ProdHWDeviceType
to "Intel->x86-64 (Windows64)"
. To generate code execute the following commands:
cfg = coder.config("exe"); cfg.Hardware = coder.hardware("Robot Operating System (ROS)"); cfg.Hardware.DeployTo = "Localhost"; cfg.Hardware.BuildAction = "Build and run"; cfg.HardwareImplementation.ProdHWDeviceType = "Intel->x86-64 (Windows64)"; cfg.HardwareImplementation.ProdLongLongMode = true; % Use 'long long' for Int64 or Uint64 data types codegen mypubnode -config cfg
You can examine the contents of the messages published by mypubnode
using rostopic
echo
/traj
command on a ROS terminal.
Additional Considerations
Code generation for messages containing cell strings: MATLAB Coder does not support code generation for cell strings. You can send and receive ROS messages containing cell strings using rossubsriber
and rospublisher,
but you cannot access the fields that are of type cell string. For instance, in a sensor_msgs/JointState
message, you cannot read or write to the field, Name
, which is of type cell string in MATLAB representation.
Variable-size message fields: Do not use indexing past the array dimension pattern to grow the size of variable-size arrays in message fields. Use the following form:
msg = rosmessage('sensor_msgs/JointState','DataFormat','struct'); msg.Position = [0.1; -0.1]; ... msg.Position = [msg.Position; 0.2]; % Add new data point
For more information, see Incompatibilities with MATLAB in Variable-Size Support for Code Generation (MATLAB Coder). Also, for the MATLAB Coder configuration object, you must set EnableVariableSizing
property to true
and enable dynamic memory allocation for variable-size arrays.