Install

The contents in this section refer to a Linux device, if you’re using a different OS please refer to the ev3dev website.

Select an image to download from http://www.ev3dev.org/downloads/, I tested the development branch based on the stable Debian version. Extract the image and flash your SD card:

$ unxz snapshot-ev3dev-stretch-ev3-generic-2017-11-01.img.xz
$ sudo dd if=snapshot-ev3dev-stretch-ev3-generic-2017-11-01.img of=/dev/mmcblk0 bs=4M

Follow the Bluetooth guide to setup the network between the brick and your host. Bluetooth is recommended over USB to remotely control the robot with Once it’s done, connect to it using SSH:

$ ssh robot@10.42.0.164

The default password is maker.

Update the software index to install tmux, a useful program to detach your session from the Unix shell that started the connection to the robot. Also update the python library while you’re there:

$ sudo -i
# apt update
# apt install tmux
# apt install --only-upgrade python3-ev3dev
# exit
$ tmux

In case the connection to the brick is temporarily lost while working inside a tmux session, you can recover it by executing tmux a.

Programming

Python library documentation can be found on the ev3dev-lang-python Github page. This API allows to easily control motors, sensors, hardware buttons, LCD displays and more from Python code. Full API documentation is available here.

The following examples have been tested with Python3, assuming that the ev3 library has been imported with import ev3dev.ev3 as ev3.

Motors

Large Motor

Motors support different modes. In run-direct mode, the motor will run at the duty cycle specified. Unlike other run commands, changing duty_cycle_sp while running will take effect immediately.

>>> motor = ev3.LargeMotor('outA')
>>> motor.commands
['run-forever', 'run-to-abs-pos', 'run-to-rel-pos', 'run-timed', 'run-direct', 'stop', 'reset']
>>> motor.run_direct()
>>> motor.duty_cycle_sp = 10
>>> motor.duty_cycle_sp = 60
>>> motor.stop()

Depending on how you built your robot, it could be handy to invert the polarity of the motor to avoid specifying negative power values to get it moving forward.

>>> motor = ev3.LargeMotor('outA')
>>> motor.reset()
>>> motor.polarity
'normal'
>>> motor.run_direct()
>>> motor.duty_cycle_sp = 30
>>> motor.stop()
>>>
>>> motor.reset()
>>> motor.polarity = 'inversed'
>>> motor.run_direct()
>>> motor.duty_cycle_sp = 30
>>> motor.stop()

Sensors

Touch Sensor

This is the most trivial sensor available in the set. It only detects whether the button has been pushed or not, without providing pressure levels.

>>> sensor_touch = ev3.TouchSensor()
>>> sensor_touch.is_pressed
0
>>> sensor_touch.is_pressed
1

Ultrasonic Sensor

A simple sensor that allows to measure distance in centimeters. It is pretty accurate and the view cone is quite narrow.

>>> sensor_ultrasonic = ev3.UltrasonicSensor()
>>> sensor_ultrasonic.distance_centimeters
14.5
>>> sensor_ultrasonic.distance_centimeters
54.2

Notice that when the eyes are fully covered by an obstacle, the reported distance is 255.

Infrared Sensor

The infrared sensor supports different modes which allows, respectively, to measure the distance to an object in front of it, the position of the infrared beacon and receive commands from the beacon acting as a remote controller. It is not very accurate

>>> sensor_ir = ev3.InfraredSensor()
>>> sensor_ir.modes
['IR-PROX', 'IR-SEEK', 'IR-REMOTE', 'IR-REM-A', 'IR-S-ALT', 'IR-CAL']

Proximity is returned in percentage, 100% is approximately 70cm. When the sensor is covered the result could be 100, 1 or a random value. The view cone is large and it’s significantly affected by interferences with other IR sensors, even when they’re place perpendicularly.

>>> sensor_ir.mode
'IR-PROX'
>>> sensor_ir.proximity
83
>>> sensor_ir.proximity
8

IR Seeker mode is supported by the BeaconSeeker class. Heading is the angle (either positive or negative) where the beacon is located and distance reports the gap between the sensor and the beacon.

