https://en.wikipedia.org/wiki/Lego_Mindstorms

0-2 minutes:

  • Introduction
  • A bit about me
  • What is Lego Mindstorms
  • Brief history

2-5 minutes:

  • Cool stuff people have done with Mindstorms

5-8 minutes:

8-12 minutes:

  • The motors and how to program them

    • Medium motor
    • Servo motor

12-17 minutes:

  • The sensors and how to program them
    • Infrared
    • Touch
    • Gyro
    • Color
    • Ultrasonic
    • Power supply

17-20 minutes:

  • Output options and how to program them
    • LED control
    • Sound and speech control
    • Screen control

20-22 minutes:

  • Popular third-party sensors and parts

    • Dexter Industries
    • Mindsensors
    • HiTechnic
    • MATRIX robotics
    • Vernier
  • Extending the Python libraries to support custom sensors (might cut this one out if running short on time)

22-23 minutes:

  • Other options to explore

    • BrickPi

23-26 minutes:

  • Demo

    • Booting ev3dev
    • Running Jupyter notebook on the brick
    • Connecting to the Jupyter notebook and running "Sound.speak('Hello PyCon Australia')."

26-30 minutes:

  • Thanks and Q&A

Putting it all together

Download an mp3 from Spotify and make the robot dance. Bonus points for randomised dance moves. Bonus points for dance moves based on song analysis.


In [ ]:
#https://github.com/plamere/spotipy

import requests
import spotipy

sp = spotipy.Spotify()

results = sp.search(q='i like to move it move it', limit=20)
for i, t in enumerate(results['tracks']['items']):
    print ' ', i, t['name']

with file(r'c:\temp\moveit.mp3','wb') as out:
    out.write(requests.get(results['tracks']['items'][0]['preview_url']).content)

import os
os.startfile(r'c:\temp\moveit.mp3')

.EV3 files

  • Lego Mindstorms LabView projects have a .ev3 extension.

  • .ev3 files can be unzipped with 7-zip to a folder.

  • Within the zip are:

    • .x3a files

    • .laz is also a .zip file which contains assets such as images

    • .rgf

    • .rsf

    • .ev3p is an XML file which captures the progam's logic

    • .lvprojx is an XML file


In [ ]:
# TODO

* toddler photo i had a dream
* word cloud for parts
* picasa collage for parts
* lego intro
    * mindstorms robots
    * what: tecnic, brain, software
    * where: buy online 499 ebay less 
    * why: community
    * history: nxt
    * education edition vs home edition

In [6]:
!dir


 Volume in drive C is Windows
 Volume Serial Number is 7A71-E759

 Directory of C:\work\pyconau2017\pyconau2017

24/07/2017  12:19 PM    <DIR>          .
24/07/2017  12:19 PM    <DIR>          ..
12/05/2017  10:20 AM               130 .gitignore
08/05/2017  03:20 AM    <DIR>          .ipynb_checkpoints
01/07/2017  01:57 AM                92 beep.py
08/05/2017  03:07 AM             1,091 LICENSE
21/07/2017  06:54 PM             8,006 nupogodi.py
24/07/2017  12:10 PM            15,613 pivottablejs.html
21/07/2017  06:35 PM             2,752 playspace.py
08/05/2017  03:07 AM               126 README.md
24/07/2017  12:19 PM            10,847 Research.ipynb
01/07/2017  02:06 AM                15 run.sh
20/07/2017  03:15 PM             4,566 starwars.py
              10 File(s)         43,238 bytes
               3 Dir(s)  31,170,265,088 bytes free

In [ ]:
%config SqlMagic.autopandas = True

In [ ]:
%%sql sqlite://
CREATE TABLE presidents (first, last, yob);
INSERT INTO presidents VALUES ('George', 'Washington', 1732);

In [1]:
import pandas as pd
import numpy as np

from pivottablejs import pivot_ui

df = pd.read_csv(r'C:\Users\Stoyan\Downloads\ev3set-parts-inventory.csv')

In [2]:
df.head()


Out[2]:
SetNumber PartID Quantity Colour Category DesignID PartName ImageURL SetCount
0 31313-1 4184286 4 Black System 44309 Tyre Normal Wide Ø43,2 X 22 http://cache.lego.com/media/bricks/5/2/4184286... 55
1 31313-1 4246901 2 Black System 50951 Tyre Low Narrow Ø14.58 X 6.24 http://cache.lego.com/media/bricks/5/2/4246901... 130
2 31313-1 4299389 4 Black System 56145 Rim Wide W.Cross 30/20 http://cache.lego.com/media/bricks/5/2/4299389... 59
3 31313-1 4502834 2 Black System 53992 Caterpillar Track http://cache.lego.com/media/bricks/5/2/4502834... 11
4 31313-1 370626 9 Black Technic 3706 Cross Axle 6M http://cache.lego.com/media/bricks/5/2/370626.jpg 761

