microMVP: A Portable, 3D-Printing Enabled Multi-Vehicle Platform for Robotics Research and Education

Introduction | Parts and assembly | Software stack | Download manuscript | V3 Page

This page provides instructions on setting up the software stack (API) for programming over microMVP with a set of six vehicles. The API will enable the vehicles to follow specific paths but do not guarantee that the vehicles will not collide. However, we do provide a demo showing how six vehicles may be synchronized. These way point based APIs are sufficient for the vehicles to follow complex paths without collision. APIs enable more complex tasks will be gradually added as they are fully tested and ready. Toward the end of the page, instructions are provided on how to add additional vehicles to the system. The source files needed for this part can be downloaded here.

Note that this page is meant for an audience comfortable with python and is not intended for general educators. We plan to make more readily usable open source distributions available for primary and secondary education in the near future with plain, easy to follow instructions.

Packages to be installed

The following libraries should be installed before proceeding. We assume that the end user has basic knowledge when it comes to setting up these libraries for the purpose of program compilation.

OpenCV 2.x and up
ZeroMQ 4.x
Arduino IDE

We assume the user has boost library installed as well.

Enabling vehicle tracking

To enable vehicle tracking, print a few chilitags (available in the chilitags github page listed above) fiducial markers and fix them to the top of the vehicles so that the front of a vehicle aligns with the front of a tag. Note down the tag IDs that are used for each vehicle. For the setup that we tried, tags that is about 3.5cm x 3.5cm (excluding borders) in size worked well. Some white border should be included for better tracking (otherwise, the borders of the tags can be difficult to locate for the tracking module. See the picture to the right for a vehicle with a tag attached.  
After compiling the chilitags main library, create a duplicate of the detect-live example within the chilitags distribution and replace the detect-live.cpp file with the one provided in the downloaded package. Alternatively, one may simply overwrite the detect-live.cpp in chilitags and compile the module. The module should be run as

> detect-live.exe cameraID port

For example, running "detect-live.exe 0 5556" will use camera 0 (the first camera of the system) and starts feeding tag positions within the camera's range on port 5556. Note that for best tracking, one should adjust the camera parameters (using openCV or camera driver software if available) including exposure, brightness, contrast, white balance, and so on to make sure the tags within the camera range are consistently tracked (the number of tags seen by chilitags is displayed at the top left corner of the tracking app. See picture below for a screen shot of the module while running. Note that all six tags are tracked. We set the camera to run at 1280x720 resolution, which appears sufficient for the purpose of tracking. This can be set to 1920x1080 (in detect-live.cpp) if desirable.


Once the vehicle position data publisher is started, one may test the data feed using positionZMQSub.py under the python folder as (make sure that the parameters in globals.py are set according to your setup)

> python positionZMQSub.py tagID

in which tagID is the chilitag ID (displayed on the tracking program UI window) for a given vehicle. For example, suppose that a vehicle with tagID 29 is in the camera's range, then running python positionZMQSub.py 29 should produce an output like the following

Collecting update from server: tcp://
Returned: 793.73 481.28 802.21 507.08 776.95 515.53 768.45 489.43 1474825621.39
Returned: 793.80 481.19 802.37 506.84 777.09 515.64 768.46 489.53 1474825621.44
Returned: 793.83 481.22 802.33 506.96 777.07 515.62 768.50 489.46 1474825621.49
Returned: 793.83 481.28 802.33 506.90 776.98 515.56 768.42 489.48 1474825621.55
Returned: 793.80 481.24 802.36 506.88 777.02 515.56 768.48 489.44 1474825621.6
Returned: 793.70 481.27 802.28 506.78 777.03 515.53 768.49 489.51 1474825621.65
Returned: 793.79 481.20 802.34 506.89 777.07 515.58 768.44 489.40 1474825621.71
Returned: 793.83 481.32 802.18 506.98 777.00 515.54 768.43 489.51 1474825621.77
Returned: 793.87 481.28 802.37 506.89 777.06 515.59 768.51 489.41 1474825621.82
Returned: 793.86 481.27 802.34 506.77 777.02 515.53 768.40 489.45 1474825621.88
Returned: 793.73 481.26 802.37 506.83 777.07 515.58 768.42 489.47 1474825621.93
Returned: 793.77 481.22 802.39 506.81 777.06 515.59 768.44 489.46 1474825621.99
Returned: 793.80 481.22 802.36 506.91 777.04 515.49 768.44 489.48 1474825622.05
Returned: 793.93 481.28 802.17 506.84 777.00 515.60 768.40 489.56 1474825622.1
Returned: 793.87 481.20 802.38 506.88 776.96 515.44 768.47 489.48 1474825622.16
Returned: 793.81 481.25 802.25 506.89 777.07 515.52 768.49 489.45 1474825622.22
Returned: 793.83 481.21 802.31 506.87 777.00 515.51 768.45 489.38 1474825622.28
Returned: 793.87 481.27 802.20 507.02 777.02 515.54 768.48 489.51 1474825622.34
Returned: 793.81 481.31 802.19 507.00 776.89 515.53 768.47 489.51 1474825622.39

Here, for each row, the first 8 float numbers are the (x,y) coordinates of the four corners of a tag and the last number is the timestamp of the data.

Flashing the xBees

The communication between the vehicles and the main control computer is done through xBees. We choose to put all xBees in the "AT" mode. For each xBee, set the channel and PAN ID to be the same and the baud rate at 57600. For example, a possible setting may be

    CH Channel: 12
    ID PAN ID: 2048
    BD Interface data rate: 57600

Programming and testing the vehicle

The fio v3 based vehicle is fully compatible with Arduino and can be easily set up so that the vehicle can be programmed with the Arduino IDE. In the IDE, compile and upload the Arduino code (fio-v3-7-car-v1.ino) via the USB port on the fio v3 board. Note that for each vehicle, a different carID should be used. For six vehicles, these IDs should be 1-6. One should know which vehicle has which carID. Leave all other field to have the default values. Once this is done, when two such xBees are powered up, they can readily communicate with each other.

To test that a vehicle is indeed working fine, run xBee.py under the python folder as

> python xBee.py carID

in which carID is the ID of the vehicle (the same carID used in the .ino file when flashing the vehicle's fio v3). This should run the two wheels of the given car for 5 seconds before turning them off.

Putting it together

Single vehicle

After the vehicles and the tracking platform are individually tested, it is time to put everything together. We start with a single vehicle. For this, we run singleController.py. In this python file, the carID (the one used in the .ino file for flashing the vehicle) and carTag (the chilitag ID attached to the vehicle) must match those of the actual car being tested. To run the module, simply fire it up

> python singleController.py

This brings up a pygame based UI. Note that this UI is completely optional. In the UI, there should be a cartoon car representing the actual vehicle location (assuming that the vehicle with the correct ID is placed in the camera's range) as shown in the picture below (without the path).

A user may then use the mouse to draw an arbitrary path inside the UI and the vehicle should start following the path using the pure pursuit algorithm (see the picture above; the path is the one that is drwan using a mouse). Note that all key functionality resides in the class DDRCar.py, which allows both the simulation and real test of DDR vehicle. Roughly the logic flow looks like the following (we assume here that all way points are added in the beginning to make the illustration simple) for running on a real vehicle

xBee = Bee(...) # Initializing xBee
ddr = DDR(...)  # Initializing a DDR vehicle

# Add all desired way points while(has more way points)

# Run pure pursuit
while(problem in progress)
   if ddr.purePursuit(...)             # True implies new thrusts are generated
      (lt,rt) = ddr.getControlInputs() # Retrieve control inputs from ddr instance
      bee.xBeeSendSingle(...)          # Send control to vehicle

For a simulated vehicle, the code is simpler

xBee = Bee(...) # Initializing xBee
ddr = DDR(...)  # Initializing a DDR vehicle

# Add all desired way points while(has more way points)

# Run pure pursuit
while(problem in progress)
   ddr.purePursuit(...) # Run pure pursuit

The initialization of the DDR is done through the __init__ method

    def __init__(self, carXBeeID, wheelDist, multiplier,
                 throttle, simulated = False, simulationStep = 0.1):

in which carXBeeID is carID of the vehicle, wheelDist is the length between the two wheel centers in screen pixels, multiplier*wheelDist is the lookahead distance (for pure pursuit) in pixels, and throttle is a number between 0 and 1 that limits the top speed of the vehicle. The later two parameters are for simulation with obviously meanings. It may happen that some vehicle has a fast/slower speed given the same thrust input. If the difference is too big, one may use throttle to adjust for it.

More complex patterns can be readily added by dissecting the pattern into a list of way points. In the python UI of singleController.py, press 'p' will cause the car to follow a circle for two full rotations (the screenshot below was captured when the car was following the circle).

The main logic of singleController.py, besides the UI part, is to separate the way point management from the running of the vehicle control. Other than this, the logic flow is straightforward and we leave it to the user to digest it.

Multiple vehicles

Adding more vehicles (without considering collision) is also straightforward once a single vehicle works well. The file controller.py provides an example that can be executed similarly as

> python controller.py

This module assumes that there are six vehicles with carID from 1 to 6 and different chilitag IDs property setup in controller.py near the beginning of the file. The UI looks like the following

controller.py allows the selection of active vehicle by pressing the keyboard key corresponding to the vehicle's ID. After a vehicle is selected, one may draing an arbitraray path for the vehicle to follow similar to the single vehicle case. Multiple vehicles may move simultaneously. As we have already mentioned, collision is not automatically handled. The console output and the screenshot are provided below.

Collecting update from server: tcp://localhost:5556
Vehicle 3 is now active.
Vehicle 4 is now active.
Vehicle 1 is now active.
Vehicle 5 is now active.

Similarly, one may program all six vehicles to move synchronously. The sychronization is performed via speed adjustments based on the individual vehicles progress in reaching way points. To avoide collision, the vehicles should be placed roughly as given in the following figure

Pressing the keyboard key "c" then should cause the vehicles to synchronously follow a circle, evenly spaced, for two full circles.

Adding even more vehicles

There are many ways of adding more vehicles to the system. The simplest one is to use a single xBee to talk to all vehicles. In the provided code, vehicle thrusts are sent in 16 byte blocks. The block is identified by the frist and last byte. The middle 14 bytes are wheel thrusts for 7 vehicles. Making this block longer will then allow more vehicles to be added.

The slight downside with the above approach is that as more and more vehicles are added, there can be interference between the xBees. This can be mitigated by limiting the number of xBees on the same channel. Both approaches require updating the Arduino code and xBee.py. Our experiments show that either approcah seems to work fine with 15-20 vehicles.

Return to top