Callbacks and Synchronicity – Week 3

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 spin() commands.

So what is the difference between spinOnce() and spin()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.

 

 

 

 

 

 

 

Intricacies with C++ and C++ OOP – Week 3

Note: Both C++ and OOP in C++ are enormous topics, so the mechanisms/concepts discussed here will be a small, limited subset of the subject. Also, general programming/OOP concepts are not addressed.

General Intricacies of C++

  • C++ is wholly compatible with C, which means that you can compile and run C programs as C++ programs, with some changes with the library/header names. The C libraries in C++ are prefixed with c, i.e. the string.h is cstring in C++.
  • Previously considered “lacking” in conveniences previously, C++ had “recently” (2014) gone through a major standard change called C++11. Features added are..
    • Standard Template Library (often abbreviated STL). STL allows for a generic (supporting multiple classes) data structure like vectors (which are arrays (linked-list implementation) with dynamic (changeable) size), queues, stacks, etc.
      • STL uses things like:
        • Iterators (essentially indexes)
        • auto (deduces type, often used for iterators due to their long name), e.g. auto i = numVector.begin(); assigns the iterator type to i.
      • Templates are often specified like template<classname T>. So, if you see a function specified with such brackets, you can infer that the function is designed to be used with multiple classes.
    • There is now nullptr to indicate null pointers (NULL is known to cause issues in error handling)
  • Nor do C++ have many of the practical features implemented, so many/most C++ programmers complement the language with a library called Boost, which provides features for Serialization, I/O streams (often used for parsing input/data), and Math.
  • C++ print to standard out using std::cout << "string" << endl; and standard error std::cerr << "string" << endl;, eschewing commonly used print statement.
  • Like C, the default in C++ is that values passed into functions are copied, rather than via reference like Java – which can cause performance issues. That is the reason why so many functions have & notation next to the parameters, indicating that the parameters are passed via “reference”, or more accurately, via pointers (unfortunately, I can’t go into pointers here).
  • You can have default arguments/paramters in C++.
  • C++ have no garbage collection, which means that you have to destroy everything that has been created dynamically with the new operator with delete. Use of C’s malloc and free is discouraged.

OOP intricacies with C++

  • C++ is more flexible with overriding things in classes in general, allowing the overriding of things operators (+, <<), destructors (things that destroy class constructor).
  • C++ have something called namespaces (which is also used in ROS) to organize things variables and functions without the structure of a class.
    • Namespaces are used to remediate the problem of global variables. Global variables are evil, as you could unintentionally call global variables from other places of the project without knowing – i.e. if you declare a global variable with a generic name of speed, the variable can now be used/changed everywhere.
      • Alternatives to global variables are enums.
    • Namespaces are called name::variable and declared namespace name { auto variable = 0; }
      • Class functions and variables, too, are called as  class::variable.
  • You indicate public, protected, private parts of class using notations public:, private:, protected:
  • Objects/Instances of the class will be destroyed once the scope of the instance is exited, e.g. with void init() { Dog d = Dog(); }, Dog d will be destroyed once init function ends. You can avoid this using the new operator discussed earlier.

Feel free to inform me if there is any part of C++ code that is confusing – I’ll add onto the list.