In [48]:
import requests
import os

root = r'C:\work\pyconau2017\pyconau2017\images\parts'

for row in df.itertuples():
    #print(row, type(row), dir(row))
    name = row.PartName.replace('Ø','').replace('°','').replace('/','-').replace(',','').replace('.','').replace('  ',' ').replace(' ','_')
    try:
        with open(os.path.join(root, '{0}.jpg'.format(name)), 'wb') as out:
            out.write(requests.get(row.ImageURL).content)
    except Exception as e:
        print(e.args[0], name)

In [50]:
for row in df.itertuples():
    #print(row, type(row), dir(row))
    name = row.PartName.replace('Ø','').replace('°','').replace('/','-').replace(',','').replace('.','').replace('  ',' ').replace(' ','_')
    print(name, row.ImageURL)


Tyre_Normal_Wide_432_X_22 http://cache.lego.com/media/bricks/5/2/4184286.jpg
Tyre_Low_Narrow_1458_X_624 http://cache.lego.com/media/bricks/5/2/4246901.jpg
Rim_Wide_WCross_30-20 http://cache.lego.com/media/bricks/5/2/4299389.jpg
Caterpillar_Track http://cache.lego.com/media/bricks/5/2/4502834.jpg
Cross_Axle_6M http://cache.lego.com/media/bricks/5/2/370626.jpg
Double_Angular_Beam_3X7_45 http://cache.lego.com/media/bricks/5/2/4111998.jpg
Technic_Ang_Beam_4X2_90_Deg http://cache.lego.com/media/bricks/5/2/4120017.jpg
Connector_Peg_W_Friction http://cache.lego.com/media/bricks/5/2/4121715.jpg
Technic_Angular_Beam_4X4 http://cache.lego.com/media/bricks/5/2/4128593.jpg
Technic_Angular_Beam_3X7 http://cache.lego.com/media/bricks/5/2/4140327.jpg
Lt_Steering_Gear http://cache.lego.com/media/bricks/5/2/4141300.jpg
Technic_5M_Beam http://cache.lego.com/media/bricks/5/2/4142135.jpg
Technic_3M_Beam http://cache.lego.com/media/bricks/5/2/4142822.jpg
Technic_Ang_Beam_3X5_90_Deg http://cache.lego.com/media/bricks/5/2/4142823.jpg
Comb_Wheel http://cache.lego.com/media/bricks/5/2/4143187.jpg
Double_Conical_Wheel_Z12_1M http://cache.lego.com/media/bricks/5/2/4177431.jpg
Ball_With_Friction_Snap http://cache.lego.com/media/bricks/5/2/4184169.jpg
Technic_Angular_Wheel http://cache.lego.com/media/bricks/5/2/4248204.jpg
Double_Conical_Wheel_Z36 http://cache.lego.com/media/bricks/5/2/4255563.jpg
Technic_7M_Beam http://cache.lego.com/media/bricks/5/2/4495935.jpg
Technic_13M_Beam http://cache.lego.com/media/bricks/5/2/4522933.jpg
Technic_15M_Beam http://cache.lego.com/media/bricks/5/2/4542573.jpg
T-Beam_3X3_W-Hole_48 http://cache.lego.com/media/bricks/5/2/4552347.jpg
Beam_3M_485_W_Fork http://cache.lego.com/media/bricks/5/2/4558692.jpg
Track_Rod_6M http://cache.lego.com/media/bricks/5/2/4629921.jpg
Technic_9M_Beam http://cache.lego.com/media/bricks/5/2/4645732.jpg
Beam_1X2_W-Cross_And_Hole http://cache.lego.com/media/bricks/5/2/6006140.jpg
Tyre_For_Wedge-Belt_Wheel http://cache.lego.com/media/bricks/5/2/6028041.jpg
Double_Conical_Wheel_Z20_1M http://cache.lego.com/media/bricks/5/2/6093977.jpg
Ev3_Cable_250_Mm http://cache.lego.com/media/bricks/4/2/6178438.jpg
Ev3_Cable_350_Mm http://cache.lego.com/media/bricks/4/2/6178439.jpg
Ev3_Cable_500_Mm http://cache.lego.com/media/bricks/4/2/6178448.jpg
Shooter http://cache.lego.com/media/bricks/5/2/6024106.jpg
Magasine_For_Balls_165 http://cache.lego.com/media/bricks/5/2/6024109.jpg
3M_Connector_Peg http://cache.lego.com/media/bricks/5/2/4514554.jpg
Conical_Wheel_Z12 http://cache.lego.com/media/bricks/5/2/4565452.jpg
Krydsaksel_M-Stop_4M http://cache.lego.com/media/bricks/5/2/4666999.jpg
Bevel_Gear_Z20 http://cache.lego.com/media/bricks/5/2/6031962.jpg
ConnBush_WFric-Crossale http://cache.lego.com/media/bricks/5/2/4206482.jpg
Connector_Peg_W_Friction_3M http://cache.lego.com/media/bricks/5/2/4514553.jpg
V-Belt_24_-_Clear_Red http://cache.lego.com/media/bricks/5/2/4100396.jpg
Technic_Cross_Block_2X1 http://cache.lego.com/media/bricks/5/2/4128594.jpg
Double_Cross_Block http://cache.lego.com/media/bricks/5/2/4128598.jpg
2M_Fric_Snap_W-Cross_Hole http://cache.lego.com/media/bricks/5/2/4140806.jpg
2M_Cross_Axle_W_Groove http://cache.lego.com/media/bricks/5/2/4142865.jpg
Technic_Cross_Block-Fork_2X2 http://cache.lego.com/media/bricks/5/2/4173975.jpg
Cross_Blok_3M http://cache.lego.com/media/bricks/5/2/4175442.jpg
Cross_Block_90 http://cache.lego.com/media/bricks/5/2/4188298.jpg
Angle_Element_90_Degrees_[6] http://cache.lego.com/media/bricks/5/2/4189131.jpg
Angle_Element_135_Deg_[4] http://cache.lego.com/media/bricks/5/2/4189936.jpg
Bush_For_Cross_Axle http://cache.lego.com/media/bricks/5/2/4227155.jpg
Angle_Element_180_Degrees_[2] http://cache.lego.com/media/bricks/5/2/4234429.jpg
Angle_Element_0_Degrees_[1] http://cache.lego.com/media/bricks/5/2/4254606.jpg
Cross_Axle_Extension_2M http://cache.lego.com/media/bricks/5/2/4513174.jpg
Technic_11M_Beam http://cache.lego.com/media/bricks/5/2/4562805.jpg
Bion_Eye http://cache.lego.com/media/bricks/5/2/4185661.jpg
Ball_165 http://cache.lego.com/media/bricks/5/2/4545430.jpg
1-2_Bush http://cache.lego.com/media/bricks/5/2/4239601.jpg
Cross_Axle_8M_With_End_Stop http://cache.lego.com/media/bricks/5/2/4499858.jpg
Cross_Axle_55_With_Stop_1M http://cache.lego.com/media/bricks/5/2/4508553.jpg
Cross_Axle_4M_With_End_Stop http://cache.lego.com/media/bricks/5/2/6083620.jpg
Gear_Wheel_Z24 http://cache.lego.com/media/bricks/5/2/6133119.jpg
Ball_W_Cross_Axle http://cache.lego.com/media/bricks/5/2/4211375.jpg
Catch_W_Cross_Hole http://cache.lego.com/media/bricks/5/2/4211553.jpg
Technic_Lever_3M http://cache.lego.com/media/bricks/5/2/4211566.jpg
Cross_Axle_5M http://cache.lego.com/media/bricks/5/2/4211639.jpg
Hub_112_X_784 http://cache.lego.com/media/bricks/5/2/4211758.jpg
Cross_Axle_7M http://cache.lego.com/media/bricks/5/2/4211805.jpg
Connector_Peg http://cache.lego.com/media/bricks/5/2/4211807.jpg
Cross_Axle_3M http://cache.lego.com/media/bricks/5/2/4211815.jpg
Module_Bush http://cache.lego.com/media/bricks/5/2/4211888.jpg
Beam_3_M_W-4_Snaps http://cache.lego.com/media/bricks/5/2/4225033.jpg
Wedge-Belt_Wheel_24 http://cache.lego.com/media/bricks/5/2/4494222.jpg
3-Branch_Cross_Axle_W-Cross_H http://cache.lego.com/media/bricks/5/2/4502595.jpg
Cross_Axle_9M http://cache.lego.com/media/bricks/5/2/4535768.jpg
Cross_Block_3X2 http://cache.lego.com/media/bricks/5/2/4538007.jpg
Beam_Frame_5X7_485 http://cache.lego.com/media/bricks/5/2/4539880.jpg
Beam_R_Frame_5X11_485 http://cache.lego.com/media/bricks/5/2/4540797.jpg
Double_Bush_3M_49 http://cache.lego.com/media/bricks/5/2/4560175.jpg
Cross_Block-Form_2X2X2 http://cache.lego.com/media/bricks/5/2/4630114.jpg
Technic_Steering-Gear_3M http://cache.lego.com/media/bricks/5/2/6013936.jpg
Ms_Ev3_Sensor_Colour http://cache.lego.com/media/bricks/5/2/6128869.jpg
Ms-Ev3_Ir_Sensor http://cache.lego.com/media/bricks/5/2/6132629.jpg
Ms_Ev3_Touch_Sensor http://cache.lego.com/media/bricks/5/2/6138404.jpg
Ms-Ev3_Medium_Motor http://cache.lego.com/media/bricks/5/2/6148292.jpg
Tube_W-_Double_485_Hole http://cache.lego.com/media/bricks/5/2/6173127.jpg
Worm_Gear_2_Module_For_Gear_Wheel http://cache.lego.com/media/bricks/5/2/6185471.jpg
Sword http://cache.lego.com/media/bricks/5/2/4657296.jpg
Crossaxle_3M_With_Knob http://cache.lego.com/media/bricks/5/2/6031821.jpg
Left_Panel_3X7 http://cache.lego.com/media/bricks/5/2/4547581.jpg
Right_Panel_3X7 http://cache.lego.com/media/bricks/5/2/4547582.jpg
Right_Panel_3X11 http://cache.lego.com/media/bricks/5/2/4558797.jpg
Left_Panel_3X11 http://cache.lego.com/media/bricks/5/2/4558802.jpg
Right_Screen_485_4X7X4 http://cache.lego.com/media/bricks/5/2/6015596.jpg
Left_Screen_485_4X7X4 http://cache.lego.com/media/bricks/5/2/6015597.jpg
Ms-Ev3_Ir_Beacon http://cache.lego.com/media/bricks/5/2/6127283.jpg
Ms-Ev3_Large_Motor http://cache.lego.com/media/bricks/5/2/6148278.jpg
Ms-Ev3_P-Brick http://cache.lego.com/media/bricks/5/2/6187080.jpg
Bion_Eye http://cache.lego.com/media/bricks/5/2/4173941.jpg
Blade_W_Technic_Hole_1 http://cache.lego.com/media/bricks/5/2/4656205.jpg

