Skip to content

Commit 0790d4f

Browse files
committed
bricks/ev3: Add Port View on apps tab.
1 parent 18abf52 commit 0790d4f

34 files changed

+2675
-1
lines changed

bricks/_common/micropython.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ pbio_error_t pbsys_main_program_validate(pbsys_main_program_t *program) {
308308
#if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_EV3_APPS
309309
case PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_MOTOR_BUTTON_CONTROL:
310310
case PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_MOTOR_IR_CONTROL:
311+
case PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_PORT_VIEW:
311312
return PBIO_SUCCESS;
312313
#endif
313314
default:
@@ -409,6 +410,10 @@ void pbsys_main_run_program(pbsys_main_program_t *program) {
409410
pb_package_pybricks_init(false);
410411
pyexec_frozen_module("_ev3_motor_ir_control.py", false);
411412
break;
413+
case PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_PORT_VIEW:
414+
pb_package_pybricks_init(false);
415+
pyexec_frozen_module("_ev3_port_view.py", false);
416+
break;
412417
#endif
413418

414419
default:

bricks/ev3/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
freeze_as_mpy("../ev3/modules", "_ev3_motor_button_control.py")
33
freeze_as_mpy("../ev3/modules", "_ev3_motor_dc.py")
44
freeze_as_mpy("../ev3/modules", "_ev3_motor_ir_control.py")
5+
freeze_as_mpy("../ev3/modules", "_ev3_port_view.py")
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
from pybricks.hubs import EV3Brick
2+
from pybricks.ev3devices import (
3+
Motor,
4+
ColorSensor as EV3ColorSensor,
5+
TouchSensor as EV3TouchSensor,
6+
GyroSensor,
7+
UltrasonicSensor,
8+
InfraredSensor,
9+
)
10+
from pybricks.nxtdevices import ColorSensor as NXTColorSensor, LightSensor, SoundSensor
11+
from pybricks.parameters import Button, Port, ImageFile
12+
from pybricks.tools import wait
13+
14+
15+
# Load and decompress images once.
16+
IMG_EV3_COLOR_AMBIENT = ImageFile._PORT_VIEW_EV3_COLOR_AMBIENT
17+
IMG_EV3_COLOR_COLOR = ImageFile._PORT_VIEW_EV3_COLOR_COLOR
18+
IMG_EV3_COLOR_REFLECTION = ImageFile._PORT_VIEW_EV3_COLOR_REFLECTION
19+
IMG_EV3_GYRO = ImageFile._PORT_VIEW_EV3_GYRO
20+
IMG_EV3_IR_BEACON = ImageFile._PORT_VIEW_EV3_IR_BEACON
21+
IMG_EV3_IR_BUTTON = ImageFile._PORT_VIEW_EV3_IR_BUTTON
22+
IMG_EV3_IR_PROXIMITY = ImageFile._PORT_VIEW_EV3_IR_PROXIMITY
23+
IMG_EV3_MOTOR_LARGE = ImageFile._PORT_VIEW_EV3_MOTOR_LARGE
24+
IMG_EV3_MOTOR_MEDIUM = ImageFile._PORT_VIEW_EV3_MOTOR_MEDIUM
25+
IMG_EV3_TOUCH = ImageFile._PORT_VIEW_EV3_TOUCH
26+
IMG_EV3_ULTRASONIC = ImageFile._PORT_VIEW_EV3_ULTRASONIC
27+
IMG_NXT_COLOR_AMBIENT = ImageFile._PORT_VIEW_NXT_COLOR_AMBIENT
28+
IMG_NXT_COLOR_COLOR = ImageFile._PORT_VIEW_NXT_COLOR_COLOR
29+
IMG_NXT_COLOR_REFLECTION = ImageFile._PORT_VIEW_NXT_COLOR_REFLECTION
30+
IMG_NXT_LIGHT_AMBIENT = ImageFile._PORT_VIEW_NXT_LIGHT_AMBIENT
31+
IMG_NXT_LIGHT_REFLECTION = ImageFile._PORT_VIEW_NXT_LIGHT_REFLECTION
32+
IMG_NXT_SOUND = ImageFile._PORT_VIEW_NXT_SOUND
33+
PORT_NONE_TOP = ImageFile._PORT_VIEW_P0_TOP
34+
PORT_NONE_BOTTOM = ImageFile._PORT_VIEW_P0_BOTTOM
35+
PORT_IMG = [
36+
ImageFile._PORT_VIEW_PA,
37+
ImageFile._PORT_VIEW_PB,
38+
ImageFile._PORT_VIEW_PC,
39+
ImageFile._PORT_VIEW_PD,
40+
ImageFile._PORT_VIEW_P1,
41+
ImageFile._PORT_VIEW_P2,
42+
ImageFile._PORT_VIEW_P3,
43+
ImageFile._PORT_VIEW_P4,
44+
]
45+
46+
# Initialize brick for display and buttons.
47+
ev3 = EV3Brick()
48+
49+
# We navigate through the ports with directional buttons.
50+
# Center button increments (and wraps) mode.
51+
PORTS = [Port.A, Port.B, Port.C, Port.D, Port.S1, Port.S2, Port.S3, Port.S4]
52+
modes = [0] * len(PORTS)
53+
selected = 0
54+
55+
56+
# Generator that repeatedly tests which device is plugged in, reading
57+
# values until it is unplugged, then trying again.
58+
def port_process(index):
59+
while True:
60+
# Reset mode on plugged in.
61+
modes[index] = 0
62+
63+
# Large and Medium Motor
64+
try:
65+
motor = Motor(PORTS[index], reset_angle=False)
66+
image = (
67+
IMG_EV3_MOTOR_LARGE
68+
if motor.control.pid()[0] > 12000
69+
else IMG_EV3_MOTOR_MEDIUM
70+
)
71+
while True:
72+
try:
73+
short = motor.angle()
74+
detail = str(short) + " deg"
75+
yield image, short, detail
76+
except OSError:
77+
motor.close()
78+
break
79+
except OSError:
80+
pass
81+
82+
# EV3 Touch Sensor
83+
try:
84+
sensor = EV3TouchSensor(PORTS[index])
85+
while True:
86+
try:
87+
pressed = sensor.pressed()
88+
detail = "pressed" if pressed else "released"
89+
yield IMG_EV3_TOUCH, pressed, detail
90+
except OSError:
91+
break
92+
except OSError:
93+
pass
94+
95+
# EV3 Ultrasonic Sensor
96+
try:
97+
sensor = UltrasonicSensor(PORTS[index])
98+
while True:
99+
try:
100+
distance = sensor.distance()
101+
detail = f"{distance:>4} mm"
102+
yield IMG_EV3_ULTRASONIC, distance, detail
103+
except OSError:
104+
break
105+
except OSError:
106+
pass
107+
108+
# EV3 Color Sensor
109+
try:
110+
sensor = EV3ColorSensor(PORTS[index])
111+
while True:
112+
try:
113+
mode = modes[index] % 3
114+
if mode == 2:
115+
detail = str(sensor.color())[6:].lower()
116+
short = detail[0:3]
117+
image = IMG_EV3_COLOR_COLOR
118+
else:
119+
image = (
120+
IMG_EV3_COLOR_AMBIENT if mode else IMG_EV3_COLOR_REFLECTION
121+
)
122+
short = sensor.ambient() if mode else sensor.reflection()
123+
detail = f"{short:>3} %"
124+
yield image, short, detail
125+
except OSError:
126+
break
127+
except OSError:
128+
pass
129+
130+
# EV3 Infrared Sensor
131+
try:
132+
sensor = InfraredSensor(PORTS[index])
133+
while True:
134+
try:
135+
mode = modes[index] % 3
136+
if mode == 0:
137+
short = sensor.distance()
138+
detail = f"{short:>3} %"
139+
image = IMG_EV3_IR_PROXIMITY
140+
elif mode == 1:
141+
image = IMG_EV3_IR_BUTTON
142+
pressed = [str(b)[7:] for b in sensor.buttons(1)]
143+
short = len(pressed)
144+
if len(pressed) == 1:
145+
detail = pressed[0]
146+
elif len(pressed) == 2:
147+
detail = pressed[0] + "\n" + pressed[1]
148+
else:
149+
detail = "Channel 1\nNo buttons"
150+
else:
151+
image = IMG_EV3_IR_BEACON
152+
distance, angle = sensor.beacon(1)
153+
if distance is None or angle is None:
154+
short = "Non"
155+
detail = "Channel 1\nNo beacon"
156+
else:
157+
short = distance or 0
158+
detail = f"{distance} %\n{angle} deg"
159+
yield image, short, detail
160+
except OSError:
161+
break
162+
except OSError:
163+
pass
164+
165+
# EV3 Gyro Sensor
166+
try:
167+
gyro = GyroSensor(PORTS[index])
168+
while True:
169+
try:
170+
short = gyro.angle()
171+
detail = f"{short:>4} deg\n{gyro.speed():>4} deg/s"
172+
yield IMG_EV3_GYRO, short, detail
173+
except OSError:
174+
break
175+
except OSError:
176+
pass
177+
178+
# NXT Color Sensor
179+
try:
180+
sensor = NXTColorSensor(PORTS[index])
181+
while True:
182+
try:
183+
mode = modes[index] % 3
184+
if mode == 2:
185+
detail = str(sensor.color())[6:].lower()
186+
short = detail[0:3]
187+
image = IMG_NXT_COLOR_COLOR
188+
else:
189+
image = (
190+
IMG_NXT_COLOR_AMBIENT if mode else IMG_NXT_COLOR_REFLECTION
191+
)
192+
value = sensor.ambient() if mode else sensor.reflection()
193+
detail = f"{value:>5.1f} %"
194+
yield image, round(value), detail
195+
except OSError:
196+
break
197+
except OSError:
198+
pass
199+
200+
# NXT Light Sensor
201+
try:
202+
sensor = LightSensor(PORTS[index])
203+
while True:
204+
try:
205+
ambient = modes[index] % 2
206+
image = (
207+
IMG_NXT_LIGHT_AMBIENT if ambient else IMG_NXT_LIGHT_REFLECTION
208+
)
209+
value = sensor.ambient() if ambient else sensor.reflection()
210+
detail = f"{value:>5.1f} %"
211+
yield image, round(value), detail
212+
except OSError:
213+
break
214+
except OSError:
215+
pass
216+
217+
# NXT Sound Sensor
218+
try:
219+
sensor = SoundSensor(PORTS[index])
220+
while True:
221+
try:
222+
value = sensor.intensity()
223+
detail = f"{value:>5.1f} %"
224+
yield IMG_NXT_SOUND, round(value), detail
225+
except OSError:
226+
break
227+
except OSError:
228+
pass
229+
230+
# Nothing on this port.
231+
yield None, "---", "No device"
232+
233+
234+
# Each port has its own process that we draw values from.
235+
processes = [port_process(i) for i in range(len(PORTS))]
236+
237+
238+
def draw_ui():
239+
# Draw main boxes.
240+
ev3.screen.clear()
241+
ev3.screen.draw_image(0, 7, PORT_IMG[selected] if selected <= 3 else PORT_NONE_TOP)
242+
ev3.screen.draw_image(0, 33, ImageFile._PORT_VIEW_EMPTY)
243+
ev3.screen.draw_image(
244+
0, 92, PORT_IMG[selected] if selected > 3 else PORT_NONE_BOTTOM
245+
)
246+
ev3.screen.draw_line(50, 42, 50, 82)
247+
248+
# Draw all port states.
249+
for i in range(len(PORTS)):
250+
# Get the state of this port.
251+
image, value, detail = next(processes[i])
252+
253+
if i == selected:
254+
# For selection, show main image and larger value.
255+
if image:
256+
ev3.screen.draw_image(4, 40, image)
257+
y = 45 if "\n" in detail or "%" in detail else 55
258+
ev3.screen.draw_text(60, y, text=detail)
259+
if "%" in detail and "\n" not in detail:
260+
ev3.screen.draw_box(60, 65, 160, 75)
261+
ev3.screen.draw_box(60, 65, 60 + value, 75, fill=True)
262+
else:
263+
# Otherwise show value only.
264+
x = 15 + (i % 4) * 40
265+
y = 11 if i <= 3 else 100
266+
267+
# Move integers slightly right and add optional short minus.
268+
if isinstance(value, int):
269+
x += 3
270+
if value < 0:
271+
ev3.screen.draw_line(x - 5, y + 7, x - 2, y + 7)
272+
value = -value
273+
if value > 999:
274+
value = 999
275+
elif isinstance(value, str):
276+
value = value[0:3]
277+
278+
if isinstance(value, bool):
279+
# Visualize bool as empty or filled box
280+
ev3.screen.draw_box(x + 7, y + 1, x + 17, y + 11, fill=value)
281+
else:
282+
# Else draw text value in the small box.
283+
ev3.screen.draw_text(x, y, text=str(value))
284+
285+
286+
# Monitor the buttons to change ports, refreshing the
287+
# screen while we wait.
288+
while True:
289+
while not (pressed := ev3.buttons.pressed()):
290+
draw_ui()
291+
wait(100)
292+
293+
if Button.LEFT in pressed:
294+
selected = (selected - 1) % len(PORTS)
295+
elif Button.RIGHT in pressed:
296+
selected = (selected + 1) % len(PORTS)
297+
elif Button.UP in pressed and selected > 3:
298+
selected -= 4
299+
elif Button.DOWN in pressed and selected <= 3:
300+
selected += 4
301+
elif Button.CENTER in pressed:
302+
modes[selected] += 1
303+
304+
while any(ev3.buttons.pressed()):
305+
draw_ui()
306+
wait(100)

bricks/virtualhub/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
freeze_as_mpy("../ev3/modules", "_ev3_motor_button_control.py")
33
freeze_as_mpy("../ev3/modules", "_ev3_motor_dc.py")
44
freeze_as_mpy("../ev3/modules", "_ev3_motor_ir_control.py")
5+
freeze_as_mpy("../ev3/modules", "_ev3_port_view.py")

lib/pbio/include/pbio/protocol.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ typedef enum {
7777
* Program to control EV3 motors with infrared remote.
7878
*/
7979
PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_MOTOR_IR_CONTROL = 134,
80+
/**
81+
* Application to view sensor values.
82+
*/
83+
PBIO_PYBRICKS_USER_PROGRAM_ID_EV3_PORT_VIEW = 135,
8084
} pbio_pybricks_user_program_id_t;
8185

8286
/**
1.45 KB
Binary file not shown.
Binary file not shown.
5.27 KB
Binary file not shown.
Binary file not shown.
5.27 KB
Binary file not shown.

0 commit comments

Comments
 (0)