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 R2022a utf8
2WorldInfo {
3 basicTimeStep 5
4}
5Viewpoint {
6 orientation -0.26570435851864105 0.22355324061151227 0.9377767018199423 1.7385757199994738
7 position 0.32559893826458497 -2.6292060353822615 2.7294458883735686
8}
9Background {
10 skyColor [
11 0.1 0.1 0.1
12 ]
13}
14DirectionalLight {
15 direction -0.4 -0.5 -1
16 intensity 0.25
17 castShadows TRUE
18}
19RectangleArena {
20 floorSize 5 2
21}
22Wall {
23 translation 0 1 0
24 rotation 0 0 1 1.5708
25 size 0.2 5 2.4
26}
27pedestal {
28 translation 0.75 0 0
29 width 0.5
30 depth 0.5
31 height 1
32}
33Robot {
34 translation 0.75 0 1
35 children [
36 DEF base Transform {
37 translation 0 0 0.05
38 children [
39 Shape {
40 appearance GlossyPaint {
41 baseColor 0.180392 0 1
42 }
43 geometry Cylinder {
44 height 0.1
45 radius 0.2
46 }
47 }
48 ]
49 }
50 PointLight {
51 attenuation 0 0 1
52 location 0 0 0.3
53 castShadows TRUE
54 }
55 Transform {
56 translation 0 0 0.2
57 children [
58 Shape {
59 appearance GlossyPaint {
60 }
61 geometry Sphere {
62 radius 0.01
63 }
64 castShadows FALSE
65 }
66 ]
67 }
68 HingeJoint {
69 jointParameters HingeJointParameters {
70 axis 0 0 1
71 dampingConstant 0.1
72 }
73 device [
74 RotationalMotor {
75 name "motor1"
76 maxVelocity 3.14
77 }
78 ]
79 endPoint Solid {
80 translation 0 0 0.11000000000000003
81 children [
82 DEF disc1 Transform {
83 translation 0 0 0.005
84 children [
85 Shape {
86 appearance GlossyPaint {
87 baseColor 0.772549 0.2 0.192157
88 }
89 geometry Cylinder {
90 height 0.01
91 radius 0.16
92 }
93 }
94 ]
95 }
96 HingeJoint {
97 jointParameters HingeJointParameters {
98 axis 0 0 1
99 anchor 0.15 0 0
100 minStop -0.01
101 maxStop 2
102 springConstant 0.05
103 dampingConstant 0.01
104 }
105 endPoint Solid {
106 translation 0 0 0.01
107 children [
108 Shape {
109 appearance GlossyPaint {
110 baseColor 0.192157 0.772549 0.589319
111 }
112 geometry Mesh {
113 url [
114 "../stl/shutterbox-vane.stl"
115 ]
116 }
117 }
118 ]
119 physics Physics {
120 density -1
121 mass 0.5
122 centerOfMass [
123 0.1 -0.1 0.1
124 ]
125 inertiaMatrix [
126 0.03 0.03 0.06
127 0 0 0
128 ]
129 }
130 }
131 }
132 HingeJoint {
133 jointParameters HingeJointParameters {
134 axis 0 0 1
135 anchor 0.0463525 0.142658 0
136 minStop -0.01
137 maxStop 2
138 springConstant 0.05
139 dampingConstant 0.01
140 }
141 endPoint Solid {
142 translation 0 0 0.01
143 rotation 0 0 1 1.2566370614359172
144 children [
145 Shape {
146 appearance GlossyPaint {
147 baseColor 0.192157 0.772549 0.589319
148 }
149 geometry Mesh {
150 url [
151 "../stl/shutterbox-vane.stl"
152 ]
153 }
154 }
155 ]
156 name "solid(1)"
157 physics Physics {
158 density -1
159 mass 0.5
160 centerOfMass [
161 0.1 -0.1 0.1
162 ]
163 inertiaMatrix [
164 0.03 0.03 0.06
165 0 0 0
166 ]
167 }
168 }
169 }
170 HingeJoint {
171 jointParameters HingeJointParameters {
172 axis 0 0 1
173 anchor -0.121353 0.0881678 0
174 minStop -0.01
175 maxStop 2
176 springConstant 0.05
177 dampingConstant 0.01
178 }
179 endPoint Solid {
180 translation 0 0 0.01
181 rotation 0 0 0.9999999999999999 2.5132741228718345
182 children [
183 Shape {
184 appearance GlossyPaint {
185 baseColor 0.192157 0.772549 0.589319
186 }
187 geometry Mesh {
188 url [
189 "../stl/shutterbox-vane.stl"
190 ]
191 }
192 }
193 ]
194 name "solid(2)"
195 physics Physics {
196 density -1
197 mass 0.5
198 centerOfMass [
199 0.1 -0.1 0.1
200 ]
201 inertiaMatrix [
202 0.03 0.03 0.06
203 0 0 0
204 ]
205 }
206 }
207 }
208 HingeJoint {
209 jointParameters HingeJointParameters {
210 axis 0 0 1
211 anchor -0.121353 -0.0881678 0
212 minStop -0.01
213 maxStop 2
214 springConstant 0.05
215 dampingConstant 0.01
216 }
217 endPoint Solid {
218 translation 0 0 0.01
219 rotation 0 0 -0.9999999999999999 2.5132741228718345
220 children [
221 Shape {
222 appearance GlossyPaint {
223 baseColor 0.192157 0.772549 0.589319
224 }
225 geometry Mesh {
226 url [
227 "../stl/shutterbox-vane.stl"
228 ]
229 }
230 }
231 ]
232 name "solid(3)"
233 physics Physics {
234 density -1
235 mass 0.5
236 centerOfMass [
237 0.1 -0.1 0.1
238 ]
239 inertiaMatrix [
240 0.03 0.03 0.06
241 0 0 0
242 ]
243 }
244 }
245 }
246 HingeJoint {
247 jointParameters HingeJointParameters {
248 axis 0 0 1
249 anchor 0.0463525 -0.142658 0
250 minStop -0.01
251 maxStop 2
252 springConstant 0.05
253 dampingConstant 0.01
254 }
255 endPoint Solid {
256 translation 0 0 0.01
257 rotation 0 0 -1 1.2566370614359172
258 children [
259 Shape {
260 appearance GlossyPaint {
261 baseColor 0.192157 0.772549 0.589319
262 }
263 geometry Mesh {
264 url [
265 "../stl/shutterbox-vane.stl"
266 ]
267 }
268 }
269 ]
270 name "solid(4)"
271 physics Physics {
272 density -1
273 mass 0.5
274 centerOfMass [
275 0.1 -0.1 0.1
276 ]
277 inertiaMatrix [
278 0.03 0.03 0.06
279 0 0 0
280 ]
281 }
282 }
283 }
284 ]
285 physics Physics {
286 density -1
287 mass 2
288 centerOfMass [
289 0 0 0
290 ]
291 inertiaMatrix [
292 0.03 0.03 0.06
293 0 0 0
294 ]
295 }
296 }
297 }
298 ]
299 name "right"
300 controller "shutterbox"
301}
302pedestal {
303 translation -0.75 0 0
304 width 0.5
305 depth 0.5
306 height 1
307}
308Robot {
309 translation -0.75 0 1
310 children [
311 DEF base Transform {
312 translation 0 0 0.05
313 children [
314 Shape {
315 appearance GlossyPaint {
316 baseColor 0.180392 0 1
317 }
318 geometry Cylinder {
319 height 0.1
320 radius 0.2
321 }
322 }
323 ]
324 }
325 PointLight {
326 attenuation 0 0 1
327 location 0 0 0.3
328 castShadows TRUE
329 }
330 Transform {
331 translation 0 0 0.2
332 children [
333 Shape {
334 appearance GlossyPaint {
335 }
336 geometry Sphere {
337 radius 0.01
338 }
339 castShadows FALSE
340 }
341 ]
342 }
343 HingeJoint {
344 jointParameters HingeJointParameters {
345 axis 0 0 1
346 dampingConstant 0.1
347 }
348 device [
349 RotationalMotor {
350 name "motor1"
351 maxVelocity 3.14
352 }
353 ]
354 endPoint Solid {
355 translation 0 0 0.11000000000000003
356 children [
357 DEF disc1 Transform {
358 translation 0 0 0.005
359 children [
360 Shape {
361 appearance GlossyPaint {
362 baseColor 0.772549 0.2 0.192157
363 }
364 geometry Cylinder {
365 height 0.01
366 radius 0.16
367 }
368 }
369 ]
370 }
371 HingeJoint {
372 jointParameters HingeJointParameters {
373 axis 0 0 1
374 anchor 0.15 0 0
375 minStop -0.01
376 maxStop 2
377 springConstant 0.05
378 dampingConstant 0.01
379 }
380 endPoint Solid {
381 translation 0 0 0.01
382 children [
383 Shape {
384 appearance GlossyPaint {
385 baseColor 0.192157 0.772549 0.589319
386 }
387 geometry Mesh {
388 url [
389 "../stl/shutterbox-vane.stl"
390 ]
391 }
392 }
393 ]
394 physics Physics {
395 density -1
396 mass 0.5
397 centerOfMass [
398 0.1 -0.1 0.1
399 ]
400 inertiaMatrix [
401 0.03 0.03 0.06
402 0 0 0
403 ]
404 }
405 }
406 }
407 HingeJoint {
408 jointParameters HingeJointParameters {
409 axis 0 0 1
410 anchor 0.0463525 0.142658 0
411 minStop -0.01
412 maxStop 2
413 springConstant 0.05
414 dampingConstant 0.01
415 }
416 endPoint Solid {
417 translation 0 0 0.01
418 rotation 0 0 1 1.2566370614359172
419 children [
420 Shape {
421 appearance GlossyPaint {
422 baseColor 0.192157 0.772549 0.589319
423 }
424 geometry Mesh {
425 url [
426 "../stl/shutterbox-vane.stl"
427 ]
428 }
429 }
430 ]
431 name "solid(1)"
432 physics Physics {
433 density -1
434 mass 0.5
435 centerOfMass [
436 0.1 -0.1 0.1
437 ]
438 inertiaMatrix [
439 0.03 0.03 0.06
440 0 0 0
441 ]
442 }
443 }
444 }
445 HingeJoint {
446 jointParameters HingeJointParameters {
447 axis 0 0 1
448 anchor -0.121353 0.0881678 0
449 minStop -0.01
450 maxStop 2
451 springConstant 0.05
452 dampingConstant 0.01
453 }
454 endPoint Solid {
455 translation 0 0 0.01
456 rotation 0 0 0.9999999999999999 2.5132741228718345
457 children [
458 Shape {
459 appearance GlossyPaint {
460 baseColor 0.192157 0.772549 0.589319
461 }
462 geometry Mesh {
463 url [
464 "../stl/shutterbox-vane.stl"
465 ]
466 }
467 }
468 ]
469 name "solid(2)"
470 physics Physics {
471 density -1
472 mass 0.5
473 centerOfMass [
474 0.1 -0.1 0.1
475 ]
476 inertiaMatrix [
477 0.03 0.03 0.06
478 0 0 0
479 ]
480 }
481 }
482 }
483 HingeJoint {
484 jointParameters HingeJointParameters {
485 axis 0 0 1
486 anchor -0.121353 -0.0881678 0
487 minStop -0.01
488 maxStop 2
489 springConstant 0.05
490 dampingConstant 0.01
491 }
492 endPoint Solid {
493 translation 0 0 0.01
494 rotation 0 0 -0.9999999999999999 2.5132741228718345
495 children [
496 Shape {
497 appearance GlossyPaint {
498 baseColor 0.192157 0.772549 0.589319
499 }
500 geometry Mesh {
501 url [
502 "../stl/shutterbox-vane.stl"
503 ]
504 }
505 }
506 ]
507 name "solid(3)"
508 physics Physics {
509 density -1
510 mass 0.5
511 centerOfMass [
512 0.1 -0.1 0.1
513 ]
514 inertiaMatrix [
515 0.03 0.03 0.06
516 0 0 0
517 ]
518 }
519 }
520 }
521 HingeJoint {
522 jointParameters HingeJointParameters {
523 axis 0 0 1
524 anchor 0.0463525 -0.142658 0
525 minStop -0.01
526 maxStop 2
527 springConstant 0.05
528 dampingConstant 0.01
529 }
530 endPoint Solid {
531 translation 0 0 0.01
532 rotation 0 0 -1 1.2566370614359172
533 children [
534 Shape {
535 appearance GlossyPaint {
536 baseColor 0.192157 0.772549 0.589319
537 }
538 geometry Mesh {
539 url [
540 "../stl/shutterbox-vane.stl"
541 ]
542 }
543 }
544 ]
545 name "solid(4)"
546 physics Physics {
547 density -1
548 mass 0.5
549 centerOfMass [
550 0.1 -0.1 0.1
551 ]
552 inertiaMatrix [
553 0.03 0.03 0.06
554 0 0 0
555 ]
556 }
557 }
558 }
559 ]
560 physics Physics {
561 density -1
562 mass 2
563 centerOfMass [
564 0 0 0
565 ]
566 inertiaMatrix [
567 0.03 0.03 0.06
568 0 0 0
569 ]
570 }
571 }
572 }
573 ]
574 name "left"
575 controller "shutterbox"
576}