>>> sensor_ir = ev3.BeaconSeeker()
>>> sensor_ir.heading_and_distance
(25, 26)

Both the IR beacon and sensor support 4 different channels that could be used to locate the position of up to 4 different beacons. The channel on the beacon can be set using the red switch, while on the sensor it can be modified using the attribute sensor_ir._channel.

Remote control can be enabled using the ev3.RemoteControl class:

>>> sensor_ir = ev3.RemoteControl()
>>> sensor_ir.blue_up
False
>>> sensor_ir.blue_up
True

Color Sensor

This rather powerful sensor supports different modes, such as measuring ambient light, identify a color among a set of 8 standard colors, return the amount of reflected light and work in raw mode, theoretically supporting a wider set of colors.

Ambient light mode (blue led on):

>>> sensor_col = ev3.ColorSensor()
>>> sensor_col.ambient_light_intensity
38

Reflected light mode (red led on). Notice that it’s not needed to manually switch mode, it’s set automatically once using reading the attribute:

>>> sensor_col.reflected_light_intensity
38

Color mode (all leds rapidly cycling). The reported value is determined by several factors, such as the material, the distance between the sensor and the colored surface, the ambient light. The colors detected by the sensor are no color (0), black (1), blue (2), green (3), yellow (4), red (5), white (6), brown (7):

>>> sensor_col.color
5

Raw color mode, returned as a RGB triple:

>>> sensor_col.raw
(22, 19, 18)

In case the task is to follow a line and identify a colored marker at the same time, it is advisable to use the raw mode and transform RGB values into HSV and use the saturation value for the line tracking part, while other values to detect the marker.

Example

We provide the source code of a simple line following program:

#!/usr/bin/env python3

import sys
import signal
from time import sleep
import ev3dev.ev3 as ev3


# misc stuff
brick_but = ev3.Button()

# sensors
sensor_col = ev3.ColorSensor()
sensor_ultrasonic = ev3.UltrasonicSensor()
sensor_touch = ev3.TouchSensor()

# motors
motor_left = ev3.LargeMotor('outD')
motor_right = ev3.LargeMotor('outA')

devices = [sensor_col, sensor_ultrasonic, sensor_touch, motor_left, motor_right]

# global vars that can be adjusted after performing calibration
min_ref = 10
max_ref = 87
mid_ref = (10 + 87) / 2
kp = 0.9
power = -80


def stop_motors():
    motor_left.stop(stop_action='brake')
    motor_right.stop(stop_action='brake')


def signal_handler(signal, frame):
    stop_motors()
    print('Ctrl+C pressed, terminating...')
    sys.exit(1)


def steering(course, power):
    if course >= 0:
        return power, power - ((power * course) // 100)
    else:
        return power + ((power * course) // 100), power


def run():
    sensor_col.mode = 'COL-REFLECT'

    motor_left.run_direct()
    motor_right.run_direct()

    while not (brick_but.any() or sensor_touch.is_pressed):
        # stop and beep if there's an obstacle
        while sensor_ultrasonic.distance_centimeters < 15:
            stop_motors()
            ev3.Sound.tone([(100, 200, 0), (150, 200, 0), (130, 200, 0), (300, 1000, 0)]).wait()
            sleep(1)
        else:
            motor_left.run_direct()
            motor_right.run_direct()

        ref = sensor_col.value()
        # error ranges between -50 (W) and 50 (B), when it's close to 0 ref == mid_ref
        error = mid_ref - (100 * (ref - min_ref) / (max_ref - min_ref))
        course = (kp * error)
        motor_left.duty_cycle_sp, motor_right.duty_cycle_sp = steering(course, power)

        sleep(0.01)

    stop_motors()


def main():
    # check that all devices are connected
    assert all(d.connected for d in devices) is True

    # capture ctrl-c and stop motors in that case
    signal.signal(signal.SIGINT, signal_handler)

    ev3.Sound.speak("3 2 1 go!").wait()
    sleep(3)
    run()


if __name__ == '__main__':
    main()