In [49]:
names = []
for row in df.itertuples():
    #print(row, type(row), dir(row))
    name = row.PartName.replace('Ø','').replace('°','').replace('/','-').replace(',','').replace('.','').replace('  ',' ').replace(' ','_')
    for i in range(0, row.Quantity):
        names.append(name)
with open(os.path.join(root, 'parts.txt'), 'w') as out:
    out.write(' '.join(names))

In [3]:
pivot_ui(df)


Out[3]:

Programming Lego Mindstorms with Python

Stoyan Shopov

#Programming Lego Mindstorms with Python#

In [58]:
import glob
from PIL import Image

#https://stackoverflow.com/questions/35438802/making-a-collage-in-pil
'''im= Image.open('Tulips.jpg')

out=im.convert("RGB", (
    0.412453, 0.357580, 0.180423, 0,
    0.212671, 0.715160, 0.072169, 0,
    0.019334, 0.119193, 0.950227, 0 ))
out.save("Image2.jpg")

out2=im.convert("RGB", (
    0.9756324, 0.154789, 0.180423, 0,
    0.212671, 0.715160, 0.254783, 0,
    0.123456, 0.119193, 0.950227, 0 ))
out2.save("Image3.jpg")

out3= im.convert("1")
out3.save("Image4.jpg")

out4=im.convert("RGB", (
    0.986542, 0.154789, 0.756231, 0,
    0.212671, 0.715160, 0.254783, 0,
    0.123456, 0.119193, 0.112348, 0 ))
out4.save("Image5.jpg")

out5=Image.blend(im, out4, 0.5)
out5.save("Image6.jpg")'''

