1
1
#!/usr/bin/env python3
2
2
3
+ # Import stuff
3
4
from configparser import ConfigParser
4
5
from time import sleep
5
6
from ev3dev2 .display import *
@@ -16,90 +17,143 @@ def configBuilder(configFile='robot.cfg'):
16
17
"""
17
18
Creates a robot config file based on user input and sensor/motor values
18
19
"""
20
+ # Make sure this is the global lego module, as it is used dynamicly later. Unsure if needed.
19
21
global lego
22
+ # Instantiate brick interface objects
20
23
btn = Button ()
21
24
disp = Display ()
22
25
spkr = Sound ()
26
+ # Instantiate a ConfigParser object to writ and read the INI format config file
23
27
config = ConfigParser ()
28
+
29
+ # Create the config file sections
24
30
config ['Drivebase' ] = {}
25
31
config ['Sensors' ] = {}
26
32
config ['AuxMotors' ] = {}
27
33
config ['Other' ] = {}
34
+
35
+ # Used when detecting motors and sensors, as iterables
28
36
outPorts = ['OUTPUT_A' , 'OUTPUT_B' , 'OUTPUT_C' , 'OUTPUT_D' ]
29
37
senTypes = ['Color' , 'Gyro' , 'Touch' , 'Ultrasonic' , 'Infrared' ]
30
38
39
+ # Ask the user if the robot is to be used for FLL
31
40
disp .text_pixels ("Is this robot for FLL?" )
32
41
disp .text_pixels ("Y" , False , 10 , 110 )
33
42
disp .text_pixels ("N" , False , 150 , 110 )
34
43
disp .update ()
35
44
spkr .beep ()
45
+ # Wait for a button press (the outer loop is to allow for selection retry if
46
+ # an invalid button is pressed)
36
47
while True :
37
48
while not btn .any ():
38
49
pass
50
+ # Get the list of currently pressed buttons
39
51
selection = btn .buttons_pressed
52
+ # Check if the first buton in the list is the left button
40
53
if selection [0 ] == 'left' :
54
+ # If it is, set the ForFLL value to TRUE and exit the outer loop
41
55
btn .wait_for_released ('left' )
42
56
config ['Other' ]['ForFLL' ] = 'TRUE'
43
57
break
44
58
elif selection [0 ] == 'right' :
59
+ # Otherwise, check if the selection is right. If it is, set ForFLL to FLASE
60
+ # and exit the outer loop
45
61
btn .wait_for_released ('right' )
46
62
config ['Other' ]['ForFLL' ] = 'FALSE'
47
63
break
48
64
else :
65
+ # If both checks fail, play an error tone and go back to waiting for a button
49
66
spkr .beep ()
50
67
spkr .beep ('-f 380' )
68
+ # Clear the display for the next prompt
51
69
disp .clear ()
52
70
53
-
71
+ # Auto-detect sensors
54
72
for i in senTypes :
55
73
try :
74
+ # When a sensor object is instantiated without a port, it will find the first port with
75
+ # that type of sensor connected. If that sensor is not connected, it will throw an error.
76
+ # The getattr function is used to get the correct sensor type dynamically
56
77
sensorClass = getattr (lego , i + 'Sensor' )
78
+ # Instantiate the current sensor type
57
79
mySensor = sensorClass ()
80
+ # Get the port that the sensor is connected to
58
81
port = str (mySensor .address )
82
+ # sensor.adress will return sometheing like ev3:in1, so this replaces anything containing in# with INPUT_#
59
83
port = re .sub ('.*in([0-9]).*' , 'INPUT_\g<1>' , port )
84
+ # Add port value to the config file
60
85
config ['Sensors' ][i + 'Port' ] = port
61
86
except :
87
+ # If the sensor instantiation failed, none of that kind of sensor are connected, so write the port value as None
62
88
config ['Sensors' ][i + 'Port' ] = 'None'
63
89
finally :
90
+ # Clear the object and class variables as they are reused every loop cycle
64
91
mySensor = None
65
92
sensorClass = None
66
93
94
+ # Detect motors
95
+ # Repeat test for each port
67
96
for i in outPorts :
68
97
try :
98
+ # Instanitiate a Large Motor object at the current port. If there is a Lrge Motor at said port, this will suceed.
99
+ # Otherwise, it will throw an error and exit the try block.
69
100
motor = LargeMotor (eval (i ))
101
+ # Print where the motor was found (port A, B, C, or D), and ask the user what the motor is being used for.
70
102
disp .text_pixels ("Found a Large Motor at " + "port " + i [- 1 ])
71
103
disp .text_pixels ("What kind of motor is this?" , False , 0 , 10 )
72
104
disp .text_pixels ("Press left for left motor," , False , 0 , 20 )
73
105
disp .text_pixels ("right for right motor," , False , 0 , 30 )
74
106
disp .text_pixels ("up for Aux1, or down for Aux2" , False , 0 , 40 )
75
107
disp .update ()
108
+ # Beep to signal that user input is required
76
109
spkr .beep ()
110
+
111
+ # Loop is used to allow for invalid button presses to repeat selection
77
112
while True :
113
+ # Wait for any button to be pressed
78
114
while not btn .any ():
79
115
pass
80
- selection = btn .buttons_pressed
81
- if selection [0 ] == 'left' :
116
+ # Store what it is (first button pressed, if multiple)
117
+ selection = btn .buttons_pressed [0 ]
118
+ if selection == 'left' :
119
+ # Wait for the button to be released, so the operation only occurs once
82
120
btn .wait_for_released ('left' )
121
+ # If left, store the current port as the left motor port
83
122
config ['Drivebase' ]['LeftMotorPort' ] = i
123
+ # Exit the loop, as this is a valid selection
84
124
break
85
- elif selection [0 ] == 'right' :
125
+ elif selection == 'right' :
126
+ # Wait for the button to be released, so the operation only occurs once
86
127
btn .wait_for_released ('right' )
128
+ # If right, store the current port as the right motor port
87
129
config ['Drivebase' ]['RightMotorPort' ] = i
130
+ # Exit the loop, as this is a valid selection
88
131
break
89
- elif selection [0 ] == 'up' :
132
+ elif selection == 'up' :
133
+ # Wait for the button to be released, so the operation only occurs once
90
134
btn .wait_for_released ('up' )
135
+ # If up, store the current port as the first auxillary motor port
91
136
config ['AuxMotors' ]['AuxMotor1' ] = i
137
+ # Exit the loop, as this is a valid selection
92
138
break
93
- elif selection [0 ] == 'down' :
139
+ elif selection == 'down' :
140
+ # Wait for the button to be released, so the operation only occurs once
94
141
btn .wait_for_released ('down' )
142
+ # If down, store the current port as the second auxillary motor port
95
143
config ['AuxMotors' ]['AuxMotor2' ] = i
144
+ # Exit the loop, as this is a valid selection
96
145
break
97
146
else :
147
+ # If the pressed button is not valid, play an error tone and repeat the selection menu
98
148
spkr .beep ()
99
149
spkr .beep ('-f 380' )
150
+
151
+ # If a motor is not found, no action is required. Apparently 'try' doesn't work without an 'except'
100
152
except :
101
153
pass
102
154
155
+ # This works exactly the same as the Large Motor detection, except that it is detecting Medium Motors instead,
156
+ # and there are no drivemotor selection options.
103
157
for i in outPorts :
104
158
try :
105
159
motor = MediumMotor (eval (i ))
@@ -112,12 +166,12 @@ def configBuilder(configFile='robot.cfg'):
112
166
while True :
113
167
while not btn .any ():
114
168
pass
115
- selection = btn .buttons_pressed
116
- if selection [ 0 ] == 'left' :
169
+ selection = btn .buttons_pressed [ 0 ]
170
+ if selection == 'left' :
117
171
btn .wait_for_released ('left' )
118
172
config ['AuxMotors' ]['AuxMotor1' ] = i
119
173
break
120
- elif selection [ 0 ] == 'right' :
174
+ elif selection == 'right' :
121
175
btn .wait_for_released ('right' )
122
176
config ['AuxMotors' ]['AuxMotor2' ] = i
123
177
break
@@ -127,15 +181,19 @@ def configBuilder(configFile='robot.cfg'):
127
181
except :
128
182
pass
129
183
184
+ # Create a MoveTank object with the detected drive motors, as it is needed for the upcoming tests.
130
185
mtrs = MoveTank (eval (config ['Drivebase' ]['LeftMotorPort' ]), eval (config ['Drivebase' ]['RightMotorPort' ]))
131
186
187
+ # A Foward-Facing Ultrasonic sensor is the only way to allow for calculating the wheel circumfrence.
132
188
if config ['Sensors' ]['UltrasonicPort' ] is not 'None' :
189
+ # Ask the user if the detected Ultrasonic sensor is facing fowards
133
190
us = UltrasonicSensor (eval (config ['Sensors' ]['UltrasonicPort' ]))
134
191
disp .text_pixels ("Does the Ultrasonic sensor" )
135
192
disp .text_pixels ("face foward?" , False , 0 , 10 )
136
193
disp .text_pixels ("Y" , False , 10 , 110 )
137
194
disp .text_pixels ("N" , False , 150 , 110 )
138
195
disp .update ()
196
+ # Same selection system as before
139
197
while not btn .any ():
140
198
pass
141
199
selection = btn .buttons_pressed [0 ]
@@ -150,39 +208,54 @@ def configBuilder(configFile='robot.cfg'):
150
208
usNav = False
151
209
us = None
152
210
211
+ # Using the variable usNav set previously, check if the Ultrasonic sensor faces fowards.
153
212
if usNav :
213
+ # Ask the user to place the robot facing a wall, then wait for a button press.
154
214
disp .text_pixels ("Place the robot facing a wall, " )
155
215
disp .text_pixels ("and press any button." , False , 0 , 10 )
156
216
disp .update ()
157
217
while not btn .any ():
158
218
pass
219
+ # Set up some variables for averaging
159
220
circsum = 0
160
221
sign = 1
161
222
tests = 3
223
+ # Repeat the wheelcircumfrence test multiple times to get a more accurate average
162
224
for j in range (tests ):
225
+ # Determine if this is an even or odd loop cycle. This is used for calculating MotorsInverted,
226
+ # as the motors are run backwards on odd cycles
163
227
if j / 2 == round (j / 2 ):
164
228
parity = 1
165
229
else :
166
230
parity = - 1
231
+ # More variables for averaging
167
232
usSum = 0
168
233
readings = 5
234
+ # Take multiple sensor readings of how far away from the wall the robot is, and average them
169
235
for i in range (readings ):
170
236
usSum += us .distance_centimeters
171
237
sleep (0.1 )
172
238
stavg = round (usSum / readings , 2 )
239
+ # Reset averaging variable
173
240
usSum = 0
174
- print ( stavg , file = stderr )
241
+ # Move the robot one wheel rotation, foward on even loop cycles, or backward on odd
175
242
mtrs .on_for_rotations (parity * 25 , parity * 25 , 1 )
243
+ # Take multiple sensor readings of how far away from the wall the robot is, and average them
176
244
for i in range (readings ):
177
245
usSum += us .distance_centimeters
178
246
sleep (0.1 )
179
247
enavg = round (usSum / readings , 2 )
180
- print (enavg , file = stderr )
248
+ # The absolute difference between the starting average and the ending average is the wheel circumfrence;
249
+ # however, this is a cumulative sum of all wheel circumfrence measurements for averaging.
181
250
circsum += abs (enavg - stavg )
251
+ # Isolate the sign (-x or x to -1 or 1) of the wheel circumfrence (Used for MotorsInverted)
182
252
sign = (enavg - stavg ) / abs (enavg - stavg )
253
+ # Calculate the average wheel circumfrence
183
254
avg = round (circsum / tests , 2 )
255
+ # Write to config file variable
184
256
config ['Drivebase' ]['WheelCircumfrence' ] = str (avg )
185
257
258
+ # Set MotorsInverted in the config file, and set the 'polarity' variable (for inverting motor commands if necessary)
186
259
polarity = 'normal'
187
260
if sign < 0 :
188
261
config ['Drivebase' ]['MotorsInverted' ] = 'FALSE'
@@ -191,31 +264,49 @@ def configBuilder(configFile='robot.cfg'):
191
264
config ['Drivebase' ]['MotorsInverted' ] = 'TRUE'
192
265
polarity = 'inversed'
193
266
267
+ # Invert motor commands if necessary
194
268
mtrs .set_polarity (polarity )
269
+ # Ask the user to place the robot in a clear area, and wait for a button press
195
270
disp .text_pixels ("Place robot in clear area," )
196
271
disp .text_pixels ("and press any button." , False , 0 , 10 )
197
272
disp .update ()
198
273
while not btn .any ():
199
274
pass
275
+ # Instantiate a Gyro Sensor object as the degrees turned is necessary for calculating the Width Between Wheels
200
276
gs = GyroSensor (eval (config ['Sensors' ]['GyroPort' ]))
277
+ # Create variables for averaging
201
278
widthbetweenwheelssum = 0
202
279
ang = 0
280
+ # Repeat the trial multiple times and average the results
203
281
for i in range (tests ):
282
+ # Reset the reported gyro angle to zero
204
283
gs ._ensure_mode (gs .MODE_GYRO_RATE )
205
284
gs ._ensure_mode (gs .MODE_GYRO_ANG )
285
+ # Turn in place for one wheel rotation
206
286
mtrs .on_for_degrees (25 , - 25 , 360 )
287
+ # Wait for the robot to settle
207
288
sleep (0.5 )
289
+ # Read the current angle of the robot
208
290
ang = gs .angle
291
+ # Calculate the width between the robot's wheels using the formula for arc length (ArcLength = (π * d * Θ) / 360) solved for d,
292
+ # the diameter of the circle, which is the width between wheels. Absolute value is used so the direction of turn does not matter.
209
293
wbw = abs ((360 * float (config ['Drivebase' ]['WheelCircumfrence' ])) / (ang * math .pi ))
294
+ # Add the calculated value to the running total (used for averaging)
210
295
widthbetweenwheelssum += wbw
296
+ # Calculate the average WBW value, round it to remove floating point errors, and store in the ConfigParser object
211
297
config ['Drivebase' ]['WidthBetweenWheels' ] = str (round (widthbetweenwheelssum / tests , 2 ))
212
298
299
+ # The motor move command previously made the robot turn right. If the gyro reported this as a turn towards positive, it is not inverted.
300
+ # Otherwise, the gyro is inverted.
213
301
if ang > 0 :
214
302
config ['Drivebase' ]['GyroInverted' ] = 'FALSE'
215
303
elif ang < 0 :
216
304
config ['Drivebase' ]['GyroInverted' ] = 'TRUE'
217
305
218
306
else :
307
+ # If the robot does not have an ultrasonic sensor that faces fowards, the robot cannot calculate the wheel circumfrence, which is required
308
+ # to calculate widthbetweenwheels. The robot also cannot calculate MotorsInverted or GyroInverted. Therefore, empty valuse are written to
309
+ # those parts of the config file, and the user is notified that they need to manually fill in that information.
219
310
spkr .beep ()
220
311
spkr .beep ()
221
312
config ['Drivebase' ]['WheelCircumfrence' ] = ''
@@ -230,6 +321,9 @@ def configBuilder(configFile='robot.cfg'):
230
321
while not btn .any ():
231
322
pass
232
323
324
+ # Write placeholder values for the PID gains, so those functions may be usable, if poorly.
325
+ # Because the WheelCircumfrence is calculated, the wheelcircumfrence value will include any gearing,
326
+ # so the effective gear ratio is one.
233
327
config ['Drivebase' ]['GearRatio' ] = '1'
234
328
config ['Drivebase' ]['kp' ] = '1'
235
329
config ['Drivebase' ]['ki' ] = '0'
@@ -238,6 +332,7 @@ def configBuilder(configFile='robot.cfg'):
238
332
config ['Sensors' ]['kiLine' ] = '0'
239
333
config ['Sensors' ]['kdLine' ] = '2'
240
334
335
+ # Tell the user that the PID values may (will) need to be tuned.
241
336
spkr .beep ()
242
337
spkr .beep ()
243
338
disp .text_pixels ("The PID gains in the config" )
@@ -248,6 +343,7 @@ def configBuilder(configFile='robot.cfg'):
248
343
while not btn .any ():
249
344
pass
250
345
346
+ # Write all the values stored in the ConfigParser object named config to a file in INI format, with the name passed by configFile.
251
347
with open (configFile , 'w' ) as configfile :
252
348
config .write (configfile )
253
349
0 commit comments