MindControl

MindControl is a Python library for a direct control over two types of popular LEGO Mindstorms devices, the EV3 and the NXT. You can use it to perform accurate motor movements, as well as perform some sensor measurements via Bluetooth, using your computer or any other compatible Bluetooth-enabled device you can run Python on.
It runs on Python 3.x, requires a quick and simple installation, and is free to download. There are some other similar libraries available across the Internet, but my aim was to make it work as simply as possible without omitting anything crucial or going against the Python grain. Namely, you can use it to:
  • spin any motor by a given angle, under any power
  • perform simultaneous controlled turns on the EV3, as well as "free", unsynchronized spinning
  • read sensor measurement values from the EV3
  • play tones on the EV3
Obviously, what is remaining to do at the time of writing is to implement the sensor measurements on the NXT, not just the EV3. Admittedly, I did not venture into EV3 screen control either, assuming that the users will be looking at the computer screen rather than the EV3 display to see what is going on.

Installation

Installation of MindControl is rather simple, especially if you are already familiar with Python and its environment. Of course, a prerequisite for using it is at least one EV3 or NXT brick with its peripherals, although you can use both, or more of each if you prefer ― at least in theory, Bluetooth should allow seven simultaneous devices, and there are workarounds (unrelated to MindControl) that should allow for even more.
The installation differs slightly depending on the type of device you are using, so pay close attention to that while setting things up:
  1. Install Python. Obviously, our starting assumption is that you will use Python, so if you don't have it yet, download and install it according to the instructions from www.python.org. It is free. Most likely you have it already installed if you read this far.
  2. Install PySerial module. To run MindControl, you will need to install this additional module that takes care of serial port connections. You can download it at pypi.python.org or, just as simple, head to c:\python\scripts and execute pip install pyserial. Again, even if you are a sporadic Python user, this should sound very familiar.
  3. Download the MindControl Python module here. Simply place it in a folder where you plan to write your own Python scripts (.py) using this module.
  4. Turn on your Mindstorms bricks, and enable Bluetooth both on them and your computer.
  5. Pair each of your devices with the computer if not paired already. Make sure new serial ports have been created for each of your Mindstorms devices. Click on the Bluetooth icon in your tray, and choose Open Settings. Then click on the COM Ports tab. At least one COM port should be assigned to each device ― note its number. If more than one port is assigned for a device, note its Outgoing COM port number. In the screenshot, one has to note the COM4 for the NXT, and the COM8 for the EV3. If you are not using Windows, please find a relevant similar operation that applies to your OS.
  6. If you are using any NXT devices, download a MindControl executable here and upload it on each of your devices, either by using the official LEGO software, or some alternative solution such as NEXTTool. This step is not required for the EV3 devices.
  7. Start the MindCtrl program on the NXT. This step is required always before you start the scripts unless you use a start function, which will be described later.
You are now ready to start coding, i.e. creating and running your own Python scripts using MindControl.

Usage

All the commands for the Mindstorms devices are based around the two main classes, EV3 and NXT. You need to create an object for each of your devices separately, and can subsequently use it to pass commands to them. Apart from the explicite exceptions, all the commands for both types of devices are synchronized, i.e. the script is suspended while the command is being executed and the flow is resumed only when it has been completed and confirmed as such by the device. In other words, should you pass two motor instructions one after the another, you can rely on the system to wait for the first one to finish completely before the second one is called.

Creating the instances

To create an object corresponding to a physical device, you just need to supply the aforementioned serial port number (COM number) it is connected to. For that particular example, you would need to use the following:
import mindctrl
ev3device=mindctrl.EV3('COM8')
nxtdevice=mindctrl.NXT('COM4')
That should create two objects, ev3device and nxtdevice, to which you can subsequently pass commands. You need to create this sort of object for each Mindstorms device you intend to use.

Absolute motor rotation

