Shutterbox¶
This sample project includes two 1-DOF rotors which each have five passive
shutters to modulate an internal light source into light and shadow.
This model is demonstrated in the shutterbox.wbt
world available within the
Webots.zip archive.
Keyboard Control Code¶
The controller implements keyboard input to trigger motor torques. The two machines respond to different keystrokes for simultaneous control.
1# shutterbox.py
2#
3# Sample Webots controller file for driving the
4# shutterbox device.
5#
6# No copyright, 2022, Garth Zeglin. This file is
7# explicitly placed in the public domain.
8
9print("loading shutterbox.py...")
10
11# Import the Webots simulator API.
12from controller import Robot
13from controller import Keyboard
14
15# Import the standard Python math library.
16import math
17
18# Define the time step in milliseconds between
19# controller updates.
20EVENT_LOOP_DT = 20
21
22# Request a proxy object representing the robot to
23# control.
24robot = Robot()
25name = robot.getName()
26print(f"shutterbox.py waking up for {name}...")
27
28# Fetch handle for the 'base' joint motor.
29motor1 = robot.getDevice('motor1')
30
31# Enable computer keyboard input for user control.
32keyboard = Keyboard()
33keyboard.enable(EVENT_LOOP_DT)
34
35# Run an event loop until the simulation quits,
36# indicated by the step function returning -1.
37while robot.step(EVENT_LOOP_DT) != -1:
38
39 # Read simulator clock time.
40 t = robot.getTime()
41
42 # Read any computer keyboard keypresses. Returns -1 or an integer keycode while a key is held down.
43 # This is debounced to detect only changes.
44 key = keyboard.getKey()
45 if key != -1:
46 # convert the integer key number to a lowercase single-character string
47 letter = chr(key).lower()
48
49 # Apply motor torques based on keypresses and robot identity.
50 if name == 'left':
51 if letter == 'a':
52 motor1.setTorque(-1.0)
53 elif letter == 'd':
54 motor1.setTorque(1.0)
55 else:
56 if letter == 'j':
57 motor1.setTorque(-1.0)
58 elif letter == 'l':
59 motor1.setTorque(1.0)
60 else:
61 motor1.setTorque(0.0)
MIDI Control Code¶
The controller implements MIDI input to trigger motor torques. The two machines respond to different note values for simultaneous control.
1# shutterbox_midi.py
2#
3# Sample Webots controller file for driving the
4# shutterbox device from MIDI input.
5#
6# No copyright, 2022, Garth Zeglin. This file is
7# explicitly placed in the public domain.
8################################################################
9print("loading shutterbox_osc.py...")
10
11# Import the Webots simulator API.
12from controller import Robot
13
14# Import standard Python libraries.
15import math, queue, platform
16
17# Import the MIDI interface.
18# See https://spotlightkid.github.io/python-rtmidi/
19# and https://pypi.org/project/python-rtmidi/
20import rtmidi
21
22# this should be customized for your particular MIDI controllers
23preferred_MIDI_device = 'IAC'
24# preferred_MIDI_device = 'MPD218'
25
26################################################################
27# Define the time step in milliseconds between
28# controller updates.
29EVENT_LOOP_DT = 20
30
31# Request a proxy object representing the robot to
32# control.
33robot = Robot()
34name = robot.getName()
35print(f"shutterbox_midi.py waking up for {name}...")
36
37# Fetch handle for the 'base' joint motor.
38motor1 = robot.getDevice('motor1')
39
40################################################################
41# Create a thread-safe queue to communicate data to the robot thread.
42messages = queue.Queue()
43
44# Callback function to receive MIDI messages.
45def midi_received(data, unused):
46 msg, delta_time = data
47 print("MIDI message: ", msg)
48 if len(msg) == 3:
49 # process NoteOn and NoteOff on channel 9
50 if msg[0] == 0x99: # decimal 153, NoteOn for channel 9
51 pad = msg[1] - 35 # pad 1 on the MPD218 is MIDI 36, pad 2 is 37, etc.
52 messages.put((pad, 1))
53 elif msg[0] == 0x89: # decimal 137, NoteOff for channel 9
54 pad = msg[1] - 35 # pad 1 on the MPD218 is MIDI 36, pad 2 is 37, etc.
55 messages.put((pad, 0))
56
57# Initialize the MIDI input system and read the currently available ports.
58midi_in = rtmidi.MidiIn()
59for idx, midi_name in enumerate(midi_in.get_ports()):
60 if preferred_MIDI_device in midi_name:
61 print("Found preferred MIDI input device %d: %s" % (idx, midi_name))
62 midi_in.open_port(idx)
63 midi_in.set_callback(midi_received)
64 break
65 else:
66 print("Ignoring unselected MIDI device: ", midi_name)
67
68if not midi_in.is_port_open():
69 if platform.system() == 'Windows':
70 print("Virtual MIDI inputs are not currently supported on Windows, see python-rtmidi documentation.")
71 else:
72 print("Creating virtual MIDI input.")
73 midi_in.open_virtual_port(preferred_MIDI_device)
74 if not midi_in.is_port_open():
75 print("Unable to open MIDI device.")
76
77################################################################
78# Run an event loop until the simulation quits,
79# indicated by the step function returning -1.
80while robot.step(EVENT_LOOP_DT) != -1:
81
82 # Read simulator clock time.
83 t = robot.getTime()
84
85 # Check for MIDI messages.
86 if not messages.empty():
87 # each message is a tuple of the form (button index, status)
88 # e.g. (1,1) is button 1 pressed, (1,0) is button 1 released
89 msg = messages.get()
90 print("Main loop received", msg)
91 if ((name == 'left') and (msg[0] == 1)) or ((name == 'right') and (msg[0] == 3)):
92 motor1.setTorque( -1 if msg[1] == 1 else 0)
93
94 elif (name == 'left' and msg[0] == 2) or (name == 'right' and msg[0] == 4):
95 motor1.setTorque( 1 if msg[1] == 1 else 0)
OSC Control Code¶
The controller implements OSC input to trigger motor torques. Each machine runs a separate OSC server.
1# shutterbox_osc.py
2#
3# Sample Webots controller file for driving the
4# shutterbox device from OSC network input.
5#
6# No copyright, 2022, Garth Zeglin. This file is
7# explicitly placed in the public domain.
8################################################################
9print("loading shutterbox_osc.py...")
10
11# Import the Webots simulator API.
12from controller import Robot
13
14# Import standard Python libraries.
15import math, threading, queue
16
17# This uses python-osc to communicate with a Max/MSP patch.
18# installation: pip3 install python-osc
19# source code: https://github.com/attwad/python-osc
20# pypi description: https://pypi.org/project/python-osc/
21from pythonosc import udp_client
22from pythonosc import dispatcher
23from pythonosc import osc_server
24
25################################################################
26# Define the time step in milliseconds between
27# controller updates.
28EVENT_LOOP_DT = 20
29
30# Request a proxy object representing the robot to
31# control.
32robot = Robot()
33name = robot.getName()
34print(f"shutterbox_osc.py waking up for {name}...")
35
36# Fetch handle for the 'base' joint motor.
37motor1 = robot.getDevice('motor1')
38
39################################################################
40# Start a background thread running an OSC server listening for messages on an UDP socket.
41
42# Create a thread-safe queue to communicate data to the robot thread.
43messages = queue.Queue()
44
45def message(msgaddr, *args):
46 """Process messages received via OSC over UDP."""
47 print("Controller received message %s: %s" % (msgaddr, args))
48 if msgaddr == '/buttonbox/button':
49 messages.put(args)
50
51def unknown_message(msgaddr, *args):
52 """Default handler for unrecognized OSC messages."""
53 print("Simulator received unmapped message %s: %s" % (msgaddr, args))
54
55# Initialize the OSC message dispatch system.
56dispatch = dispatcher.Dispatcher()
57dispatch.map("/buttonbox/*", message)
58dispatch.set_default_handler(unknown_message)
59
60# Start and run the server.
61simport = 16375 if name == 'left' else 54375
62server = osc_server.ThreadingOSCUDPServer(('127.0.0.1', simport), dispatch)
63server_thread = threading.Thread(target=server.serve_forever)
64server_thread.daemon = True
65server_thread.start()
66print(f"shutterbox_osc {name} started OSC server on port {simport}")
67
68################################################################
69# Run an event loop until the simulation quits,
70# indicated by the step function returning -1.
71while robot.step(EVENT_LOOP_DT) != -1:
72
73 # Read simulator clock time.
74 t = robot.getTime()
75
76 # Check for network messages.
77 if not messages.empty():
78 # each message is a tuple of the form (button index, status)
79 # e.g. (1,1) is button 1 pressed, (1,0) is button 1 released
80 msg = messages.get()
81 if len(msg) == 2:
82 if msg[0] == 1:
83 if msg[1] == 1:
84 motor1.setTorque(-1)
85 else:
86 motor1.setTorque(0)
87 elif msg[0] == 1:
88 if msg[1] == 1:
89 motor1.setTorque(1)
90 else:
91 motor1.setTorque(0)
World File¶
1#VRML_SIM R2023b utf8
2
3EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/GlossyPaint.proto"
4EXTERNPROTO "../protos/pedestal.proto"
5EXTERNPROTO "../protos/HL-A11-room.proto"
6
7WorldInfo {
8 basicTimeStep 5
9}
10Viewpoint {
11 fieldOfView 1
12 orientation -0.06014444191355276 0.01554098495027224 0.9980686969811661 2.067312179577502
13 position 2.38996 -4.54385 1.7
14}
15Background {
16 skyColor [
17 0.1 0.1 0.1
18 ]
19}
20DirectionalLight {
21 direction -0.4 -0.5 -1
22 intensity 0.25
23 castShadows TRUE
24}
25HL-A11-room {
26 rotation 0 0 1 -1.5707953071795862
27}
28pedestal {
29 translation 0.75 0 0
30 width 0.5
31 depth 0.5
32 height 1
33}
34Robot {
35 translation 0.75 0 1
36 children [
37 DEF base Pose {
38 translation 0 0 0.05
39 children [
40 Shape {
41 appearance GlossyPaint {
42 baseColor 0.180392 0 1
43 }
44 geometry Cylinder {
45 height 0.1
46 radius 0.2
47 }
48 }
49 ]
50 }
51 PointLight {
52 attenuation 0 0 1
53 location 0 0 0.3
54 castShadows TRUE
55 }
56 Pose {
57 translation 0 0 0.2
58 children [
59 Shape {
60 appearance GlossyPaint {
61 }
62 geometry Sphere {
63 radius 0.01
64 }
65 castShadows FALSE
66 }
67 ]
68 }
69 HingeJoint {
70 jointParameters HingeJointParameters {
71 axis 0 0 1
72 dampingConstant 0.1
73 }
74 device [
75 RotationalMotor {
76 name "motor1"
77 maxVelocity 3.14
78 }
79 ]
80 endPoint Solid {
81 translation 0 0 0.11000000000000003
82 children [
83 DEF disc1 Pose {
84 translation 0 0 0.005
85 children [
86 Shape {
87 appearance GlossyPaint {
88 baseColor 0.772549 0.2 0.192157
89 }
90 geometry Cylinder {
91 height 0.01
92 radius 0.16
93 }
94 }
95 ]
96 }
97 HingeJoint {
98 jointParameters HingeJointParameters {
99 axis 0 0 1
100 anchor 0.15 0 0
101 minStop -0.01
102 maxStop 2
103 springConstant 0.05
104 dampingConstant 0.01
105 }
106 endPoint Solid {
107 translation 0 0 0.01
108 children [
109 Shape {
110 appearance GlossyPaint {
111 baseColor 0.192157 0.772549 0.589319
112 }
113 geometry Mesh {
114 url [
115 "../stl/shutterbox-vane.stl"
116 ]
117 }
118 }
119 ]
120 physics Physics {
121 density -1
122 mass 0.5
123 centerOfMass [
124 0.1 -0.1 0.1
125 ]
126 inertiaMatrix [
127 0.03 0.03 0.06
128 0 0 0
129 ]
130 }
131 }
132 }
133 HingeJoint {
134 jointParameters HingeJointParameters {
135 axis 0 0 1
136 anchor 0.0463525 0.142658 0
137 minStop -0.01
138 maxStop 2
139 springConstant 0.05
140 dampingConstant 0.01
141 }
142 endPoint Solid {
143 translation 0 0 0.01
144 rotation 0 0 1 1.2566370614359172
145 children [
146 Shape {
147 appearance GlossyPaint {
148 baseColor 0.192157 0.772549 0.589319
149 }
150 geometry Mesh {
151 url [
152 "../stl/shutterbox-vane.stl"
153 ]
154 }
155 }
156 ]
157 name "solid(1)"
158 physics Physics {
159 density -1
160 mass 0.5
161 centerOfMass [
162 0.1 -0.1 0.1
163 ]
164 inertiaMatrix [
165 0.03 0.03 0.06
166 0 0 0
167 ]
168 }
169 }
170 }
171 HingeJoint {
172 jointParameters HingeJointParameters {
173 axis 0 0 1
174 anchor -0.121353 0.0881678 0
175 minStop -0.01
176 maxStop 2
177 springConstant 0.05
178 dampingConstant 0.01
179 }
180 endPoint Solid {
181 translation 0 0 0.01
182 rotation 0 0 0.9999999999999999 2.5132741228718345
183 children [
184 Shape {
185 appearance GlossyPaint {
186 baseColor 0.192157 0.772549 0.589319
187 }
188 geometry Mesh {
189 url [
190 "../stl/shutterbox-vane.stl"
191 ]
192 }
193 }
194 ]
195 name "solid(2)"
196 physics Physics {
197 density -1
198 mass 0.5
199 centerOfMass [
200 0.1 -0.1 0.1
201 ]
202 inertiaMatrix [
203 0.03 0.03 0.06
204 0 0 0
205 ]
206 }
207 }
208 }
209 HingeJoint {
210 jointParameters HingeJointParameters {
211 axis 0 0 1
212 anchor -0.121353 -0.0881678 0
213 minStop -0.01
214 maxStop 2
215 springConstant 0.05
216 dampingConstant 0.01
217 }
218 endPoint Solid {
219 translation 0 0 0.01
220 rotation 0 0 -0.9999999999999999 2.5132741228718345
221 children [
222 Shape {
223 appearance GlossyPaint {
224 baseColor 0.192157 0.772549 0.589319
225 }
226 geometry Mesh {
227 url [
228 "../stl/shutterbox-vane.stl"
229 ]
230 }
231 }
232 ]
233 name "solid(3)"
234 physics Physics {
235 density -1
236 mass 0.5
237 centerOfMass [
238 0.1 -0.1 0.1
239 ]
240 inertiaMatrix [
241 0.03 0.03 0.06
242 0 0 0
243 ]
244 }
245 }
246 }
247 HingeJoint {
248 jointParameters HingeJointParameters {
249 axis 0 0 1
250 anchor 0.0463525 -0.142658 0
251 minStop -0.01
252 maxStop 2
253 springConstant 0.05
254 dampingConstant 0.01
255 }
256 endPoint Solid {
257 translation 0 0 0.01
258 rotation 0 0 -1 1.2566370614359172
259 children [
260 Shape {
261 appearance GlossyPaint {
262 baseColor 0.192157 0.772549 0.589319
263 }
264 geometry Mesh {
265 url [
266 "../stl/shutterbox-vane.stl"
267 ]
268 }
269 }
270 ]
271 name "solid(4)"
272 physics Physics {
273 density -1
274 mass 0.5
275 centerOfMass [
276 0.1 -0.1 0.1
277 ]
278 inertiaMatrix [
279 0.03 0.03 0.06
280 0 0 0
281 ]
282 }
283 }
284 }
285 ]
286 physics Physics {
287 density -1
288 mass 2
289 centerOfMass [
290 0 0 0
291 ]
292 inertiaMatrix [
293 0.03 0.03 0.06
294 0 0 0
295 ]
296 }
297 }
298 }
299 ]
300 name "right"
301 controller "shutterbox"
302}
303pedestal {
304 translation -0.75 0 0
305 width 0.5
306 depth 0.5
307 height 1
308}
309Robot {
310 translation -0.75 0 1
311 children [
312 DEF base Pose {
313 translation 0 0 0.05
314 children [
315 Shape {
316 appearance GlossyPaint {
317 baseColor 0.180392 0 1
318 }
319 geometry Cylinder {
320 height 0.1
321 radius 0.2
322 }
323 }
324 ]
325 }
326 PointLight {
327 attenuation 0 0 1
328 location 0 0 0.3
329 castShadows TRUE
330 }
331 Pose {
332 translation 0 0 0.2
333 children [
334 Shape {
335 appearance GlossyPaint {
336 }
337 geometry Sphere {
338 radius 0.01
339 }
340 castShadows FALSE
341 }
342 ]
343 }
344 HingeJoint {
345 jointParameters HingeJointParameters {
346 axis 0 0 1
347 dampingConstant 0.1
348 }
349 device [
350 RotationalMotor {
351 name "motor1"
352 maxVelocity 3.14
353 }
354 ]
355 endPoint Solid {
356 translation 0 0 0.11000000000000003
357 children [
358 DEF disc1 Pose {
359 translation 0 0 0.005
360 children [
361 Shape {
362 appearance GlossyPaint {
363 baseColor 0.772549 0.2 0.192157
364 }
365 geometry Cylinder {
366 height 0.01
367 radius 0.16
368 }
369 }
370 ]
371 }
372 HingeJoint {
373 jointParameters HingeJointParameters {
374 axis 0 0 1
375 anchor 0.15 0 0
376 minStop -0.01
377 maxStop 2
378 springConstant 0.05
379 dampingConstant 0.01
380 }
381 endPoint Solid {
382 translation 0 0 0.01
383 children [
384 Shape {
385 appearance GlossyPaint {
386 baseColor 0.192157 0.772549 0.589319
387 }
388 geometry Mesh {
389 url [
390 "../stl/shutterbox-vane.stl"
391 ]
392 }
393 }
394 ]
395 physics Physics {
396 density -1
397 mass 0.5
398 centerOfMass [
399 0.1 -0.1 0.1
400 ]
401 inertiaMatrix [
402 0.03 0.03 0.06
403 0 0 0
404 ]
405 }
406 }
407 }
408 HingeJoint {
409 jointParameters HingeJointParameters {
410 axis 0 0 1
411 anchor 0.0463525 0.142658 0
412 minStop -0.01
413 maxStop 2
414 springConstant 0.05
415 dampingConstant 0.01
416 }
417 endPoint Solid {
418 translation 0 0 0.01
419 rotation 0 0 1 1.2566370614359172
420 children [
421 Shape {
422 appearance GlossyPaint {
423 baseColor 0.192157 0.772549 0.589319
424 }
425 geometry Mesh {
426 url [
427 "../stl/shutterbox-vane.stl"
428 ]
429 }
430 }
431 ]
432 name "solid(1)"
433 physics Physics {
434 density -1
435 mass 0.5
436 centerOfMass [
437 0.1 -0.1 0.1
438 ]
439 inertiaMatrix [
440 0.03 0.03 0.06
441 0 0 0
442 ]
443 }
444 }
445 }
446 HingeJoint {
447 jointParameters HingeJointParameters {
448 axis 0 0 1
449 anchor -0.121353 0.0881678 0
450 minStop -0.01
451 maxStop 2
452 springConstant 0.05
453 dampingConstant 0.01
454 }
455 endPoint Solid {
456 translation 0 0 0.01
457 rotation 0 0 0.9999999999999999 2.5132741228718345
458 children [
459 Shape {
460 appearance GlossyPaint {
461 baseColor 0.192157 0.772549 0.589319
462 }
463 geometry Mesh {
464 url [
465 "../stl/shutterbox-vane.stl"
466 ]
467 }
468 }
469 ]
470 name "solid(2)"
471 physics Physics {
472 density -1
473 mass 0.5
474 centerOfMass [
475 0.1 -0.1 0.1
476 ]
477 inertiaMatrix [
478 0.03 0.03 0.06
479 0 0 0
480 ]
481 }
482 }
483 }
484 HingeJoint {
485 jointParameters HingeJointParameters {
486 axis 0 0 1
487 anchor -0.121353 -0.0881678 0
488 minStop -0.01
489 maxStop 2
490 springConstant 0.05
491 dampingConstant 0.01
492 }
493 endPoint Solid {
494 translation 0 0 0.01
495 rotation 0 0 -0.9999999999999999 2.5132741228718345
496 children [
497 Shape {
498 appearance GlossyPaint {
499 baseColor 0.192157 0.772549 0.589319
500 }
501 geometry Mesh {
502 url [
503 "../stl/shutterbox-vane.stl"
504 ]
505 }
506 }
507 ]
508 name "solid(3)"
509 physics Physics {
510 density -1
511 mass 0.5
512 centerOfMass [
513 0.1 -0.1 0.1
514 ]
515 inertiaMatrix [
516 0.03 0.03 0.06
517 0 0 0
518 ]
519 }
520 }
521 }
522 HingeJoint {
523 jointParameters HingeJointParameters {
524 axis 0 0 1
525 anchor 0.0463525 -0.142658 0
526 minStop -0.01
527 maxStop 2
528 springConstant 0.05
529 dampingConstant 0.01
530 }
531 endPoint Solid {
532 translation 0 0 0.01
533 rotation 0 0 -1 1.2566370614359172
534 children [
535 Shape {
536 appearance GlossyPaint {
537 baseColor 0.192157 0.772549 0.589319
538 }
539 geometry Mesh {
540 url [
541 "../stl/shutterbox-vane.stl"
542 ]
543 }
544 }
545 ]
546 name "solid(4)"
547 physics Physics {
548 density -1
549 mass 0.5
550 centerOfMass [
551 0.1 -0.1 0.1
552 ]
553 inertiaMatrix [
554 0.03 0.03 0.06
555 0 0 0
556 ]
557 }
558 }
559 }
560 ]
561 physics Physics {
562 density -1
563 mass 2
564 centerOfMass [
565 0 0 0
566 ]
567 inertiaMatrix [
568 0.03 0.03 0.06
569 0 0 0
570 ]
571 }
572 }
573 }
574 ]
575 name "left"
576 controller "shutterbox"
577}