aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaspberry Pi <raspberrypi@umn.edu>2019-11-26 19:31:10 -0600
committerRaspberry Pi <raspberrypi@umn.edu>2019-11-26 19:31:10 -0600
commit83823aebece092a845bf1b9eae44175f91f7115d (patch)
tree5de439098f7287d7e9c3f466dd33385abcf137cd
parentModify test_System to verify that threaded encoder measurements are working a... (diff)
downloadee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar.gz
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar.bz2
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar.lz
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar.xz
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.tar.zst
ee4511w-83823aebece092a845bf1b9eae44175f91f7115d.zip
Got threading and SW limits working really well. Still might be some shakiness on deinitialization (need to coordinate GPIO.cleanup with threads), but program does exit and return home normally. Swingup test now runs very smooth and SW limits were increased with the drastically improved response time. Pushing to merge back to master.
-rw-r--r--System/system.py122
-rw-r--r--System/system_swingup_test_2.py15
2 files changed, 77 insertions, 60 deletions
diff --git a/System/system.py b/System/system.py
index c06063a..63d0ecc 100644
--- a/System/system.py
+++ b/System/system.py
@@ -7,7 +7,7 @@ from time import sleep
import RPi.GPIO as GPIO
import sys
import os
-from threading import Thread
+from threading import Thread, Lock
# IO pin definitions
### Motor pins
@@ -32,8 +32,9 @@ system_min_x = -16.5
# System Class
# This is the primary interface a student will use to control the pendulum.
class System:
- def __init__(self, negative_limit=float('nan'), positive_limit=float('nan'), angular_units='Degrees', sw_limit_routine=self.limit_triggered):
+ def __init__(self, negative_limit=float('nan'), positive_limit=float('nan'), angular_units='Degrees', sw_limit_routine=None):
GPIO.setwarnings(False)
+ self.deinit = False
# Initialize public variables
self.max_x = system_max_x
self.min_x = system_min_x
@@ -47,11 +48,7 @@ class System:
# We assume that the system has been initialized on startup to a 0 position, or that the previous run ended by returning the system to 0
self.encoder_linear.set_zero()
- # Setup a thread to constantly be measuring encoder positions
- self.encoder_thread = Thread(target = self.encoder_thread_routine)
- self.angular_position = 0.
- self.linear_position = 0.
- self.encoder_thread.start()
+ self.angular_units = angular_units
# Enable hardware interrupts for hardware limit switches
GPIO.setup(limit_negative_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
@@ -71,9 +68,9 @@ class System:
self.positive_soft_limit = float('nan')
# NOTE: If only one limit has been defined, this should always work (hardware limits will be the absolute edge on the undefined side, although this would be difficult for users to utilize unless we provide the position of the hardware limits on each side
# NOTE: If neither limit is defined, the hardware limits will be the only limits in effect.
- self.sw_limit_routine = sw_limit_routine
-
- self.angular_units = angular_units
+ self.sw_limit_routine = self.limit_triggered
+ if sw_limit_routine is not None:
+ self.sw_limit_routine = sw_limit_routine
# Create and setup results file (to be sent back to the server and displayed/downloaded to the user)
# Results file is a CSV with the following entries: angle, position, speed
@@ -82,6 +79,14 @@ class System:
result_file = open(self.result_filename, "w+")
result_file.write("angle(" + angular_units + "),position(inches),speed(percentage)\n")
result_file.close()
+
+ # Setup a thread to constantly be measuring encoder positions
+ #self.encoder_thread = EncoderThread(instance = self)
+ self.encoder_thread = Thread(target = self.encoder_thread_routine)
+ self.encoder_thread.setDaemon(True)
+ self.angular_position = 0.
+ self.linear_position = 0.
+ self.encoder_thread.start()
# END __init__()
def initialize(self):
@@ -90,7 +95,7 @@ class System:
GPIO.remove_event_detect(limit_positive_pin)
# Begin moving slowly in the negative direction until the negative limit switch is triggered
if not GPIO.input(limit_negative_pin) == False:
- self.motor.move(-4)
+ self.motor.move(-5)
pressed = True
while pressed != False:
pressed = GPIO.input(limit_negative_pin)
@@ -100,24 +105,22 @@ class System:
self.encoder_linear.set_zero()
sleep(1)
# Begin moving slowly in the positive direction until the positive limit switch is triggered
- self.motor.move(4)
+ self.motor.move(5)
pressed = True
while pressed != False:
# We must continue reading linear encoder motion to keep track of rotations
- position = self.encoder_linear.read_position()
pressed = GPIO.input(limit_positive_pin)
sleep(0.01)
- #GPIO.wait_for_edge(limit_positive_pin, GPIO.FALLING)
self.motor.brake()
# Get the current position (the extent of the track)
- extent = self.encoder_linear.read_position()
+ extent = self.linear_position
# Move back towards the center until we reach position extent/2
position = extent
sleep(1)
self.motor.move(-4)
while position >= (extent / 2.):
- position = self.encoder_linear.read_position()
- sleep(0.01)
+ position = self.linear_position
+ sleep(0.015)
self.motor.brake()
# Set zero again: this is the real zero
self.encoder_linear.set_zero()
@@ -130,7 +133,10 @@ class System:
def deinitialize(self):
self.return_home()
self.motor.brake()
- self.encoder_thread.terminate()
+ self.deinit = True
+ if self.encoder_thread.isAlive():
+ self.encoder_thread.join()
+ sleep(1)
GPIO.cleanup()
# Get the values of the encoders to determine the angular and linear position of the pendulum.
@@ -146,40 +152,48 @@ class System:
return (self.angular_position, self.linear_position)
# END measure()
+
# Thread routine (0.1s interval). Get the values of the encoders to determine the angular and linear position of the pendulum.
# Values are saved in the class (self.angular_position and self.linear_position), which are then simply returned by measure()
def encoder_thread_routine(self):
- angular_position = self.encoder_angular.read_position(self.angular_units)
- if self.angular_units == 'Degrees':
- if angular_position > 180.:
- angular_position = angular_position - 360.
- self.angular_position = angular_position
- self.linear_position = self.encoder_linear.read_position()
- # Check soft limits
- if (not math.isnan(self.negative_soft_limit) and self.linear_position < self.negative_soft_limit) or self.linear_position < self.min_x:
- # SW limit reached: stop the motor, set the interrupted flag so that the motor cannot continue to move until the interrupt has been completely serviced
- #self.interrupted = True
- self.motor.brake()
- # Print negative soft limit violation to the results file.
- result_file = open(self.result_filename, "a")
- result_file.write("Negative software limit %f has been reached!" % self.negative_soft_limit)
- result_file.close()
- # Fire the limit trigger method (stops motor, kills program immediately).
- self.sw_limit_routine()
- if (not math.isnan(self.positive_soft_limit) and self.linear_position > self.positive_soft_limit) or self.linear_position > self.max_x:
- # SW limit reached: stop the motor, set the interrupted flag so that the motor cannot continue to move until the interrupt has been completely serviced
- #self.interrupted = True
- self.motor.brake()
- # Print positive soft limit violation to the results file.
- result_file = open(self.result_filename, "a")
- result_file.write("Positive software limit %f has been reached!" % self.positive_soft_limit)
- result_file.close()
- # Fire the limit trigger method (stops motor, kills program immediately).
- self.sw_limit_routine()
- # If this point is reached, the SW limit routine was user-defined and did not kill the program - un-set the 'interrupted' flag
- #self.interrupted = False
- # This thread should run on ~0.1s intervals
- sleep(0.1)
+ limit_serviced = False
+ while self.deinit == False:
+ angular_position = self.encoder_angular.read_position(self.angular_units)
+ if self.angular_units == 'Degrees':
+ if angular_position > 180.:
+ angular_position = angular_position - 360.
+ self.angular_position = angular_position
+ self.linear_position = self.encoder_linear.read_position()
+ # Check soft limits
+ if (not math.isnan(self.negative_soft_limit)) and self.linear_position < self.negative_soft_limit: #or self.linear_position < self.min_x:
+ if limit_serviced == False:
+ limit_serviced = True
+ # SW limit reached: stop the motor, set the interrupted flag so that the motor cannot continue to move until the interrupt has been completely serviced
+ #self.interrupted = True
+ self.motor.brake()
+ # Print negative soft limit violation to the results file.
+ result_file = open(self.result_filename, "a")
+ result_file.write("Negative software limit %f has been reached!" % self.negative_soft_limit)
+ result_file.close()
+ # Fire the limit trigger method
+ self.sw_limit_routine()
+ elif (not math.isnan(self.positive_soft_limit)) and self.linear_position > self.positive_soft_limit: #or self.linear_position > self.max_x:
+ if limit_serviced == False:
+ limit_serviced = True
+ # SW limit reached: stop the motor, set the interrupted flag so that the motor cannot continue to move until the interrupt has been completely serviced
+ #self.interrupted = True
+ self.motor.brake()
+ # Print positive soft limit violation to the results file.
+ result_file = open(self.result_filename, "a")
+ result_file.write("Positive software limit %f has been reached!" % self.positive_soft_limit)
+ result_file.close()
+ # Fire the limit trigger method
+ self.sw_limit_routine()
+ elif limit_serviced == True and self.linear_position > (self.negative_soft_limit+0.5) and self.linear_position < (self.positive_soft_limit-0.5):
+ # Clear the limit service flag once we return to a reasonable range that the limit will not trigger again
+ limit_serviced = False
+ # This thread should run on ~0.01s intervals
+ sleep(0.01)
# Adjust the pendulum's linear position using the motor.
### speed: Acceptable values range from -100 to 100 (as a percentage), with 100/-100 being the maximum adjustment speed.
@@ -215,20 +229,20 @@ class System:
# Go back to the zero position (linear) so that the next execution starts in the correct place.
def return_home(self):
- position = self.encoder_linear.read_position()
+ position = self.linear_position
# slowly move towards 0 until we get there
if position > 0:
self.motor.move(-4)
while position > 0:
- position = self.encoder_linear.read_position()
- sleep(0.01)
+ position = self.linear_position
+ sleep(0.015)
self.motor.brake()
return
else:
self.motor.move(4)
while position < 0:
- position = self.encoder_linear.read_position()
- sleep(0.01)
+ position = self.linear_position
+ sleep(0.015)
self.motor.brake()
return
# END return_home
diff --git a/System/system_swingup_test_2.py b/System/system_swingup_test_2.py
index e240b01..5c0ac5a 100644
--- a/System/system_swingup_test_2.py
+++ b/System/system_swingup_test_2.py
@@ -53,13 +53,13 @@ class SwingUpEnv():
}
def __init__(self):
- self.sys = System(angular_units='Radians', positive_limit=10., negative_limit=-10., sw_limit_routine=self.x_threshold_routine)
+ self.x_threshold = 14.
+ self.sys = System(angular_units='Radians', positive_limit=self.x_threshold, negative_limit=-self.x_threshold, sw_limit_routine=self.x_threshold_routine)
- self.force_mag = 10.
+ self.force_mag = 11.
self.last_time = time.time() # time for seconds between updates
# Angle at which to fail the episode
- self.x_threshold = 10.
self.x_dot_threshold = 10.
self.theta_dot_threshold = 3*np.pi
@@ -134,6 +134,7 @@ class SwingUpEnv():
return np.array(self.state), reward, done, {'max_up_time' : self.max_up_time}
def x_threshold_routine(self):
+ print("SW Limit reached!")
self.done = True
self.sys.adjust(0)
@@ -311,14 +312,16 @@ class sarsaAgent:
env = SwingUpEnv()
# For simplicity, we only consider forces of -1 and 1
-numActions = 5
+numActions = 2
Actions = np.linspace(-1, 1, numActions)
# This is our learning agent
gamma = .95
-agent = sarsaAgent(5, numActions, 20, 1, epsilon = 5e-2, gamma = gamma, alpha = 1e-5)
+#agent = sarsaAgent(5, numActions, 20, 1, epsilon = 5e-2, gamma = gamma, alpha = 1e-5)
+agent = deepQagent(5,numActions,20,2,epsilon=5e-2,gamma=gamma,batch_size=20,
+ c= 100,alpha=1e-4)
-maxSteps = 5e4
+maxSteps = 2e5
# This is a helper to deal with the fact that x[2] is actually an angle
x_to_y = lambda x : np.array([x[0], x[1], np.cos(x[2]), np.sin(x[2]), x[3]])