The most frequent job for MindControl will be to rotate the motors. You need to use the rotate function for that purpose:
rotate(motor1, motor2, motor3, motor4, speed)
You can use it for both NXT and the EV3, e.g.:
ev3device.rotate(90, None, -360, speed=75)
The motors correspond to their parameter position. This command rotates the first motor 90° forward and the third motor one full turn reverse (one full turn is 360°), both with a speed of 75. The speed is simply percentage of full speed, which is default. If you do not want to rotate a motor, pass a zero, or the Python object None. Missing motors are filled up automatically. Note that, for the NXT device, you can specify only the three motors, as it only has three output ports.
If you specify more than one motor with a single call of rotate, it will rotate these motors sequentially, i.e. one after another by default. However, if you are using the EV3, you have the option of rotating them all at once, proportionally, by setting the simult parameter as True. For example:
ev3device.rotate(180, -360, 720, 90, speed=80, simult=True)
This command rotates all four motors simultaneously ― the first motor makes half a turn forward while the second makes a full turn in reverse, while the third makes full two turns, etc. The speed, if specified, in this case always applies to the motor making the largest rotation (greatest absolute angle), while the other speeds are calculated proportionally for them all to finish at the exact same moment, or at least, as closely to the same moment as possible. Note that the simult parameter is available only for the EV3.

Relative motor rotation

Apart from the absolute rotations specified by rotate, you can command relative rotations as well, in a similar fashion. The basic idea is that all the motors begin in the origin, i.e. zero position, and the relative rotation commands rotate the motors relative to the origin and their starting position. This is done by using the rotateto function:
rotateto(motor1, motor2, motor3, motor4, speed)
It is somewhat clearer with an example:
ev3device.rotateto(50, -100)
ev3device.rotateto(-20, -110)
ev3device.rotateto(0, 0)
So, the first command rotates the first motor 50° forward and the second 100° in reverse, just like the absolute rotation command. However, the second one rotates the first motor 70° reverse to "reach" -20, and the second motor 10° backwards, to the -110° from the origin.
The third call rotates the first motor 20° forward and the second 110° forward, in order to return the motor positions to those from the start.
The origin is always 0 for all the motors upon the start of the script. Again, only three motors are available for the NXT, and on EV3 you can additionally specify simult parameter to be True if you want all the movements done simultaneously.

Unsynchronized motor spin (EV3 only)

Occasionally you will need the motors to just start spinning and proceed so until some other instruction comes up. The spin command does just that:
spin(speed1, speed2, speed3, speed4)
It lets you specify the speed (which may be negative as well for the reverse direction) for each motor. Passing zero as a parameter stops the motor, and None does not change anything about it. For example:
ev3device.spin(-50, 100, None, 20)
This command spins the first motor with half speed reverse, the second with full speed forward, and the fourth with 20% speed forward. The motors keep turning and the control is passed back to the Python script, i.e. as said in the title, this function is not synchronized.
Typical usage for unsynchronized spin is i.e. letting a car drive and then checking some sensor value until it needs to stop. This is much more convenient than turning the motor a little, checking the sensor, turning some more, etc. which would cause a rather shaky movement.
You can change the speeds of these motors anytime by reinvoking spin with new parameters. To stop them all, you can use the stop function:
ev3device.stop()
This stops all the motors and is actually equivalent to ev3device.spin(0, 0, 0, 0).

Reading sensor value (EV3 only)

If you are using EV3 sensors, you can use the sensor function to read their measured values. Its syntax requires only one parameter:
sensor(port)
This parameter, ranging from 1 to 4, specifies the EV3 hardware input port the sensor in question is connected to. It returns a number which is the value measured by the sensor. With the standard sensors, it works as follows (though it should work similarly for any other sensor as well):
  • Touch sensor returns either 0 if not pressed, and a nonzero value if pressed.
  • Infrared sensor and ultrasonic sensor return the estimated distance to the target in front of them.
  • Light sensor returns the percentage amount (0-100) of the reflected light, though more about it later.
Apart from the EV3 sensors, you can use the NXT sensors connected to the EV3 device in the same way. An example:
ev3device.sensor(1) returns 60 if the target is 60 cm ahead of the infrared sensor connected to the EV3 in the port 1.

