11#!/usr/bin/env python3
22
3+ # Import stuff
34from configparser import ConfigParser
45from time import sleep
56from ev3dev2 .display import *
@@ -16,90 +17,143 @@ def configBuilder(configFile='robot.cfg'):
1617 """
1718 Creates a robot config file based on user input and sensor/motor values
1819 """
20+ # Make sure this is the global lego module, as it is used dynamicly later. Unsure if needed.
1921 global lego
22+ # Instantiate brick interface objects
2023 btn = Button ()
2124 disp = Display ()
2225 spkr = Sound ()
26+ # Instantiate a ConfigParser object to writ and read the INI format config file
2327 config = ConfigParser ()
28+
29+ # Create the config file sections
2430 config ['Drivebase' ] = {}
2531 config ['Sensors' ] = {}
2632 config ['AuxMotors' ] = {}
2733 config ['Other' ] = {}
34+
35+ # Used when detecting motors and sensors, as iterables
2836 outPorts = ['OUTPUT_A' , 'OUTPUT_B' , 'OUTPUT_C' , 'OUTPUT_D' ]
2937 senTypes = ['Color' , 'Gyro' , 'Touch' , 'Ultrasonic' , 'Infrared' ]
3038
39+ # Ask the user if the robot is to be used for FLL
3140 disp .text_pixels ("Is this robot for FLL?" )
3241 disp .text_pixels ("Y" , False , 10 , 110 )
3342 disp .text_pixels ("N" , False , 150 , 110 )
3443 disp .update ()
3544 spkr .beep ()
45+ # Wait for a button press (the outer loop is to allow for selection retry if
46+ # an invalid button is pressed)
3647 while True :
3748 while not btn .any ():
3849 pass
50+ # Get the list of currently pressed buttons
3951 selection = btn .buttons_pressed
52+ # Check if the first buton in the list is the left button
4053 if selection [0 ] == 'left' :
54+ # If it is, set the ForFLL value to TRUE and exit the outer loop
4155 btn .wait_for_released ('left' )
4256 config ['Other' ]['ForFLL' ] = 'TRUE'
4357 break
4458 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
4561 btn .wait_for_released ('right' )
4662 config ['Other' ]['ForFLL' ] = 'FALSE'
4763 break
4864 else :
65+ # If both checks fail, play an error tone and go back to waiting for a button
4966 spkr .beep ()
5067 spkr .beep ('-f 380' )
68+ # Clear the display for the next prompt
5169 disp .clear ()
5270
53-
71+ # Auto-detect sensors
5472 for i in senTypes :
5573 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
5677 sensorClass = getattr (lego , i + 'Sensor' )
78+ # Instantiate the current sensor type
5779 mySensor = sensorClass ()
80+ # Get the port that the sensor is connected to
5881 port = str (mySensor .address )
82+ # sensor.adress will return sometheing like ev3:in1, so this replaces anything containing in# with INPUT_#
5983 port = re .sub ('.*in([0-9]).*' , 'INPUT_\g<1>' , port )
84+ # Add port value to the config file
6085 config ['Sensors' ][i + 'Port' ] = port
6186 except :
87+ # If the sensor instantiation failed, none of that kind of sensor are connected, so write the port value as None
6288 config ['Sensors' ][i + 'Port' ] = 'None'
6389 finally :
90+ # Clear the object and class variables as they are reused every loop cycle
6491 mySensor = None
6592 sensorClass = None
6693
94+ # Detect motors
95+ # Repeat test for each port
6796 for i in outPorts :
6897 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.
69100 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.
70102 disp .text_pixels ("Found a Large Motor at " + "port " + i [- 1 ])
71103 disp .text_pixels ("What kind of motor is this?" , False , 0 , 10 )
72104 disp .text_pixels ("Press left for left motor," , False , 0 , 20 )
73105 disp .text_pixels ("right for right motor," , False , 0 , 30 )
74106 disp .text_pixels ("up for Aux1, or down for Aux2" , False , 0 , 40 )
75107 disp .update ()
108+ # Beep to signal that user input is required
76109 spkr .beep ()
110+
111+ # Loop is used to allow for invalid button presses to repeat selection
77112 while True :
113+ # Wait for any button to be pressed
78114 while not btn .any ():
79115 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
82120 btn .wait_for_released ('left' )
121+ # If left, store the current port as the left motor port
83122 config ['Drivebase' ]['LeftMotorPort' ] = i
123+ # Exit the loop, as this is a valid selection
84124 break
85- elif selection [0 ] == 'right' :
125+ elif selection == 'right' :
126+ # Wait for the button to be released, so the operation only occurs once
86127 btn .wait_for_released ('right' )
128+ # If right, store the current port as the right motor port
87129 config ['Drivebase' ]['RightMotorPort' ] = i
130+ # Exit the loop, as this is a valid selection
88131 break
89- elif selection [0 ] == 'up' :
132+ elif selection == 'up' :
133+ # Wait for the button to be released, so the operation only occurs once
90134 btn .wait_for_released ('up' )
135+ # If up, store the current port as the first auxillary motor port
91136 config ['AuxMotors' ]['AuxMotor1' ] = i
137+ # Exit the loop, as this is a valid selection
92138 break
93- elif selection [0 ] == 'down' :
139+ elif selection == 'down' :
140+ # Wait for the button to be released, so the operation only occurs once
94141 btn .wait_for_released ('down' )
142+ # If down, store the current port as the second auxillary motor port
95143 config ['AuxMotors' ]['AuxMotor2' ] = i
144+ # Exit the loop, as this is a valid selection
96145 break
97146 else :
147+ # If the pressed button is not valid, play an error tone and repeat the selection menu
98148 spkr .beep ()
99149 spkr .beep ('-f 380' )
150+
151+ # If a motor is not found, no action is required. Apparently 'try' doesn't work without an 'except'
100152 except :
101153 pass
102154
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.
103157 for i in outPorts :
104158 try :
105159 motor = MediumMotor (eval (i ))
@@ -112,12 +166,12 @@ def configBuilder(configFile='robot.cfg'):
112166 while True :
113167 while not btn .any ():
114168 pass
115- selection = btn .buttons_pressed
116- if selection [ 0 ] == 'left' :
169+ selection = btn .buttons_pressed [ 0 ]
170+ if selection == 'left' :
117171 btn .wait_for_released ('left' )
118172 config ['AuxMotors' ]['AuxMotor1' ] = i
119173 break
120- elif selection [ 0 ] == 'right' :
174+ elif selection == 'right' :
121175 btn .wait_for_released ('right' )
122176 config ['AuxMotors' ]['AuxMotor2' ] = i
123177 break
@@ -127,15 +181,19 @@ def configBuilder(configFile='robot.cfg'):
127181 except :
128182 pass
129183
184+ # Create a MoveTank object with the detected drive motors, as it is needed for the upcoming tests.
130185 mtrs = MoveTank (eval (config ['Drivebase' ]['LeftMotorPort' ]), eval (config ['Drivebase' ]['RightMotorPort' ]))
131186
187+ # A Foward-Facing Ultrasonic sensor is the only way to allow for calculating the wheel circumfrence.
132188 if config ['Sensors' ]['UltrasonicPort' ] is not 'None' :
189+ # Ask the user if the detected Ultrasonic sensor is facing fowards
133190 us = UltrasonicSensor (eval (config ['Sensors' ]['UltrasonicPort' ]))
134191 disp .text_pixels ("Does the Ultrasonic sensor" )
135192 disp .text_pixels ("face foward?" , False , 0 , 10 )
136193 disp .text_pixels ("Y" , False , 10 , 110 )
137194 disp .text_pixels ("N" , False , 150 , 110 )
138195 disp .update ()
196+ # Same selection system as before
139197 while not btn .any ():
140198 pass
141199 selection = btn .buttons_pressed [0 ]
@@ -150,39 +208,54 @@ def configBuilder(configFile='robot.cfg'):
150208 usNav = False
151209 us = None
152210
211+ # Using the variable usNav set previously, check if the Ultrasonic sensor faces fowards.
153212 if usNav :
213+ # Ask the user to place the robot facing a wall, then wait for a button press.
154214 disp .text_pixels ("Place the robot facing a wall, " )
155215 disp .text_pixels ("and press any button." , False , 0 , 10 )
156216 disp .update ()
157217 while not btn .any ():
158218 pass
219+ # Set up some variables for averaging
159220 circsum = 0
160221 sign = 1
161222 tests = 3
223+ # Repeat the wheelcircumfrence test multiple times to get a more accurate average
162224 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
163227 if j / 2 == round (j / 2 ):
164228 parity = 1
165229 else :
166230 parity = - 1
231+ # More variables for averaging
167232 usSum = 0
168233 readings = 5
234+ # Take multiple sensor readings of how far away from the wall the robot is, and average them
169235 for i in range (readings ):
170236 usSum += us .distance_centimeters
171237 sleep (0.1 )
172238 stavg = round (usSum / readings , 2 )
239+ # Reset averaging variable
173240 usSum = 0
174- print ( stavg , file = stderr )
241+ # Move the robot one wheel rotation, foward on even loop cycles, or backward on odd
175242 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
176244 for i in range (readings ):
177245 usSum += us .distance_centimeters
178246 sleep (0.1 )
179247 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.
181250 circsum += abs (enavg - stavg )
251+ # Isolate the sign (-x or x to -1 or 1) of the wheel circumfrence (Used for MotorsInverted)
182252 sign = (enavg - stavg ) / abs (enavg - stavg )
253+ # Calculate the average wheel circumfrence
183254 avg = round (circsum / tests , 2 )
255+ # Write to config file variable
184256 config ['Drivebase' ]['WheelCircumfrence' ] = str (avg )
185257
258+ # Set MotorsInverted in the config file, and set the 'polarity' variable (for inverting motor commands if necessary)
186259 polarity = 'normal'
187260 if sign < 0 :
188261 config ['Drivebase' ]['MotorsInverted' ] = 'FALSE'
@@ -191,31 +264,49 @@ def configBuilder(configFile='robot.cfg'):
191264 config ['Drivebase' ]['MotorsInverted' ] = 'TRUE'
192265 polarity = 'inversed'
193266
267+ # Invert motor commands if necessary
194268 mtrs .set_polarity (polarity )
269+ # Ask the user to place the robot in a clear area, and wait for a button press
195270 disp .text_pixels ("Place robot in clear area," )
196271 disp .text_pixels ("and press any button." , False , 0 , 10 )
197272 disp .update ()
198273 while not btn .any ():
199274 pass
275+ # Instantiate a Gyro Sensor object as the degrees turned is necessary for calculating the Width Between Wheels
200276 gs = GyroSensor (eval (config ['Sensors' ]['GyroPort' ]))
277+ # Create variables for averaging
201278 widthbetweenwheelssum = 0
202279 ang = 0
280+ # Repeat the trial multiple times and average the results
203281 for i in range (tests ):
282+ # Reset the reported gyro angle to zero
204283 gs ._ensure_mode (gs .MODE_GYRO_RATE )
205284 gs ._ensure_mode (gs .MODE_GYRO_ANG )
285+ # Turn in place for one wheel rotation
206286 mtrs .on_for_degrees (25 , - 25 , 360 )
287+ # Wait for the robot to settle
207288 sleep (0.5 )
289+ # Read the current angle of the robot
208290 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.
209293 wbw = abs ((360 * float (config ['Drivebase' ]['WheelCircumfrence' ])) / (ang * math .pi ))
294+ # Add the calculated value to the running total (used for averaging)
210295 widthbetweenwheelssum += wbw
296+ # Calculate the average WBW value, round it to remove floating point errors, and store in the ConfigParser object
211297 config ['Drivebase' ]['WidthBetweenWheels' ] = str (round (widthbetweenwheelssum / tests , 2 ))
212298
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.
213301 if ang > 0 :
214302 config ['Drivebase' ]['GyroInverted' ] = 'FALSE'
215303 elif ang < 0 :
216304 config ['Drivebase' ]['GyroInverted' ] = 'TRUE'
217305
218306 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.
219310 spkr .beep ()
220311 spkr .beep ()
221312 config ['Drivebase' ]['WheelCircumfrence' ] = ''
@@ -230,6 +321,9 @@ def configBuilder(configFile='robot.cfg'):
230321 while not btn .any ():
231322 pass
232323
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.
233327 config ['Drivebase' ]['GearRatio' ] = '1'
234328 config ['Drivebase' ]['kp' ] = '1'
235329 config ['Drivebase' ]['ki' ] = '0'
@@ -238,6 +332,7 @@ def configBuilder(configFile='robot.cfg'):
238332 config ['Sensors' ]['kiLine' ] = '0'
239333 config ['Sensors' ]['kdLine' ] = '2'
240334
335+ # Tell the user that the PID values may (will) need to be tuned.
241336 spkr .beep ()
242337 spkr .beep ()
243338 disp .text_pixels ("The PID gains in the config" )
@@ -248,6 +343,7 @@ def configBuilder(configFile='robot.cfg'):
248343 while not btn .any ():
249344 pass
250345
346+ # Write all the values stored in the ConfigParser object named config to a file in INI format, with the name passed by configFile.
251347 with open (configFile , 'w' ) as configfile :
252348 config .write (configfile )
253349
0 commit comments