Python Wrapping - northern-bites/nbites GitHub Wiki

While most of our systems are written in C++, the behavior system is written in Python. Since behaviors can't run all by themselves and need information from or need to control the C++ systems, the two languages have to interact.

Some Background Information

Standard Python is implemented in C, and Python is billed as easily extensible with C or C++. The Python/C API is the most immediately obvious way to extend Python (provide new, specific functionality to Python) or embed it in a C or C++ program (use Python for a small part of a larger program, which is essentially what we do). If you #include "Python.h" you can use the various definitions in the API in your C/C++ program and thus gain access to Python from C++. However.

For the type of embedding that we do in particular, using the Python/C API is a huge headache. This simple example is not really all that simple, and our C++ systems are far from simple.

To save ourselves many a headache, most of our C++ systems are "wrapped" (meaning that their information is provided for Python's use) using Boost.Python. There are many Boost C++ libraries and we use several in our code base, such as the smart pointer library, along with Boost.Python. (If you're not familiar with the idea of a library, hit up the wikipedia article.)

#include <boost/python.hpp> to use the main Boost.Python library; note that this automatically includes Python.h too, and you can use the Python/C API with Boost if necessary.

Looking at this example, you can see that this library makes it a lot easier to wrap a C++ function than the Python/C API, although still not always a complete piece of cake. Most of the information needed to work with Boost.Python can be found in its reference pages, but this library is somewhat under-documented, and the doc pages can be frustrating. The tutorial, however, is fairly helpful, as are a few other pages (here for example).

In the NBites Context

In most of wrapper headers, there are two functions that are called c_init_[modulename] and set_[modulename]_pointer.
The former is generally called in Noggin.cpp, here. This method in Noggin gets Python up and running and adds the appropriate modules, via the c_init_[modulename]. This is important for all our wrapper modules.
The latter must be called after the C++ class is initialized, and it provides Python with a pointer to the actual instance of the wrapped system, ie the Vision, Loc, or Motion that is currently running on the robot. This pointer is stored in the scope-wide wrapped pointer seen in most of the wrapper modules. Many of them are set here. This is important for any module that wraps an actual object, but not for modules like PyConstants.

Other Misc Notes

  • Vectors and lists, ie std::vector or std::list don't have any sort of built-in converter. There are various different ones wrapped throughout this code. For vectors, see this site. For lists, see this limited example.
  • Many boost classes actually extend both the class and the shared pointer to it, ie
    class_<CPPName, boost::shared_ptr<CPPName> >("PyName")
    This means that Python can access a class via its pointer, which is useful in many NBites situations.
  • Boost noncopyable and the no_init option, which means that a class cannot be initialized from Python, are both widely used to make sure Python cannot manipulate objects on the C++ side.

Where to Look

Each of the wrapped systems should have its own "Py[system name].h/cpp" files, but this rule varies a little. They are, in no particular order:

What These Do

Some information about each of the wrapping files.

  • PyLights - Allows LED colors to be set from Python. Overloads the function setRGB so that it can call either of two similar C++ functions.

  • PyRoboGuardian - Allows Python to access fall information from C++ and to call certain other functions from the C++ RoboGuardian.

  • PySensors - Wraps the main Sensors class from C++ plus three specific sensor structs: FSR, FootBumper, and Inertial. Also wraps a vector of floats for use as angle vectors. Contains many readonly values so that Python can access C++ sensor readings, but it doesn't need to change them. Also provides wrappers for methods related to capturing frames, such as saveFrame or startSavingFrames.

  • PySpeech - Wraps speech-control methods, including say, volume control methods, and enable/disable methods.

  • PyLoggingBoard - Provides Python with methods to start and stop logging.

  • PyMotion - Wraps several types of motion-command classes (all of which inherit from the base class PyMotionCommand) and the motion interface. See PyMotionClasses.h because these aren't the main C++ motion classes but rather appear to make up an interface specifically for Python to use, via the wrapper.

  • PyConstants - Allows Python to use the same constants as C++, as opposed to having to copy all of the constants into Python separately. All of these constants are collected into PyNogginConstants.h then wrapped here. Note that there's no scope-wide pointer since this doesn't wrap a C++ class, just constants.

  • PyObjects - Combination objects are special objects created in C++ for Python usage through this wrapper. They connect a vision object and a loc object, so that, on the Python side, someobject.vis.[properties] refers to any of the object's properties acquired from the vision system, whereas someobject.loc.[properties] allows access to the properties acquired from the localization system. [More about combination objects.] (https://github.com/northern-bites/nbites/wiki/More-About-Combination-Objects)

  • PyLoc - In the header, an interface for the localization system is contained in the PyLoc class. This is specifically for Python's usage through the wrapper. This interface can access values from both the BallEKF and the self loc system. The PyLoc class is wrapped so that Python can access localization information (via lots of read-only values) or reset it (with several wrapped reset methods).

  • PyVision - Wraps the main Vision class and all of the specific vision objects that it contains: VisualBall, VisualFieldObject (goalposts), VisualFieldEdge, VisualRobot, VisualCrossbar (wrapped, but not actually used), FieldLines, VisualLine, and VisualCorner. Also wraps many enums required for properties of these classes. The main vision class has one or more instances of all of the above (except see below). About FieldLines: Contains a list of VisualCorners and a vector of shared pointers to VisualLines, so both of these types are wrapped. Thus, line and corner information must be accessed through this class rather than separately. Lines and corners each have a vector of IDs, so these two types of vectors are wrapped as well.

  • PyComm - Currently DNE. Comm is wrapped with straight C/Python API within the main Comm files. This needs to be redone!

  • PyCommandSender - pull request.

⚠️ **GitHub.com Fallback** ⚠️