Reading light sensor value (EV3 only)

Since the light sensor can work in several modes, it has the honour of having its own function sensor_light. Apart from the port number, it also requires an additional parameter that specifies the mode it should be running in:
ev3device.sensor_light(2, 'COLOURS')
This commands the light sensor connected to the EV3 port number 2 to switch to the colour scanning mode and return the measured value.
There are three modes that can be specified, either by a string as shown in the above example, or with an integer:
  • 0 or 'REFLECT': measure the reflected light. Returns the percentage of reflected light (0-100).
  • 1 or 'AMBIENT': measure the ambient light level, and return it as a percentage (0-100).
  • 2 or 'COLOURS': measure the colour the sensor points to and return its code along with its name. In this mode, the sensor always returns a tuple, i.e. a pair of a colour number and the name of the colour. The colour codes are: 0 - not available (nothing measured), 1 - black, 2 - blue, 3 - green, 4 - yellow, 5 - red, 6 - white and 7 - brown. I.e. if the sensor points at a blue surface, calling the function returns (2, 'BLUE').
Note that the sensor needs to be rather close to the measured object in the colour detection mode.

Playing tones (EV3 only)

You can use the EV3 to play a tone with a specified frequency, duration and volume:
tone(frequency, volume, duration)
Frequency is specified in Hertz (Hz), the volume in percentage (1-100), and the duration in milliseconds, i.e. 1000 corresponds to a duration of one second. An example:
ev3device.tone(440, 75, 500)
This command plays an A note (440 Hz) at 75% loudness for 500 milliseconds (half a second).

Starting a MindCtrl program (NXT only)

In order to perform the functions correctly, the MindControl executable needs to keep running on the NXT device. You can either do it manually by choosing it in the appropriate menu on the device, or with a script, using a start function. It has no parameters:
nxtdevice.start()
This is equivalent to you starting it manually. If you are going to use this function, make sure it is started before any movements are sent to that particular NXT device.

Disconnecting a device

Although a very pragmatic programmer may argue that it may not be required, a proper purits will always insist that the connection to the device be terminated once it is not required anymore. You need to terminate it by using the disconnect function without parameters, for example:
ev3device.disconnect()
After this command you cannot call (at least not successfully) any functions on ev3device unless you reinstance it under the same name. Keep in mind that you need to disconnect every device independently!

Logging

By default, all the instructions and the communication happening between the computer and the Mindstorms devices is logged both to the console and the log file mindctrl.log. These are actually controlled by the module-level variables logtofile and logtoconsole. Therefore, if you want to disable logging to console, use:
mindctrl.logtoconsole=False
Likewise, for the file:
mindctrl.logtofile=False
You can, of course, set them back to True anytime to resume logging to the console or the file.

Delays after movements

Sometimes you may prefer to have brief delays after each motor movement, either for communication or mechanical reasons. You can set this by using a module-level variable betweendelay. It specified the number of seconds to wait after each movement has been done. It can be a floating-point number as well as an integer. For example, should you desire to have a quarter of a second pause after each moments, use:
mindctrl.betweendelay=250
This can be readjusted anytime in your Python script.

Quick reference

If you are an experienced Pythonist or just want a handy reference of the stuff we just went through, this should help. Note that the bold items apply only to EV3 devices and are not available for NXT.
Classes: mindctrl.EV3(conn='COM8') and mindctrl.NXT(conn='COM4')
Their functions:
rotate(motor1, motor2, motor3, motor4, speed=100, simult=False) - rotate given motor angles with specified speed, simultaneously if simult is True
rotateto(motor1, motor2, motor3, motor4, speed=100, simult=False) - rotate motors to their given relative position, with specified speed and simultaneously if simult is True
spin(speed1, speed2, speed3, speed4) - rotate each motor continuously (not synchronized) with a given speed
stop() - stop all motors
sensor(port) - read the measured value from the sensor connected to the given port
sensor_light(port, mode) - read the measured value from the light sensor connected to the given port, in mode 0 (reflection), 1 (ambient) or 2 (colour detector)
tone(frequency, volume, duration) - play a tone of a given frequency, with a given volume in percentage, for a given duration in seconds
start() - start the MindControl client program (NXT only)
disconnect() - disconnect a device
MindControl module variables:
mindctrl.logtofile - a boolean value specifying whether actions are logged to a file mindctrl.log
mindctrl.logtoconsole - a boolean value specifying whether actions are logged to the console
mindctrl.betweendelay - floating number of seconds of a pause to occur after each motor movement

