|
MIRA
|
The python toolbox allows to use MIRA's framework functionality in python and to use python inside MIRA.
There are three ways to use MIRA in python. The first is by booting up an own framework in python and launch units from script. The second way is to use the python interpreter from inside an already existing framework and launch units from script. The last way is to use the <pyunit> tag in the config files to launch a python unit in a framework. You should add the follwing path(s) to the PYTHONPATH environment variable in order to use the mirapy and service modules directly from python:
One can use MIRA inside a python console by adding the MIRADIR folders to the python module search path (PYTHONPATH). After that the mirapy module can be imported like any other module and a framework can be created. Note that there can be only one instance of a framework and an exception will be thrown if one attempts to create second one.
In the above example you can see that the frameworks constructor takes the command line arguments. The load() method loads all config files given on the command line arguments. One can also load additional configuration files via the load(file) method. The framework needs to be started to start up units. Optional you can enter an infinite loop until the user signals an interruption request (e.g. Ctrl-C)
The mirapy module contains a Unit class that can be inherited and provides nearly the same functionality as the C++ counterpart (See Unit)
The mirapy.Unit class also has some predefined functions for accessing channels and RPC services.
checkin(namespace, id, optionalCycleTime=500ms):resolveName(name):bootup(message):warning(category, message):error(category, message):ok(category):queryServicesForInterface(interfacename):waitForService(servicename, optionalDuration=infinity()):waitForServiceInterface(interfacename, optionalDuration=infinity()):existsService(servicename):publishService():subscribe(channelName, channelType, optionalCallback=None):subscribeInterval(channelName, channelType, callback, Duration):unsubscribe(channelName, channelType):publish(channelName, type):getChannel(channelName):getTransform2(target, source, optionalTime = Time()):getTransform3(target, source, optionalTime = Time()):publishTransform2(nodeName, pose, optionalTime = now()):publishTransform3(nodeName, pose, optionalTime = now()):publishTransformIndirect2(frameID, targetID, sourceID, transform, optionalTime = now()):publishTransformIndirect3(frameID, targetID, sourceID, transform, optionalTime = now()):addTransformLink(child, parent):spin():The reflectors support members, properties, roproperties, methods and interfaces. Members and (ro)properties must be registered using getters and setters (no setter for roproperty) and a type. Lists can be used as members or (ro)properties with the [type] syntax:
All arguments (as well as the return value) of methods for services implemented in Python and defined by r.method(...) are of type json::Value on the RPC layer. In order to avoid having to call a method that is not returning any value by callService<json::Value>, a service method without return value can be explicitly defined using voidMethod(...). This will have to be called with callService<void>, thus is consistent with services implemented in C++. (In contrast to C++, the Python function's return type is not defined before the function is executed and therefore cannot be inferred automatically.)
RPC methods' parameters can be documented by name and description or even name, description and sample value, just as for services implemented in C++. Since parameters are of type json::Value, sample values for these RPC arguments must also be JSON values, or any types that can be extracted as json::Value by the MIRA Python integration layer. This requirement is fullfilled e.g. by primitive types, Python list and dict (using keys of type str only, which is a restriction of the JSON format itself), as the Python toolbox explicitly defines conversion between Python and JSON for those. As a further convenience, any type with a convertObjectToJSON member function is also implicitly converted using that method by the reflection wrapper. Such a method is generically implemented in UnitWrapper.h and automatically added as member function to all types registered using the MIRA_PYTHONCONNECTOR_(NAMED_)TYPE_FOOTER macro defined there too.
Units can publish and subscribe to channels that are represented as Channel objects in python. A channel supports different methods to access its data:
get(optionalTime=now(), optionalTimeTolerance=infinity()):read(optionalTime=now(), optionalTimeTolerance=infinity()):post(data, optionalTime=now(), optionalFrame=""):waitForData(optionalDuration=infinity()):waitForPublisher(optionalDuration=infinity()):hasPublisher():hasSubscriber():Callbacks or calling read() on a channel provides a ChannelRead object that has the following properties:
Value:Timestamp:FrameID:SequenceID:To call RPC services one can use the service module. Note that you need to add the MIRADIR folder to the python module search path (PYTHONPATH). The following example calls the RPC method sum() on MyService and prints its result.
The service calls return a RPCFuture that can be used to wait for the return of the call. It supports the following functions:
get(optionalType=None):bool, int, string) this will be a mirapy.jsonObject by default. For types registered through MIRA, you can specify the return value type by passing it to get() like future.get(mirapy.Point3f). MIRA serialization mechanisms will then handle the automatic conversion to that type if possible.wait(optionalDuration=infinity()):hasException():The module mirapy contains the following global methods:
queryServicesForInterface(interfacename):waitForService(servicename, optionalDuration=infinity()):waitForServiceInterface(interfacename, optionalDuration=infinity()):existsService(servicename):getTransform2(target, source):getTransform3(target, source):publishTransform2(nodeName, pose):publishTransform3(nodeName, pose):publishTransformIndirect2(frameID, targetID, sourceID, transform):publishTransformIndirect3(frameID, targetID, sourceID, transform):addTransformLink(child, parent):log(severityLevel, message):severityLevelString(severityLevel):isFrameworkConnected(remoteId):isConnectedTo(address):Note that in contrast to the member methods of the Unit class global methods that take frames or servicenames as parameters do not resolve these names.
You can also use Qt bindings together with a mira pyunit. Therefore you need to have the PySide Qt bindings package installed on your system. To ensure that all handlers are called within the Qt thread you need to set the flag mirapy.Authority.NO_MAIN_THREAD on your unit and call spin from within a Qt timer. The following example shows (after a unit that provides an odometry channel is available) a window with a label that prints the robots current odometry reading.
One can also instantiate a python unit via the <pyunit> tag from a framework config file. Therefore you need to tell python where to look for your unit scripts, using the <pypath> tag.
<pypath> optionally adds multiple paths, delimited by ';'. Each individual path is resolved as usual (package names, variables etc.)
The above example assumes that there is a file Math.py in one of the specified python paths, that contains a class MyUnit. It will instantiate an object of this class with the authority id "SumUnit".
Within miracenter you can execute python scripts from the Python Editor View. Click on Window->Show View and select the Python Editor View in the Scripts section. The editor starts with a template script that contains the outline of a unit. You can fill in the functionality like in any other unit. If you hit "execute" the script will be interpreted. If you want to change the code just hit "execute" after making the changes. The old unit is destroyed and a new one will be created. The script will be saved inside the workspace configuration when you close miracenter.
The MIRA Python toolbox also includes Python wrappers for additional MIRA (and base dependencies') types which may only be required in specific cases. They are generally provided as separate Python modules.
1.8.14