listofimages=['Tulips.jpg', 'Image2.jpg', 'Image3.jpg', 'Image4.jpg', 'Image5.jpg', 'Image6.jpg']
listofimages = glob.glob(r'C:\work\pyconau2017\pyconau2017\images\parts\*.jpg')
print(len(listofimages))
def create_collage(width, height, listofimages):
    cols = 10
    rows = 10
    thumbnail_width = width//cols
    thumbnail_height = height//rows
    size = thumbnail_width, thumbnail_height
    new_im = Image.new('RGB', (width, height))
    ims = []
    for p in listofimages:
        im = Image.open(p)
        im.thumbnail(size)
        ims.append(im)
    i = 0
    x = 0
    y = 0
    for col in range(cols):
        for row in range(rows):
            print(i, x, y)
            if i<len(ims):
                new_im.paste(ims[i], (x, y))
                i += 1
                y += thumbnail_height
        x += thumbnail_width
        y = 0

    new_im.save(r"C:\work\pyconau2017\pyconau2017\images\Collage.jpg")

create_collage(500, 500, listofimages)


100
0 0 0
1 0 50
2 0 100
3 0 150
4 0 200
5 0 250
6 0 300
7 0 350
8 0 400
9 0 450
10 50 0
11 50 50
12 50 100
13 50 150
14 50 200
15 50 250
16 50 300
17 50 350
18 50 400
19 50 450
20 100 0
21 100 50
22 100 100
23 100 150
24 100 200
25 100 250
26 100 300
27 100 350
28 100 400
29 100 450
30 150 0
31 150 50
32 150 100
33 150 150
34 150 200
35 150 250
36 150 300
37 150 350
38 150 400
39 150 450
40 200 0
41 200 50
42 200 100
43 200 150
44 200 200
45 200 250
46 200 300
47 200 350
48 200 400
49 200 450
50 250 0
51 250 50
52 250 100
53 250 150
54 250 200
55 250 250
56 250 300
57 250 350
58 250 400
59 250 450
60 300 0
61 300 50
62 300 100
63 300 150
64 300 200
65 300 250
66 300 300
67 300 350
68 300 400
69 300 450
70 350 0
71 350 50
72 350 100
73 350 150
74 350 200
75 350 250
76 350 300
77 350 350
78 350 400
79 350 450
80 400 0
81 400 50
82 400 100
83 400 150
84 400 200
85 400 250
86 400 300
87 400 350
88 400 400
89 400 450
90 450 0
91 450 50
92 450 100
93 450 150
94 450 200
95 450 250
96 450 300
97 450 350
98 450 400
99 450 450