Example programs

Finally, a couple of sample scripts should clarify everything. You may want to copy and paste them into your own projects after you have finished the installation.
Rotating first two motors on both devices 90° forward, sequentially:
import mindctrl
ev3=mindctrl.EV3('COM8')
nxt=mindctrl.NXT('COM4')
ev3.rotate(90, 90)
nxt.rotate(90, 90)
ev3.disconnect()
nxt.disconnect()
Continuously print the color under the light sensor connected to the EV3 port 1, until the touch sensor at port 2 is pressed:
import mindctrl
mindctrl.logtoconsole=False
ev3=mindctrl.EV3('COM8')
while not ev3.sensor(2):
print(ev3.sensor_light(1, 2)[1])
ev3.disconnect()
Turn all four motors with full speed until the infrared sensor connected to input port 4 detects proximity closer than 30. Then stop all the motors and sound a tone:
import mindctrl
ev3=mindctrl.EV3('COM8')
ev3.spin(100, 100, 100, 100)
while not ev3.sensor(4)<30: pass
ev3.stop()
ev3.tone(250,30,700)
ev3.disconnect()

Download

Click on the links to download the:

Some additional notes

  • If you are unsure with what Python 3.x version to start with, or it does not matter for you, go for the version 3.4 (MindControl was written in it). I can perform only very basic tests on platforms other than Windows, so ― apologies for any incompatibilites if some come up.
  • As far as I was able to test, this module does not interfere with other communications that may be simultaneously happening on other serial (COM) ports.
  • Depending on the Bluetooth device being used, sometimes the connection between it and the Mindstorms device is terminated if no instructions are passed for a while. However, the connection is reestablished automatically if new instructions are sent, so this does not cause any inconvenience but a very brief delay while reconnecting.
  • NXT and EV3 are somewhat different in the way they handle rotation at given angles. NXT tends to rotate the desired angle, overdo a bit, then return, correct again, etc. until the output wheel or axle comes to rest at the desired position. The EV3 is much more controlled, without this shaky movement at its end position. Keep this in mind if using both devices while designing your machine. Use the EV3 where more accurate and homogenous movements are important.
  • Try not to base your scripts around the instructions taking a consistent amount of time. Due to many layers and processes each of them starts, and their dependency on external factors, their duration may be unpredictable. If you need accurately timed processes (e.g. building a clock), I'd suggest you keep a master timer in your Python script and use it as a sole reference. Standard module time can help you a lot here.
  • As long as they do not interfere with each other's motors or sensors, calling functions in a multithreaded program should work fine. However, each thread should use the same instance of the EV3 or NXT object, instead of each thread opening its own.
  • Daisy-chaining the EV3 devices is not implemented as such ― you should connect to each EV3 device via Bluetooth independently instead.
  • Letting motors perform very small movements (only a couple of degrees) sometimes do not produce any real results. Only after a few small movements are aggregated to something more (say, 10°), will the motor indeed rotate. If you need very small rotations, I'd rather suggest gearing the motor down.

Obligatory legal stuff

You can freely use MindControl whatever way you wish, and distribute it as much as you like, either standalone or as a component of your other projects. However, you may not sell it as such, and please keep Legoism.info credited. MindControl is provided as-is, I hold no responsibility to any kind of damage you may have done to anything or anyone by using MindControl or any of its derivatives. For any unclarities, Apache License 2.0 applies.


No comments:

Post a Comment