Robotics and its software differ from a typical software in its two main requirements.
The first requirement is that robot software must react differently based on environmental changes and conditions – often modeled by a state machine and its states (see here, but at its simplest, it’s just if’s in a while loop).
For example, should the robot not enter into a recovery state after ramming into some obstacle, rather than continuing to pummel through? Even if all the visuals and sensor data may be the same, the robot must act differently in a recovery state than the normal state.
Another requirement is that the robot (the state machine above or something else) needs to be able to handle inputs and requests from multiple sources – from the sensor data, requested actions, and such. Here, the complexity emerges, and ROS is handed the duties of a typical operating system – managing multiple programs at once.
ROS, in its distributed approach, does not have THE state machine, but rather leaves it to each node to determine its own state and resolve the potential conflicts (an approach supported by recent neuroscience advances).
So, when designing nodes for ROS, one must be aware of these requirements.
But first of all, let us first discuss the service tool, which is a synchronous way to establish one-to-one communication, which means that there is an order to the communications. The order is the following: A node (client) would send a request to another node (server), possibly waiting for a reply as the server processes the request.
Where in the code do the waiting and processing happen? The waiting happens at
spin() and spinOnce(), while the processing happens in the callback.
Each node, when they receive messages through topics or service, does not immediately process them, as they very well might be doing something more important. Instead, the messages are held in the queue (a line essentially) and processed as a batch during the
So what is the difference between
spin() is essentially
spinOnce() called in a loop – once the node runs
spin(), the node will now only deal with the callbacks. By comparison,
spinOnce() allows the node to process the callbacks at the designated time “once”.
So when should you use each? You should use
spinOnce() if you have a code not part of the callback that constantly needs to run and
spin() if the server only needs to deal with callbacks from then on. Also, with
spinOnce(), you need to be aware of the time that will take to process the tasks between
spinOnce(), making sure that the tasks will not prevent callback from activating too late.
And for the services in general, you need to also consider the time that the service will take, making sure it doesn’t occupy the server for too long. Otherwise, the server will not be able to react quickly to the inputs from other nodes – perhaps a command that is requesting for the robot to freeze immediately. Thus, it is generally recommended that services be simple, short requests.
However, you would need the ability to command a robot to do a more complicated task – or even allow for multitasking of tasks. For those requirements, there is the action lib. In contrast to service, it is – and must be – an asynchronous way to communicate one-to-one.
Why the must? The actions must be asynchronous, as for actions with a long-time frame (~seconds), the state of the robot might change, with actions needed to be interrupted and continued later. So actions are asynchronous because the execution of action is not fixed – unlike services, which can’t be interrupted.
While the exact details of both service and action is in the respective tutorials, these differing goals for services and actions must be accounted for when making servers (nodes) for each of them – asking if the service or action would be more appropriate at this point.
Assignment: Implement a client/service, in which client sends in a service to the server to change the state of the server in some way.