In [ ]:
import os
ps=[]
for p in os.listdir(r'C:\work\pyconau2017\pyconau2017\images'):
    if p.startswith('Product'):
        ps.append('![{0}](./images/{0})'.format(p))
        
print(' | '.join(ps))

In [ ]:
#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep
from PIL import Image

lcd = Screen()

logo = Image.open('pics/Bomb.bmp')
lcd.image.paste(logo, (0,0))
lcd.update()
sleep(5) 

f = ImageFont.truetype('/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', 75)
lcd.draw.text((3,0), 'Hello', font=f)
lcd.draw.text((2,55), 'world', font=f)
lcd.update()

sleep(7) 

lcd.draw.text((10,10), 'Hello World!', font=fonts.load('luBS14'))
lcd.update()
sleep(8)

lcd.draw.rectangle((0,0,177,40), fill='black')
lcd.draw.text((48,13),'Hello, world.', fill='white')
my_string='THIS TEXT IS BLACK'
print(lcd.draw.textsize(my_string))
lcd.draw.text((36,80),my_string)
lcd.update()
sleep(6)

lcd.draw.rectangle((0,0,177,40), fill='black')
lcd.draw.text((48,13),'Hello, world.', fill='white')
lcd.draw.text((36,80),'THIS TEXT IS BLACK')
lcd.update()
sleep(6)

In [ ]:
#!/usr/bin/env python3
from time import sleep
from ev3dev.ev3 import *

lcd = Screen()

smile = True

while True:
    lcd.clear()

    # lcd.draw returns a PIL.ImageDraw handle
    lcd.draw.ellipse(( 20, 20,  60, 60))
    lcd.draw.ellipse((118, 20, 158, 60))

    if smile:
        lcd.draw.arc((20, 80, 158, 100), 0, 180)
    else:
        lcd.draw.arc((20, 80, 158, 100), 180, 360)

    smile = not smile  # toggle between True and False

    # Update lcd display
    lcd.update() # Applies pending changes to the screen.
    # Nothing will be drawn on the lcd screen
    # until this function is called.

    sleep(1)

