Tony Hirst, The Open University, @psychemedia, blog.ouseful.info
This notebook describes the basic operation of the Ev3 / ev3dev Python bindings for the ev3dev-jessie-2015-12-30 release.
To set up the ev3 with IPython and configure a Jupyter notebook server to work with a remote IPython kernel running on am ev3 brick see Using IPython on Lego EV3 Robots Running Ev3Dev.
Reference: Python language bindings for ev3dev; documentation included herein was originally published as inline code documentation on the original repository under a GNU GENERAL PUBLIC LICENSE (Version 2, June 1991), re-presented as ev3dev-lang.readthedocs.io.
This notebook uses ipywidgets
. Run Jupyter with: jupyter nbextension enable --py --sys-prefix widgetsnbextension
#Wake the process up (this may take some time, up to a minute or two)
print('hello world')
hello world
#Python bindings exposed via https://github.com/rhempel/ev3dev-lang-python
from ev3dev.auto import *
#Import time for delays
import time
#Bring in a useful constant...
from math import pi
#Python 2.7 needs nudging when it comes to doing division
from __future__ import division
(The version 10 release of the ev3dev kernel introduced a revised Tacho Motor class that may be incompatible with these notes.)
M=Motor()
#Show docs
M?
The IPython notebooks support tab completion. Enter the name of an object, followed by a . and then hit TAB: you will be presented with a list of valid methods that can be called on that object.
#Show methods in base Motor() class using tab completion. Type: M. followed by TAB
M.
#Show values of OUTPUT_* constants
print(OUTPUT_A, OUTPUT_B, OUTPUT_C, OUTPUT_D)
('outA', 'outB', 'outC', 'outD')
The large servo motor is the motor most likely to be used to drive the wheels on a mobile robot.
# Define a large servo motor on output A
m = LargeMotor(OUTPUT_A)
#Legitimate output values: OUTPUT_A | OUTPUT_B | OUTPUT_C | OUTPUT_D
A list of gotchas and error fixes found to date..
File -> Close and Halt
, make sure notebook tab is closed, then re-open notebook.Kernel -> Reconnect
. If that doesn't work, File -> Close and Halt
, close the notebook tab, then re-open the notebook.Kernel -> Interrupt
then run the motor stop command (e.g. m.stop()
).milliseconds
, time.sleep()
is in seconds. Maybe we should define a function def wait(period_in_ms): time.sleep(period_in_ms / 1000)
?AttributeError: 'LargeMotor' object has no attribute '_path'
: check that a motor is actually connected to the port you have assigned it to.IOError: [Errno 19] No such device
: each motor can only be assigned to one variable; if you assign the same physical motor port to two variables, only the last set variable will be valid.Kernel -> Interrupt
. You could also try unplugging the motor; however, if you do unplug the motor, you will need to reconnect to it (for example, re-run m = LargeMotor(OUTPUT_A)
).AttributeError: 'Sensor' object has no attribute '_path'
: check that there is at least one sensor connectedTypeError: 'unicode' object is not callable
: are you trying to read a sensor's units using ,units()
? Omit the brackets and call .units
.OSError: [Errno 2] No such file or directory: '/sys/class/lego-sensor'
on trying to set up RemoteControl()
: check that the infra-red sensor is connectedMoving a large number of contiguous cells up or down, deleting them, or copy /pasting them within a notebook: click the first cell in the block to highligt it then shift-click the last cell in the required block. The cells should all be highlighted blue. Use the toolbar up/down arros to move the cells as a block.
connected
: check that the specified motor is connected; returns True
or False
m.connected
True
commands
lists the commands available for the connected motor; note that the python bindings use underscores rather than dashes in the method names.m.commands
[u'run-forever', u'run-to-abs-pos', u'run-to-rel-pos', u'run-timed', u'run-direct', u'stop', u'reset']
#Show help for a particular command (it will be displayed at the bottom of the notebook)
m.reset?
reset
will reset all of the motor parameter attributes to their default value. This will also have the effect of stopping the motor.m.reset()
run_forever
will cause the motor to run until another command is sent.#m.run_forever() will have no effect unless a non-zero duty cycle has previously been set
m.run_forever(duty_cycle_sp=75)
#Wait for one second and then stop the motor
time.sleep(1)
m.stop()
run_to_abs_pos
will run to an absolute position specified by position_sp
and then stop using the command specified in stop_action
.m.run_to_abs_pos(position_sp=10)
run_to_rel_pos
will run to a position relative to the current position value. The new position will be current position
+ position_sp
. When the new position is reached, the motor will stop using the command specified by stop_action
.m.run_to_rel_pos(position_sp=-10)
run-timed
will run the motor for up to the amount of time specified in time_sp
and then stop the motor using the command specified by stop_action
.#Run for 1000ms with a duty_cycle of 75% (range -100% to 100%)
m.run_timed(time_sp=1000, duty_cycle_sp=75)
Writing another run command while the run_timed
command is running will reset the motor timer.
m.run_timed(time_sp=10000, duty_cycle_sp=75)
m.run_timed(time_sp=1000, duty_cycle_sp=75)
To run the motor for a guaranteed fixed period of time, blocking the ability of other run motor commands to be issued within that period, use run_forever
and time.sleep()
.
def run_for_period_blocking(m, time_sp, duty_cycle_sp=75):
m.run_forever(duty_cycle_sp=duty_cycle_sp)
#Sleep operates in seconds; convert from milliseconds
time.sleep(time_sp / 1000 )
m.stop()
run_for_period_blocking(m,2000)
run_direct
will run the motor at the duty cycle specified by duty_cycle_sp
. Unlike other run commands, changing duty_cycle_sp
while running will take effect immediately.#Start the motor running with a specified duty_cycle_sp
m.run_direct(duty_cycle_sp=75)
#Change the duty_cycle_sp
m.duty_cycle_sp=50
#Change the duty_cycle_sp
m.duty_cycle_sp=-50
#Change the duty_cycle_sp
m.duty_cycle_sp=10
#Stop the motor
m.stop()
stop
will stop any of the run commands, whether or not they are complete, using the command specified by stop_action
.m.run_forever(duty_cycle_sp=25)
m.stop()
The following paramters are available for reading and, in some cases, setting.
address
returns the name of the port that this motor is connected tom.address
u'outA'
state
: reading returns a list of state flags. Possible flags are running
, ramping
, holding
and stalled
.m.state
[]
m.run_forever(duty_cycle_sp=30)
m.state
[u'running']
m.stop()
time_sp
: writing specifies the amount of time the motor will run when using the run-timed command. Reading returns the current value. Units are in milliseconds.m.time_sp
1000
m.time_sp=200
m.run_timed()
stop_commands
returns a list of stop actions supported by the motor controller. Possible values are coast
, brake
and hold
.coast
means that power will be removed from the motor and it will freely coast to a stop;brake
means that power will be removed from the motor and a passive electrical load will be placed on the motor. This is usually done by shorting the motor terminals together. This load will absorb the energy from the rotation of the motors and cause the motor to stop more quickly than coasting;hold
does not remove power from the motor. Instead it actively tries to hold the motor at the current position. If an external force tries to turn the motor, the motor will ‘push back’ to maintain its position.m.stop_commands
[u'coast', u'brake', u'hold']
stop_command
: reading returns the current stop action/command, writing sets the stop action/command. The value determines the motors behavior when command is set to stop. Also, it determines the motors behavior when a run command completes. See stop_commands
for a list of possible values.m.stop_command
u'coast'
m.run_forever(duty_cycle_sp=100)
time.sleep(0.5)
#brake: power will be removed from the motor and a passive electrical load will be placed on the motor.
#This is usually done by shorting the motor terminals together.
#This load will absorb the energy from the rotation of the motors and cause the motor to stop quickly
m.stop_command='brake'
m.stop()
m.run_forever(duty_cycle_sp=100)
time.sleep(0.5)
#hold: does not remove power from the motor.
#Instead it actively tries to hold the motor at the current position.
#If an external force tries to turn the motor, the motor will ‘push back’ to maintain its position.
m.stop_command='hold'
m.stop()
m.run_forever(duty_cycle_sp=100)
time.sleep(0.5)
#coast: power will be removed from the motor and it will freely coast to a stop;
m.stop_command='coast'
m.stop()
count_per_m
returns the number of tacho counts in one meter of travel of the motor. Tacho counts are used by the position and speed attributes, so you can use this value to convert from distance to tacho counts. (linear motors only)#Not used for rotation servos
#m.count_per_m
count_per_rot
returns the number of tacho counts in one rotation of the motor. Tacho counts are used by the position and speed attributes, so you can use this value to convert rotations or degrees to tacho counts. (rotation motors only)m.count_per_rot
360
#Counts per rotation
cpr=m.count_per_rot
#Tacho count per degree - divide the tacho count per rotation nu the number of degrees in one rotation (360)
tc= cpr / 360
print('With {cpr} tacho counts per rotation, there are {tc} tacho counts per degree.'.format(cpr=cpr, tc=tc))
With 360 tacho counts per rotation, there are 1.0 tacho counts per degree.
duty_cycle
returns the current duty cycle of a running motor. Units are percent. Values are -100
to 100
.m.duty_cycle
0
m.run_direct(duty_cycle_sp=25)
m.duty_cycle
25
m.run_direct(duty_cycle_sp=-35)
m.duty_cycle
-35
m.stop()
duty_cycle_sp
: writing sets the duty cycle setpoint. Reading returns the current value. Units are in percent. Valid values are -100
to 100
. A negative value causes the motor to rotate in reverse.m.run_direct(duty_cycle_sp=25)
m.duty_cycle_sp
45
m.duty_cycle_sp=-20
m.duty_cycle_sp
-20
m.stop()
full_travel_count
returns the number of tacho counts in the full travel of the motor. When combined with the count_per_m atribute, you can use this value to calculate the maximum travel distance of the motor. (linear motors only)#Not used for rotation servos
#m.full_travel_count
polarity
sets the polarity of the motor. With normal
polarity, a positive duty cycle will cause the motor to rotate clockwise. With inversed
polarity, a positive duty cycle will cause the motor to rotate counter-clockwise. Valid values are normal
and inversed
.m.polarity
u'normal'
m.run_timed(time_sp=1000, duty_cycle_sp=50)
m.polarity='inversed'
m.run_timed(time_sp=1000, duty_cycle_sp=50)
m.polarity='normal'
position
returns the current position of the motor in pulses of the rotary encoder. When the motor rotates clockwise, the position will increase. Likewise, rotating counter-clockwise causes the position to decrease. Writing will set the position to that value.m.position
736
Now turn the wheel by hand a short distance:
#Check the position after turning the wheel by hand a short way
m.position
630
And turn it a short way back the other way:
#Now turn the wheel by hand a short way in the other direction
m.position
780
position_sp
: writing specifies the target position for the run_to_abs_pos
and run_to_rel_pos
commands. Reading returns the current value. Units are in tacho counts. You can use the value returned by counts_per_rot
to convert tacho counts to/from rotations or degrees.m.position_sp
321
#Counts per rotation
cpr=m.count_per_rot
#Degrees per tacho count - divide the number of degrees in one rotation (360) by the tacho count per rotation
dpc = 360 / cpr
print('With {cpr} tacho counts per rotation, each tacho count corresponds to {dpc} degrees.'.format(cpr=cpr, dpc=dpc))
With 360 tacho counts per rotation, each tacho count corresponds to 1.0 degrees.
#Number of tacho counts for a given number of rotations
number_of_rotations = 1.5
required_tacho_count = number_of_rotations * m.count_per_rot
print('{n} rotations corresponds to a tacho count of {c}'.format(n=number_of_rotations, c=required_tacho_count))
1.5 rotations corresponds to a tacho count of 540.0
speed
returns the current motor speed in tacho counts per second. Note, this is not necessarily degrees (although it is for LEGO motors). Use the count_per_rot
attribute to convert this value to RPM or deg/sec.m.speed
0
m.run_forever(duty_cycle_sp=75)
m.speed
698
m.stop()
speed_sp
: writing sets the target speed in tacho counts per second used for all run_*
commands except run_direct
. Reading returns the current value. A negative value causes the motor to rotate in reverse with the exception of run_to_*_pos
commands where the sign is ignored. Use the count_per_rot
attribute to convert RPM or deg/sec to tacho counts per second. Use the count_per_m
attribute to convert m/s to tacho counts per second.m.speed_sp
0
speed_regulation_enabled
enables PID speed regulation; legitimate values are on
and off
(off
is the default).m.reset()
m.speed_regulation_enabled
u'off'
m.run_forever(duty_cycle_sp=75)
m.stop()
#Setting the speed with speed_regulation_enabled='off' has no effect
m.speed_sp=200
m.run_forever(duty_cycle_sp=75)
m.stop()
#Switch speed regulation on
m.speed_regulation_enabled='on'
#The duty cycle setting is ignored and the speed is controlled
m.run_forever(duty_cycle_sp=75)
m.stop()
m.speed_regulation_enabled='off'
ramp_up_sp
: when ramp_up_sp
and ramp_down_sp
are set to non-zero values, the speed will ramp up from 0 to 100% duty cycle over the specified time period. If the duty cycle is limited by the duty_cycle_sp
or speed regulation mode is on, then the ramp time will be less. For example if we have duty_cycle_sp set to 50 (and speed_regulation_enabled
mode is off) and ramp_up_sp
is 500ms, when we start the motor, it will ramp up from 0 to 50% duty cycle over the span of 250ms and then stay at 50% duty cycle until there is a stop command (reference). Writing to ramp_up_sp
sets the ramp up setpoint. Reading returns the current value. Units are in milliseconds and must be positive. When set to a non-zero value, the motor speed will increase from 0 to 100% of the maximum over the span of this setpoint. The actual ramp time is the ratio of the difference between the speed_sp
and the current speed
and max_speed multiplied by ramp_up_sp
.#Speed regulation should be set to on:
m.speed_regulation_enabled='on'
u'off'
#Speed set point should be non_zero
m.speed_sp=200
m.ramp_up_sp
0
m.ramp_up_sp=1000
m.run_timed(time_sp=3000)
m.speed_regulation_enabled='off'
ramp_down_sp
: writing sets the ramp down setpoint. Reading returns the current value. Units are in milliseconds and must be positive. When set to a non-zero value, the motor speed will decrease from 0 to 100% of max_speed over the span of this setpoint. The actual ramp time is the ratio of the difference between the speed_sp
and the current speed and the maximum speed multiplied by ramp_down_sp
.m.ramp_down_sp
0
speed_regulation_p
, the proportional constant for the speed regulation PID. (used with speed_regulation_enabled='on'
)m.speed_regulation_p
1000
speed_regulation_i
, the integral constant for the speed regulation PID. (used with speed_regulation_enabled='on'
)m.speed_regulation_i
60
speed_regulation_d
, the derivative constant for the speed regulation PID. (used with speed_regulation_enabled='on'
)m.speed_regulation_d
0
The medium servo motor inherits the same methods from the Motor
class as the LargeMotor
.
# Define a medium servo motor on output D
mm = ev3.MediumMotor(OUTPUT_D)
The commands and parameters are the same as for the LargeMotor.
The EV3 brick has several output devices of its own that we can access, including several LEDs and a speaker.
There are two LEDs on either side of the EV3 button, a red one and a green one, that can be controlled as a group (left group or right group). Enabling both LEDs in a group gives yellow and range colours, as well as the individual red and green.
L=Leds
all_off
: turn all LEDs offL.all_off()
set_color
: used to set the colour of the left or right group. Usage: .set_color(group, color, pct=1)
L.set_color(L.LEFT, L.RED)
time.sleep(1)
L.set_color(L.RIGHT,L.YELLOW)
time.sleep(1)
L.set_color(L.LEFT,L.GREEN)
time.sleep(1)
L.set_color(L.RIGHT,L.AMBER)
time.sleep(1)
L.set_color(L.LEFT,L.ORANGE)
time.sleep(1)
L.set_color(L.RIGHT,L.YELLOW)
L.all_off()
set
: sets brightness of leds in the given group to the values specified in color tuple. When percentage is specified, brightness of each LED is reduced proportionally. set
can also be used to trigger an LED to flash according to a timer. Usage: .set(Leds.LEFT, brightness_pct=0.5, trigger='timer')
L.set(L.LEFT, brightness_pct=0.5, trigger='timer')
L.all_off()
The properties of each of the LEDs can be specified spearately. The LEDs are referenced from L=Leds
as:
L.green_left
L.green_right
L.red_left
L.red_right
max_brightness
: returns the maximum allowable brightness value for an LEDL.green_left.max_brightness
255
brightness
: sets the brightness level. Possible values are from 0
to max_brightness
.L.set_color(L.LEFT,L.RED)
L.red_left.brightness = L.red_left.max_brightness
time.sleep(3)
L.red_left.brightness = int(L.red_left.max_brightness / 2)
time.sleep(3)
L.red_left.brightness = 10
time.sleep(3)
L.all_off()
brightness_pct
: returns led brightness as a fraction of max_brightness
in the range 0
to 1
. NEED TO CHECK WHETHER WE CAN DO THIS DYNAMICALLY OR WHETHER WE NEED TO SET_COLORL.set_color(L.RIGHT,L.GREEN)
L.green_right.brightness_pct = 1
time.sleep(3)
L.green_right.brightness_pct = 0.5
time.sleep(3)
L.green_right.brightness_pct = 0.01
time.sleep(3)
L.green_right.brightness_pct = 1
L.all_off()
triggers
: show the triggers associated with the LEDs. A trigger is a kernel based source of LED events. Triggers can either be simple or complex. A simple trigger isn’t configurable and is designed to slot into existing subsystems with minimal additional code. Examples are the heartbeat
and timer
triggers. Complex triggers, whilst available to all LEDs, have LED specific parameters and work on a per LED basis as with the timer
trigger.L.green_right.triggers
[u'none', u'mmc0', u'timer', u'heartbeat', u'default-on', u'transient', u'legoev3-battery-charging-or-full', u'legoev3-battery-charging', u'legoev3-battery-full', u'legoev3-battery-charging-blink-full-solid', u'rfkill0']
trigger
: sets the led trigger. The timer
trigger will periodically change the LED brightness between 0 and the current brightness setting. The on and off time can be specified via delay_{on,off}
attributes in milliseconds. You can change the brightness value of a LED independently of the timer trigger. However, if you set the brightness value to 0 it will also disable the timer trigger. [TH: I didn't have much success with the delay_{on,off}
settings?]#Use the left LED as a heartbeat
L.set(L.LEFT, brightness_pct=0.5, trigger='heartbeat')
#Add in a heartbeat to the right
L.set(L.RIGHT, brightness_pct=0.5, trigger='heartbeat')
L.all_off()
L.green_right.brightness_pct = 0.01
L.red_right.brightness_pct = 1
L.set(L.RIGHT, brightness_pct=0.5, trigger='timer')
#Do some housekeeping
L.all_off()
for l in [L.green_right,L.red_right,L.green_left,L.red_left]:
l.trigger='none'
l.brightness_pct=1
The EV3 can play sound files and generate sounds of its own, as well as generating speech from text.
beep()
: call beep command with the provided arguments (if any). See beep
man page or do a web search for linux beep music for inspiration.play(wav_file)
to play a .wav file.speak(text)
: speak the given text aloud.Sound.speak('Welcome to the O U !').wait()
pass
tone(frequency, duration)
: play single tone of given frequency (Hz) and duration (milliseconds).Sound.tone(392,350).wait()
pass
tone(tone_sequence)
: play a tone sequence. The tone_sequence parameter is a list of tuples, where each tuple contains up to three numbers. The first number is frequency in Hz, the second is duration in milliseconds, and the third is delay in milliseconds between this and the next tone in the sequence.Sound.tone([
(392, 350, 100), (392, 350, 100), (392, 350, 100), (311.1, 250, 100),
(466.2, 25, 100), (392, 350, 100), (311.1, 250, 100), (466.2, 25, 100),
(392, 700, 100)
]).wait()
pass
This section describes how to use the various sensors:
The infra-red sensor is used in conjunction with the remote control, which is also described in this section.
If we only connect one sensor of each sensor type to the ev3, we do not need to declare which port the sensor was connected to - the brick can work it out for itself.
Sensors may be attached to INPUT_1, INPUT_2, INPUT_3 or INPUT_4.
print(INPUT_1, INPUT_2, INPUT_3, INPUT_4)
('in1', 'in2', 'in3', 'in4')
The base clase doesn't have much to show of interest.
S=Sensor()
#Use tab completion to see the generic methods associated with the base Sensor() class
#Enter S. followed by TAB
S.
The touch sensor is a simple switch returning one of two possible integer values:
0
to show the switch is open (that is, it is not being pressed)1
to show the switch is closed (that is, it is being pressed)ts=TouchSensor()
connected
: test whether a sensor of this type is connectedts.connected
True
address
: identify which input port the sensor is connected tots.address
u'in1'
driver_name
: identify which software driver is used to interact with the sensorts.driver_name
u'lego-ev3-touch'
value()
: show the current value of the switch, as a binary integer, indicating whether the current touch sensor is being pressed or not: 0
for open, 1
for pressed.#Without touching the switch, check its current value
ts.value()
0
#Now hold the touch sensor closed and try again
ts.value()
1
ambient_light_intensity()
: ambient light intensity. Light on sensor is dimly lit blue.blue()
: blue component of the detected color, in the range 0-1020.connected
: test whether a sensor of this type is connectedcs=ColorSensor()
cs.modes
[u'COL-REFLECT', u'COL-AMBIENT', u'COL-COLOR', u'REF-RAW', u'RGB-RAW', u'COL-CAL']
cs.mode='RGB-RAW'
cs.mode
u'COL-REFLECT'
print(cs.value(0),cs.value(1),cs.value(2))
(20, 1, 2)
cs.num_values
3
cs.
cs.connected
True
address
: identify which input port the sensor is connected tocs.address
u'in4'
driver_name
: identify which software driver is used to interact with the sensorcs.driver_name
u'lego-ev3-color'
modes
: display the available modes the sensor can takemode
: show/set the current sensor modecolor()
: color detected by the sensor, categorized by overall value. (This method didn't exist for me. However, with the mode set to 'COL-COLOR', the .value()
is in this range andd seems to map to appropriate corresponding color values.0
: No color1
: Black2
: Blue3
: Green4
: Yellow5
: Red6
: White7
: Browngreen()
: green component of the detected color, in the range 0-1020.red()
: red component of the detected color, in the range 0-1020.reflected_light_intensity()
: reflected light intensity as a percentage. Light on sensor is red.The gyro sensor can be used to allow the robot to go in a particular direction.
gs=GyroSensor()
connected
: test whether a sensor of this type is connectedgs.connected
True
address
: identify which input port the sensor is connected togs.address
u'in1'
driver_name
: identify which software driver is used to interact with the sensorgs.driver_name
u'lego-ev3-gyro'
gs.
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-205-374e93a77c6f> in <module>() ----> 1 gs.angle AttributeError: 'GyroSensor' object has no attribute 'angle'
gs.units
u'deg'
gs.modes
[u'GYRO-ANG', u'GYRO-RATE', u'GYRO-FAS', u'GYRO-G&A', u'GYRO-CAL', u'TILT-RATE', u'TILT-ANG']
gs.mode
u'GYRO-ANG'
gs.mode='GYRO-RATE'
gs.value()
8
Some of the sensor specific functions described in the docs do not appear to be available?
If there are several sensors in the same room, I suspect that they may interfere with each other?
us=UltrasonicSensor()
connected
: test whether a sensor of this type is connectedus.connected
True
address
: identify which input port the sensor is connected tous.address
u'in2'
driver_name
: identify which software driver is used to interact with the sensorus.driver_name
u'lego-ev3-us'
us.value()
2550
us.units
u'cm'
The value/units combination looks wrong to me?
proximity()
: a measurement of the distance between the sensor and the remote, as a percentage. 100% is approximately 70cm/27in.ir=InfraredSensor()
connected
: test whether a sensor of this type is connectedir.connected
True
address
: identify which input port the sensor is connected toir.address
u'in3'
driver_name
: identify which software driver is used to interact with the sensorir.driver_name
modes
: display the modes available for this sensor.ir.modes
mode
: set the mode for the infra-red sensor - IR-PROX
| IR-SEEK
| IR-REMOTE
| IR-REM-A
| IR-S-ALT
| R-CAL
(reference).ir.mode='IR-PROX'
mode='IR-PROX'
: in the proximity mode, the infra-red sensor returns a value corresponding to distance (range 0 to 100, where 100% is approximately 70cm/27in).ir.mode='IR-SEEK'
mode='IR-SEEK'
: in the seek mode, the values report back on messagees received via a particular channel. When looking in the same direction as the sensor, a heading of -25 is far left and +25 is far right. On the distance measure, 100% is approximately 70cm/27in. The absence of a beacon on a channel can be detected when distance == -128 and heading == 0.value0
: channel 1, heading (-25 to 25)value1
: channel 1, distance (-128 and 0 to 100)value2
: channel 2, heading (-25 to 25)value3
: channel 2, distance (-128 and 0 to 100)value4
: channel 3, heading (-25 to 25)value5
: channel 3, distance (-128 and 0 to 100)value6
: channel 4, heading (-25 to 25)value7
: channel 4, distance (-128 and 0 to 100)ir.mode='IR-REMOTE'
mode='IR-REMOTE'
: in the remote mode, the infra-red sensor returns a value that can be decoded to identify which remote control buttons have been pressed. Conventionally, red == left and blue == right. The same codes are used for all channels. Channel 1..4 report on sensor value 0..3.The Lego remote control provides a means of controlling the Ev3 via the infra-red sensors, or it can be used to set up a beacon that can operate on one of four channels.
R=RemoteControl()
connected
: Boolean value identifiying whether the infra-red sensor is connectedR.connected
True
beacon
: Boolean value identifiying whether the beacon is enabledR.beacon
True
blue_down
| blue_up
| red_down
| red_up
: Boolean value identifiying whether the corresponsding remote button is currently being pressedany()
: Boolean value identifiying whether any buttons on the remote are being pressed#Run this cell without any remote button pressed
R.any()
#Then run it again whilst pressing one or more buttons on the remote
buttons_pressed()
: return a list containing the names of any buttons currently being pressed#Run this cell without any remote button pressed
R.buttons_pressed()
#Then run it again whilst pressing one or more buttons on the remote
We can create a simple dashboard to display which button or buttons are currently being pressed.
The RepeatedTimer()
class should perhaps be included as a helper utility?
#http://stackoverflow.com/a/13151299/454773
#This class allows you to fire an event repeatedly ever interval seconds
from threading import Timer
class RepeatedTimer(object):
def __init__(self, interval, function, name='Repeated Timer', *args, **kwargs):
self._timer = None
self.interval = interval
self.function = function
self.name=name
self.args = args
self.kwargs = kwargs
self.is_running = False
self.start()
def _run(self):
self.is_running = False
self.start()
self.function(*self.args, **self.kwargs)
def start(self):
if not self.is_running:
self._timer = Timer(self.interval, self._run)
#name the timer so we can spot it and kill it
self._timer.setName(self.name)
self._timer.start()
self.is_running = True
def stop(self):
self._timer.cancel()
self.is_running = False
def stop(self):
self._timer.cancel()
self.is_running = False
#Provide some defensive coutnermeasures when handling threads to kill all named timers we've created
#If we make sure we name thread
import threading
#t is here as a placeholder in case we want to pass in a specific thread to kill?
def killThreads(n=None,t=None):
if n is None:
n=['Repeated Timer','Data logger']
for t in threading.enumerate():
if t.getName() in n:
t.cancel()
#We can then kill any threads we know we've created if we have to...
killThreads()
We will use the RepeatedTimer()
to repeatedly check the remote.
We can create a simple text widget to display whether any buttons are currently being pressed on the remote control.
from ipywidgets import widgets
output = widgets.Text()
output
The installed widget Javascript is the wrong version.
Bind a simple display function to the remote control button events and call the timer every tenth of a second until we tell it stop by switching the beacon on.
def test_remote():
output.value= 'No button pressed' if not R.any() else 'Buttons pressed: {}'.format(','.join(R.buttons_pressed))
rt = RepeatedTimer(0.1, test_remote)
#Switch off the monitoring of the remote when the beacon is switched on
while not R.beacon:
pass
#Stop the timer
rt.stop()
on_red_down
| on_red_up
| on_blue_down
| on_blue_up
: remote control button events that we can bind our own handler functions to.#Create a simple text output display
output2= widgets.Text()
output2
The installed widget Javascript is the wrong version.
#Create a function to display when a remote control button event has been raised
def remote_print_button(txt=''):
output2.value=txt
time.sleep(1)
output2.value=''
#Bind event display function calls to the remote control button events
R.on_red_down = lambda x: remote_print_button('red down')
R.on_red_up = lambda x: remote_print_button('red up')
R.on_blue_down = lambda x: remote_print_button('blue down')
R.on_blue_up = lambda x: remote_print_button('blue up')
#A more generic event
#R.on_change
#Start checking the remote
#Enable the beacon to break out of this loop
while not R.beacon:
R.process()
Various diagnostics associated with the power supply can be reported.
P=PowerSupply()
max_voltage
: the maximum voltage in (in microvolts)P.max_voltage
7500000
print('The maximum voltage is {} volts.'.format(P.max_voltage/1000000))
The maximum voltage is 7.5 volts.
min_voltage
: the minimum voltage (in microvolts)P.min_voltage
7100000
measured_amps
: the measured current that the battery is supplying (in amps)P.measured_amps
0.221333
measured_current
: the measured current that the battery is supplying (in microamps)P.measured_current
194666
measured_voltage
: the measured voltage that the battery is supplying (in microvolts)P.measured_voltage
8249733
measured_volts
: the measured voltage that the battery is supplying (in volts)P.measured_volts
8.249733
technology
: the type battery technology being usedP.technology
u'Li-ion'
type
: the type of power source being usedP.type
The button class allows us to access buttons on the EV3 brick. These will not be explored in any depth here.
any()
: checks if any button is pressed.
backspace
: check if ‘backspace’ button is pressed.
buttons_pressed
: returns list of names of pressed buttons.
check_buttons(buttons=[])
: check if currently pressed buttons exactly match the given list.
down
: check if ‘down’ button is pressed.
enter
: check if ‘enter’ button is pressed.
left
: check if ‘left’ button is pressed.
static on_backspace(state)
: this handler is called by process() whenever state of ‘backspace’ button has changed since last process() call. state parameter is the new state of the button.
on_change(changed_buttons)
: this handler is called by process() whenever state of any button has changed since last process() call. changed_buttons is a list of tuples of changed button names and their states.
static on_down(state)
: this handler is called by process() whenever state of ‘down’ button has changed since last process() call. state parameter is the new state of the button.
static on_enter(state)
: this handler is called by process() whenever state of ‘enter’ button has changed since last process() call. state parameter is the new state of the button.
static on_left(state)
: this handler is called by process() whenever state of ‘left’ button has changed since last process() call. state parameter is the new state of the button.
static on_right(state)
: this handler is called by process() whenever state of ‘right’ button has changed since last process() call. state parameter is the new state of the button.
static on_up(state)
: this handler is called by process() whenever state of ‘up’ button has changed since last process() call. state parameter is the new state of the button.
process()
: check for currenly pressed buttons. If the new state differs from the old state, call the appropriate button event handlers.
right
: check if ‘right’ button is pressed.
up
: check if ‘up’ button is pressed.
m = LargeMotor(OUTPUT_A)
#Run some commands
m.run_forever(duty_cycle_sp=75)
while not Button().any():
#do whatever
pass
m.stop()
Some simple utilities are able to manage the sscreen., but they are limited and will not be covered here.
clear()
: clears the screendraw()
: returns a handle to PIL.ImageDraw.Draw class associated with the screen. For example, screen.draw.rectangle((10,10,60,20), fill='black')
shape
: dimensions of the screen.update()
: applies pending changes to the screen. Nothing will be drawn on the screen until this function is called.xres
: horizontal screen resolutionyres
: vertical screen resolution