Skip to content

Commit 5fd56db

Browse files
gideongrinbergWasabiFan
authored andcommitted
Moves the console_menu.py demo from python repo (#46)
Moves the console menu demo from python-lang/utils to the demos. This demonstrates LEDs, console and buttons.
1 parent 65b79d1 commit 5fd56db

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

robots/misc/console_menu.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#!/usr/bin/env micropython
2+
from time import sleep
3+
from sys import stderr
4+
from os import listdir
5+
from ev3dev2.button import Button
6+
from ev3dev2.console import Console
7+
from ev3dev2.led import Leds
8+
from ev3dev2.sensor import list_sensors, INPUT_1, INPUT_2, INPUT_3, INPUT_4
9+
10+
11+
"""
12+
Used to create a console menu for switching between programs quickly
13+
without having to return to Brickman to find and launch a program.
14+
Demonstrates the EV3DEV2 Console(), Led(), and Button() classes.
15+
"""
16+
17+
18+
def get_positions(console):
19+
"""
20+
Compute a dictionary keyed by button names with horizontal alignment,
21+
and column/row location to show each choice on the EV3 LCD console.
22+
Parameter:
23+
- `console` (Console): an instance of the EV3 Console() class
24+
returns a dictionary keyed by button names with column/row location
25+
"""
26+
27+
midrow = 1 + console.rows // 2
28+
midcol = 1 + console.columns // 2
29+
# horiz_alignment, col, row
30+
return {
31+
"up": ("C", midcol, 1),
32+
"right": ("R", console.columns, midrow),
33+
"down": ("C", midcol, console.rows),
34+
"left": ("L", 1, midrow),
35+
"enter": ("C", midcol, midrow)
36+
}
37+
38+
39+
def wait_for_button_press(button):
40+
"""
41+
Wait for a button to be pressed and released.
42+
Parameter:
43+
- `button` (Button): an instance of the EV3 Button() class
44+
return the Button name that was pressed.
45+
"""
46+
pressed = None
47+
while True:
48+
allpressed = button.buttons_pressed
49+
if bool(allpressed):
50+
pressed = allpressed[0] # just get the first one
51+
while not button.wait_for_released(pressed):
52+
pass
53+
break
54+
return pressed
55+
56+
57+
def menu(choices, before_run_function=None, after_run_function=None):
58+
"""
59+
Console Menu that accepts choices and corresponding functions to call.
60+
The user must press the same button twice: once to see their choice highlited,
61+
a second time to confirm and run the function. The EV3 LEDs show each state change:
62+
Green = Ready for button, Amber = Ready for second button, Red = Running
63+
Parameters:
64+
- `choices` a dictionary of tuples "button-name": ("mission-name", function-to-call)
65+
Example:
66+
choices = {
67+
# "button-name": ("mission-name", function-to-call)
68+
# or "button-name": ("mission-name", lambda: call(x, y, z))
69+
"enter": ("CAL", lambda: auto_calibrate(robot, 1.0)),
70+
"up": ("MI2", fmission2),
71+
"right": ("MI3", fmission3),
72+
"down": ("MI4", fmission4),
73+
"left": ("MI5", fmission5)
74+
}
75+
where fmission2, fmission3 are functions;
76+
note don't call them with parentheses, unless preceded by lambda: to defer the call
77+
- `before_run_function` when not None, call this function before each mission run, passed with mission-name
78+
- `after_run_function` when not None, call this function after each mission run, passed with mission-name
79+
"""
80+
81+
console = Console()
82+
leds = Leds()
83+
button = Button()
84+
85+
leds.all_off()
86+
leds.set_color("LEFT", "GREEN")
87+
leds.set_color("RIGHT", "GREEN")
88+
menu_positions = get_positions(console)
89+
90+
last = None # the last choice--initialize to None
91+
92+
while True:
93+
# display the menu of choices, but show the last choice in inverse
94+
console.reset_console()
95+
for btn, (name, _) in choices.items():
96+
align, col, row = menu_positions[btn]
97+
console.text_at(name, col, row, inverse=(btn == last), alignment=align)
98+
99+
pressed = wait_for_button_press(button)
100+
101+
# get the choice for the button pressed
102+
if pressed in choices:
103+
if last == pressed: # was same button pressed?
104+
console.reset_console()
105+
leds.set_color("LEFT", "RED")
106+
leds.set_color("RIGHT", "RED")
107+
108+
# call the user's subroutine to run the mission, but catch any errors
109+
try:
110+
name, mission_function = choices[pressed]
111+
if before_run_function is not None:
112+
before_run_function(name)
113+
mission_function()
114+
except Exception as ex:
115+
print("**** Exception when running")
116+
print(ex)
117+
finally:
118+
if after_run_function is not None:
119+
after_run_function(name)
120+
last = None
121+
leds.set_color("LEFT", "GREEN")
122+
leds.set_color("RIGHT", "GREEN")
123+
else: # different button pressed
124+
last = pressed
125+
leds.set_color("LEFT", "AMBER")
126+
leds.set_color("RIGHT", "AMBER")
127+
128+
129+
if __name__ == "__main__":
130+
131+
# This is the main program to demonstrate the console menu logic above.
132+
#
133+
# Define functions that represent different missions
134+
# Note: these can be imported from different modules (files)
135+
# and use lambda notation to defer the function call
136+
# i.e. lambda : call(a, b, c)
137+
138+
def calibrate():
139+
""" Placeholder for call to your calibration logic to set the black and white values for your color sensors """
140+
print("calibrating...")
141+
sleep(1)
142+
143+
def show_sensors(iterations):
144+
""" Show the EV3 sensors, current mode and value """
145+
sensors = list(list_sensors(address=[INPUT_1, INPUT_2, INPUT_3])) # , INPUT_4
146+
for _ in range(iterations):
147+
for sensor in sensors:
148+
print("{} {}: {}".format(sensor.address, sensor.mode, sensor.value()))
149+
sleep(.5)
150+
sleep(10)
151+
152+
def mission1():
153+
print("mission 1...")
154+
sleep(1)
155+
156+
def mission2():
157+
print("mission 2...")
158+
sleep(1)
159+
160+
def mission3():
161+
print("mission 3...")
162+
sleep(1)
163+
# for testing when a function generates an error
164+
raise Exception('Raised error')
165+
166+
# Define the functions to be called before and after each run.
167+
# Functions will be called with the mission_name as the argument.
168+
# Useful for resetting motor positions between runs, etc.
169+
170+
def before(mission_name):
171+
print("before " + mission_name)
172+
173+
def after(mission_name):
174+
print("after " + mission_name)
175+
sleep(1)
176+
177+
# Define the buttons, mission names, functions for the console menu.
178+
# Key is the button assignment: one of "enter", "up", "right", "down", "left"
179+
# Note the "backspace" button interrupts the program and returns to Brickman
180+
# Example:
181+
# CHOICES = {
182+
# # "button-name": ("mission-name", function-to-call)
183+
# # or "button-name": ("mission-name", lambda: call(x, y, z))
184+
# "up": ("MI1", mission1),
185+
# "right": ("MI2", mission2),
186+
# "left": ("MI3", mission3),
187+
# "down": ("SHOW", lambda: show_sensors(5)),
188+
# "enter": ("CAL", calibrate)
189+
# }
190+
# menu(CHOICES, before_run_function=before, after_run_function=after)
191+
192+
CHOICES = {
193+
"up": ("MI1", mission1),
194+
"right": ("MI2", mission2),
195+
"left": ("MI3", mission3),
196+
"down": ("SHOW", lambda: show_sensors(5)),
197+
"enter": ("CAL", calibrate)
198+
}
199+
200+
menu(CHOICES, before_run_function=before, after_run_function=after)

0 commit comments

Comments
 (0)