In [ ]:
screen = ev3.Screen()
def draw_face():
    w,h = screen.shape
    y = h // 2

    eye_xrad = 20
    eye_yrad = 30

    pup_xrad = 10
    pup_yrad = 10

    def draw_eye(x):
        screen.draw.ellipse((x-eye_xrad, y-eye_yrad, x+eye_xrad, y+eye_yrad))
        screen.draw.ellipse((x-pup_xrad, y-pup_yrad, x+pup_xrad, y+pup_yrad), fill='black')

    draw_eye(w//3)
    draw_eye(2*w//3)

    screen.update()
draw_face()

In [ ]:
#!/usr/bin/env python3

from ev3dev.ev3 import *
from time import sleep

btn = Button()

# Do something when state of any button changes:
  
def left(state):
    if state:
        print('Left button pressed')
    else:
        print('Left button released')
    
def right(state):  # neater use of 'if' follows:
    print('Right button pressed' if state else 'Right button released')
    
def up(state):
    print('Up button pressed' if state else 'Up button released')
    
def down(state):
    print('Down button pressed' if state else 'Down button released')
    
def enter(state):
    print('Enter button pressed' if state else 'Enter button released')
    
def backspace(state):
    print('Backspace button pressed' if state else 'Backspace button released')
    
btn.on_left = left
btn.on_right = right
btn.on_up = up
btn.on_down = down
btn.on_enter = enter
btn.on_backspace = backspace

while True:  # This loop checks buttons state continuously, 
             # calls appropriate event handlers
    btn.process() # Check for currently pressed buttons. 
    # If the new state differs from the old state, 
    # call the appropriate button event handlers.
    sleep(0.01)  # buttons state will be checked every 0.01 second

# If running this script via SSH, press Ctrl+C to quit
# if running this script from Brickman, long-press backspace button to quit


def change(changed_buttons):   # changed_buttons is a list of 
# tuples of changed button names and their states.
    print('These buttons changed state: ' + str(changed_buttons))

btn.on_change = change

while True:  # This loop checks buttons state
# continuously and calls appropriate event handlers
    btn.process()
    sleep(0.01)
    
    
while True:
    if btn.any():    # Checks if any button is pressed.
        exit()
    else:
        sleep(0.01)  # Check for button press every 0.01 second
        

while not btn.backspace:
    print(btn.left)
    sleep(1)
    
#end the script by pressing ONLY the left and right buttons simultaneously    
while True:
    print(btn.buttons_pressed)
    if btn.check_buttons(buttons=['left','right']):
        exit()
    sleep(1)

In [ ]:
# This demo shows how to remote control an Explor3r robot
#
# Red buttons control left motor, blue buttons control right motor.
# Leds are used to indicate movement direction.

from time import sleep
from ev3dev.ev3 import *

# Connect two large motors on output ports B and C
lmotor = LargeMotor('outB')
rmotor = LargeMotor('outC')

# Check that the motors are actually connected
assert lmotor.connected
assert rmotor.connected

# Connect remote control
rc = RemoteControl(); assert rc.connected

# Initialize button handler
# button = Button()   # not working so disabled

# Turn leds off
Leds.all_off()

def roll(motor, led_group, direction):
    """
    Generate remote control event handler. It rolls given motor into given
    direction (1 for forward, -1 for backward). When motor rolls forward, the
    given led group flashes green, when backward -- red. When motor stops, the
    leds are turned off.

    The on_press function has signature required by RemoteControl class.
    It takes boolean state parameter; True when button is pressed, False
    otherwise.
    """
    def on_press(state):
        if state:
            # Roll when button is pressed
            motor.run_forever(speed_sp=90*direction)
            Leds.set_color(led_group, direction > 0 and Leds.GREEN or Leds.RED)
        else:
            # Stop otherwise
            motor.stop(stop_action='brake')
            Leds.set(led_group, brightness_pct=0)

    return on_press

# Assign event handler to each of the remote buttons
rc.on_red_up    = roll(lmotor, Leds.LEFT,   1)
rc.on_red_down  = roll(lmotor, Leds.LEFT,  -1)
rc.on_blue_up   = roll(rmotor, Leds.RIGHT,  1)
rc.on_blue_down = roll(rmotor, Leds.RIGHT, -1)

# Enter event processing loop
#while not button.any():   #not working so commented out
while True:   #replaces previous line so use Ctrl-C to exit
    rc.process()
    sleep(0.01)
    
# Press Ctrl-C to exit