Exemple #1
0
    def __init__(self):
        rospy.Subscriber("/Indoor_Bldddimp/setpoint_position", PoseStamped,
                         self.process_setpoint)
        rospy.Subscriber("/imu/data", Imu, self.process_current)
        self.blimp = Blimp()
        self.set_yaw = 90
        self.set_trans_x = 0
        self.set_trans_z = 0

        self.curr_yaw = 0
        self.curr_trans_x = 0
        self.curr_trans_z = 0

        yaw_kp = 0.8  #rospy.get_param("~yaw_kp")
        yaw_ki = 0.8  #rospy.get_param("~yaw_ki")
        yaw_kd = 0.8  #rospy.get_param("~yaw_kd")

        self.yaw_control = PID_Controller(yaw_kp, yaw_ki, yaw_kd)
Exemple #2
0
    def _initialize_pid_controlers(self):
        '''
        Initialize communication with the pid controllers.

        Parameters:
            N/A
        Returns:
            N/A
        '''

        pid_params = rospy.get_param("/pid")
        velocity_pid_params = pid_params['velocity']
        steering_pid_params = pid_params['steering']

        velocity_k_p = velocity_pid_params['k_p']
        velocity_k_i = velocity_pid_params['k_i']
        velocity_k_d = velocity_pid_params['k_d']

        self.velocity_pid_controller = PID_Controller(k_p=velocity_k_p,
                                                      k_i=velocity_k_i,
                                                      k_d=velocity_k_d,
                                                      integral_min=-10,
                                                      integral_max=10,
                                                      max_control_effort=255,
                                                      min_control_effort=-255)

        steering_k_p = steering_pid_params['k_p']
        steering_k_i = steering_pid_params['k_i']
        steering_k_d = steering_pid_params['k_d']
        self.steering_pid_controller = PID_Controller(k_p=steering_k_p,
                                                      k_i=steering_k_i,
                                                      k_d=steering_k_d,
                                                      max_control_effort=255,
                                                      min_control_effort=-255,
                                                      integral_min=-10,
                                                      integral_max=10,
                                                      angle_error=True)

        #Initialize the service for updating the pid controller gains
        rospy.Service('update_pid_gains', pid_gains,
                      self.handle_pid_gains_update)

        return
Exemple #3
0
class Yaw_control:
    def __init__(self):
        rospy.Subscriber("/Indoor_Bldddimp/setpoint_position", PoseStamped,
                         self.process_setpoint)
        rospy.Subscriber("/imu/data", Imu, self.process_current)
        self.blimp = Blimp()
        self.set_yaw = 90
        self.set_trans_x = 0
        self.set_trans_z = 0

        self.curr_yaw = 0
        self.curr_trans_x = 0
        self.curr_trans_z = 0

        yaw_kp = 0.8  #rospy.get_param("~yaw_kp")
        yaw_ki = 0.8  #rospy.get_param("~yaw_ki")
        yaw_kd = 0.8  #rospy.get_param("~yaw_kd")

        self.yaw_control = PID_Controller(yaw_kp, yaw_ki, yaw_kd)

    def control_loop(self):

        print("Set Yaw Angle: " + repr(self.set_yaw))
        print("Current Yaw Angle: " + repr(self.curr_yaw))
        mv = self.yaw_control.corrective_action(self.set_yaw, self.curr_yaw)
        print("corrected speed")
        print(mv)

        self.blimp.motors.turn(mv)

    def pan_tilt(self):
        self.pan_tilt.pitch(10)
        self.pan_tilt.roll(6)

    def process_setpoint(self, poseStamped):

        pose = poseStamped.pose
        quart = pose.orientation
        trans = pose.position
        exp_quart = [quart.x, quart.y, quart.z, quart.w]
        r, p, y = euler_from_quaternion(exp_quart)

        self.set_yaw = y

    def process_current(self, imu_data):

        self.curr_yaw = imu_data.orientation.x
    def __init__(self):

        rospy.Subscriber("key", String, self.velocity_set)

        rospy.Subscriber("/ultrasonic", Float64, self.obstacle_control)

        self.blimp = Blimp()
        self.set_yaw = 90
        self.set_trans_x = 0
        self.set_trans_z = 0

        self.curr_yaw = 0
        self.curr_trans_x = 0
        self.curr_trans_z = 0

        yaw_kp = 0.8  #rospy.get_param("~yaw_kp")
        yaw_ki = 0.8  #rospy.get_param("~yaw_ki")
        yaw_kd = 0.8  #rospy.get_param("~yaw_kd")

        self.yaw_control = PID_Controller(yaw_kp, yaw_ki, yaw_kd)
    def __init__(self):
        rospy.Subscriber("/Outdoor_Blimp/setpoint_position", PoseStamped,
                         self.process_setpoint)
        rospy.Subscriber("/Outdoor_Blimp/imu/orientation", Vector3,
                         self.process_current)
        self.blimp = Blimp()
        self.set_yaw = 0
        self.set_pitch = 0
        self.set_roll = 0
        self.set_trans_x = 0
        self.set_trans_z = 0
        self.curr_pitch = 0
        self.curr_roll = 0
        self.curr_yaw = 0
        self.curr_trans_x = 0
        self.curr_trans_z = 0

        yaw_kp = rospy.get_param("~yaw_kp")
        yaw_ki = rospy.get_param("~yaw_ki")
        yaw_kd = rospy.get_param("~yaw_kd")

        trans_x_kp = rospy.get_param("~trans_x_kp")
        trans_x_ki = rospy.get_param("~trans_x_ki")
        trans_x_kd = rospy.get_param("~trans_x_kd")

        trans_z_kp = rospy.get_param("~trans_z_kp")
        trans_z_ki = rospy.get_param("~trans_z_ki")
        trans_z_kd = rospy.get_param("~trans_z_kd")

        self.curr_roll = 0

        self.yaw_control = PID_Controller(yaw_kp, yaw_ki, yaw_kd)

        self.trans_x_control = PID_Controller(trans_x_kp, trans_x_ki,
                                              trans_x_kd)

        self.trans_z_control = PID_Controller(trans_z_kp, trans_z_ki,
                                              trans_z_kd)

        self.servos = pigpio.pi()
    def run_sim(self):
        dt = .02
        self.manage_inputs()
        self.mass = MassModel(self, self.initial_mass_state, dt)

        if self.MODE == 'pid':
            controller = PID_Controller(self, dt)
        else:
            controller = BB_Controller(self, dt)

        ref = self.ref  #meters
        self.draw_reference(ref)

        xt = [self.mass.X[0], self.mass.X[0]]
        tt = [-1, 0]
        rt = [self.initial_mass_state[0], self.initial_mass_state[0]]
        ut = [0, 0]

        t_end = self.TIME_RUN  #seconds

        count = 0

        while self.mass.t < t_end:

            count += 1
            if (count % 10) == 0:
                print "time: %.2f" % self.mass.t

            t0 = datetime.datetime.now()

            tt.append(self.mass.t)
            xt.append(self.mass.X[0])
            ut.append(sum(controller.control_output))
            rt.append(ref)

            gui.update_mass(self.mass.X)
            controller.run(self.mass.X, ref)
            control = controller.control_output
            self.draw_control_effort(control)

            self.mass.step_time_forward(sum(control))
            self.master.update_idletasks()
            self.master.update()

            t1 = datetime.datetime.now()
            dt_actual = (t1 - t0).total_seconds()
            if dt_actual < dt:
                time.sleep(dt - dt_actual)
            else:
                time.sleep(.005)

        self.step_info(np.array(tt), np.array(xt), ref)

        line1, = plt.plot(tt, xt, label='Block Location X(t)')
        line2, = plt.plot(tt, rt, 'g--', label='Reference Command R(t)')
        plt.ylabel('x(t) (meters)')
        plt.xlabel('Time (seconds)')
        #plt.axis([-1, t_end, -.5, 1.5])
        # plt.legend(handles=[line1,line2])
        plt.legend([line1, line2], loc='best')
        plt.show()
Exemple #7
0
dt = time[1] - time[0]  # delta t, (sec)

##################################################################################
# Core simulation code
# Inital conditions (i.e., initial state vector)
y = [0, 0]
#y[0] = initial altitude, (m)
#y[1] = initial speed, (m/s)

# Initialize array to store values
soln = np.zeros((len(time), len(y)))

# Create instance of PID_Controller class and initalize all the variables
pid = PID_Controller(kp=kp,
                     ki=ki,
                     kd=kd,
                     max_windup=1e6,
                     u_bounds=[0, umax],
                     alpha=alpha)

# Set altitude target
r = 10  # meters
pid.setTarget(r)

# Simulate quadrotor motion
j = 0  # dummy counter
for t in time:
    # Evaluate state at next time point
    y = ydot(y, t, pid)
    # Store results
    soln[j, :] = y
    j += 1
    def set_up_PID_controllers(self):
        '''
        Setup the PID controllers with the initial values set in the subs
        parameter xml server file.

        Parameters:
            N/A

        Returns:
            N/A
        '''
        d_t = float(self.param_serv.get_param("Control/PID/dt"))
        roll_p = float(self.param_serv.get_param("Control/PID/roll_pid/p"))
        roll_i = float(self.param_serv.get_param("Control/PID/roll_pid/i"))
        roll_d = float(self.param_serv.get_param("Control/PID/roll_pid/d"))
        self.roll_pid_controller = PID_Controller(roll_p, roll_i, roll_d, d_t)

        pitch_p = float(self.param_serv.get_param("Control/PID/pitch_pid/p"))
        pitch_i = float(self.param_serv.get_param("Control/PID/pitch_pid/i"))
        pitch_d = float(self.param_serv.get_param("Control/PID/pitch_pid/d"))
        self.pitch_pid_controller = PID_Controller(pitch_p, pitch_i, pitch_d,
                                                   d_t)

        yaw_p = float(self.param_serv.get_param("Control/PID/yaw_pid/p"))
        yaw_i = float(self.param_serv.get_param("Control/PID/yaw_pid/i"))
        yaw_d = float(self.param_serv.get_param("Control/PID/yaw_pid/d"))
        self.yaw_pid_controller = PID_Controller(yaw_p, yaw_i, yaw_d, d_t)

        x_p = float(self.param_serv.get_param("Control/PID/x_pid/p"))
        x_i = float(self.param_serv.get_param("Control/PID/x_pid/i"))
        x_d = float(self.param_serv.get_param("Control/PID/x_pid/d"))
        self.x_pid_controller = PID_Controller(x_p, x_i, x_d, d_t)

        y_p = float(self.param_serv.get_param("Control/PID/y_pid/p"))
        y_i = float(self.param_serv.get_param("Control/PID/y_pid/i"))
        y_d = float(self.param_serv.get_param("Control/PID/y_pid/d"))
        self.y_pid_controller = PID_Controller(y_p, y_i, y_d, d_t)

        #z = depth pid
        z_p = float(self.param_serv.get_param("Control/PID/z_pid/p"))
        z_i = float(self.param_serv.get_param("Control/PID/z_pid/i"))
        z_d = float(self.param_serv.get_param("Control/PID/z_pid/d"))
        self.z_pid_controller = PID_Controller(z_p, z_i, z_d, d_t)
class Movement_PID:
    '''
    A movement controller for Perseverance that relies on PID controller to control
    the 6 degrees of freedom.
    '''
    def __init__(self):
        '''
        Initialize the thrusters and PID controllers on Perseverance.

        Parameters:
            error_publisher: A MechOS publisher to send the publish the current error

        Returns:
            N/A
        '''
        #Get the mechos network parameters
        configs = MechOS_Network_Configs(
            MECHOS_CONFIG_FILE_PATH)._get_network_parameters()

        #Initialize parameter server client to get and set parameters related to
        #the PID controller. This includes update time and PID contstants for
        #each degree of freedom.
        self.param_serv = mechos.Parameter_Server_Client(
            configs["param_ip"], configs["param_port"])
        self.param_serv.use_parameter_database(configs["param_server_path"])

        #Initialize serial connection to the maestro
        com_port = self.param_serv.get_param("COM_Ports/maestro")
        maestro_serial_obj = serial.Serial(com_port, 9600)

        #Initialize all 8 thrusters (max thrust 80%)
        max_thrust = float(self.param_serv.get_param("Control/max_thrust"))

        self.thrusters = [None, None, None, None, None, None, None, None]
        self.thrusters[0] = Thruster(maestro_serial_obj, 1, [0, 0, 1],
                                     [1, -1, 0], max_thrust, True)
        self.thrusters[1] = Thruster(maestro_serial_obj, 2, [0, 1, 0],
                                     [1, 0, 0], max_thrust, False)
        self.thrusters[2] = Thruster(maestro_serial_obj, 3, [0, 0, 1],
                                     [1, 1, 0], max_thrust, False)
        self.thrusters[3] = Thruster(maestro_serial_obj, 4, [1, 0, 0],
                                     [0, 1, 0], max_thrust, False)
        self.thrusters[4] = Thruster(maestro_serial_obj, 5, [0, 0, 1],
                                     [-1, 1, 0], max_thrust, True)
        self.thrusters[5] = Thruster(maestro_serial_obj, 6, [0, 1, 0],
                                     [-1, 0, 0], max_thrust, False)
        self.thrusters[6] = Thruster(maestro_serial_obj, 7, [0, 0, 1],
                                     [-1, -1, 0], max_thrust, False)
        self.thrusters[7] = Thruster(maestro_serial_obj, 8, [1, 0, 0],
                                     [0, -1, 0], max_thrust, False)

        #Initialize the PID controllers for control system
        self.set_up_PID_controllers()

    def set_up_PID_controllers(self):
        '''
        Setup the PID controllers with the initial values set in the subs
        parameter xml server file.

        Parameters:
            N/A

        Returns:
            N/A
        '''
        d_t = float(self.param_serv.get_param("Control/PID/dt"))
        roll_p = float(self.param_serv.get_param("Control/PID/roll_pid/p"))
        roll_i = float(self.param_serv.get_param("Control/PID/roll_pid/i"))
        roll_d = float(self.param_serv.get_param("Control/PID/roll_pid/d"))
        self.roll_pid_controller = PID_Controller(roll_p, roll_i, roll_d, d_t)

        pitch_p = float(self.param_serv.get_param("Control/PID/pitch_pid/p"))
        pitch_i = float(self.param_serv.get_param("Control/PID/pitch_pid/i"))
        pitch_d = float(self.param_serv.get_param("Control/PID/pitch_pid/d"))
        self.pitch_pid_controller = PID_Controller(pitch_p, pitch_i, pitch_d,
                                                   d_t)

        yaw_p = float(self.param_serv.get_param("Control/PID/yaw_pid/p"))
        yaw_i = float(self.param_serv.get_param("Control/PID/yaw_pid/i"))
        yaw_d = float(self.param_serv.get_param("Control/PID/yaw_pid/d"))
        self.yaw_pid_controller = PID_Controller(yaw_p, yaw_i, yaw_d, d_t)

        x_p = float(self.param_serv.get_param("Control/PID/x_pid/p"))
        x_i = float(self.param_serv.get_param("Control/PID/x_pid/i"))
        x_d = float(self.param_serv.get_param("Control/PID/x_pid/d"))
        self.x_pid_controller = PID_Controller(x_p, x_i, x_d, d_t)

        y_p = float(self.param_serv.get_param("Control/PID/y_pid/p"))
        y_i = float(self.param_serv.get_param("Control/PID/y_pid/i"))
        y_d = float(self.param_serv.get_param("Control/PID/y_pid/d"))
        self.y_pid_controller = PID_Controller(y_p, y_i, y_d, d_t)

        #z = depth pid
        z_p = float(self.param_serv.get_param("Control/PID/z_pid/p"))
        z_i = float(self.param_serv.get_param("Control/PID/z_pid/i"))
        z_d = float(self.param_serv.get_param("Control/PID/z_pid/d"))
        self.z_pid_controller = PID_Controller(z_p, z_i, z_d, d_t)

    def simple_thrust(self, thrusts):
        '''
        Individually sets each thruster PWM given the PWMs in the thrusts
        list.

        Parameters:
            thrusts: A list of length 8 containing a thrust value (%) between
            [-100, 100]. The list should be in order of the thruster ids.

        Returns:
            N/A
        '''

        for thruster_id, thrust in enumerate(thrusts):
            self.thrusters[thruster_id].set_thrust(thrust)

    def controlled_thrust(self, roll_control, pitch_control, yaw_control,
                          x_control, y_control, z_control):
        '''
        Function takes control outputs from calculated by the PID values and applys
        them to the 6 degree control matrix to determine the thrust for each thruster
        to reach desired point.

        Parameters:
            roll_control: The control output from the roll pid controller.
            pitch_control: The control output from the pitch pid controller.
            yaw_control: The control output from the yaw pid controller.
            x_control: The control output from the x translation pid controller.
            y_control: The control output from the y translation pid controller.
            z_control: The control output from the z translation pid controller.

        Returns:
            N/A
        '''
        for thruster_id, thruster in enumerate(self.thrusters):

            thrust = (roll_control * thruster.orientation[2] * thruster.location[1]) + \
                     (pitch_control * thruster.orientation[2] * thruster.location[0]) + \
                     (yaw_control * thruster.orientation[1] * thruster.location[0]) + \
                     (yaw_control * thruster.orientation[0] * thruster.location[1]) + \
                     (x_control * thruster.orientation[0]) + \
                     (y_control * thruster.orientation[1]) + \
                     (z_control * thruster.orientation[2])
            self.thrusters[thruster_id].set_thrust(thrust)

    def advance_move(self, curr_roll, curr_pitch, curr_yaw, curr_x_pos,
                     curr_y_pos, curr_z_pos, desired_roll, desired_pitch,
                     desired_yaw, desired_x_pos, desired_y_pos, desired_z_pos):
        '''
        Given the current position and desired positions of the AUV, obtain the pid
        control outputs for all 6 degrees of freedom.

        Parameters:
            curr_roll: Current roll data
            curr_pitch Current pitch data
            curr_yaw: Current yaw data
            curr_x_pos: Current x position
            curr_y_pos: Current y position
            curr_z_pos: Current z position
            desired_roll: Desired roll position
            desired_pitch: Desired pitch position
            desired_yaw: Desired yaw position
            desired_x_pos: Desired x position
            desired_y_pos: Desired y position
            desired_z_pos: Desired z position

        Returns:
            N/A
        '''

        #calculate the error of each degree of freedom
        error = [0, 0, 0, 0, 0, 0]

        error[0] = desired_roll - curr_roll  #roll error
        error[1] = desired_pitch - curr_pitch  #pitch error

        #Calculate yaw error. The logic includes calculating error for choosing shortest angle to travel
        if (desired_yaw >= curr_yaw):
            yaw_error = desired_yaw - curr_yaw
            if (
                    yaw_error > 180
            ):  #This will choose the shortest angle to take to desred position.
                error[2] = yaw_error - 360
            else:
                error[2] = yaw_error

        else:
            yaw_error = curr_yaw - desired_yaw
            if (abs(yaw_error) > 180):
                error[2] = yaw_error + 360
            else:
                error[2] = yaw_error

        #Calculate translation error
        error[3] = desired_z_pos - curr_z_pos
        error[4] = desired_x_pos - curr_x_pos
        error[5] = desired_y_pos - curr_y_pos

        roll_control = self.roll_pid_controller.control_step(error[0])
        pitch_control = self.pitch_pid_controller.control_step(error[1])
        yaw_control = self.yaw_pid_controller.control_step(error[2])
        z_control = self.z_pid_controller.control_step(error[3])
        x_control = self.x_pid_controller.control_step(error[4])
        z_control = self.z_pid_controller.control_step(error[5])

        #Write the contrls to thrusters
        self.controlled_thrust(roll_control, pitch_control, yaw_control,
                               x_control, y_control, z_control)
        return error

    def simple_depth_move_no_yaw(self, curr_roll, curr_pitch, curr_z_pos,
                                 desired_roll, desired_pitch, desired_z_pos):
        '''
        Without carrying about x axis position, y axis position, or yaw, use the PID controllers
        to go down to a give depth (desired_z_pos) with a give orientation.

        Parameters:
            curr_roll: Current roll data
            curr_pitch Current pitch data
            curr_z_pos: Current z position
            desired_roll: Desired roll position
            desired_pitch: Desired pitch position
            desired_z_pos: Desired z (depth) position

        Returns:
            error: The roll, pitch and depth error
        '''
        #Calculate error for each degree of freedom
        error = [0, 0, 0, 0, 0, 0]
        error[0] = desired_roll - curr_roll
        roll_control = self.roll_pid_controller.control_step(error[0])

        error[1] = desired_pitch - curr_pitch
        pitch_control = self.pitch_pid_controller.control_step(error[1])

        #depth error
        print(curr_z_pos, desired_z_pos)
        error[2] = desired_z_pos - curr_z_pos
        z_control = self.z_pid_controller.control_step(error[2])

        #Write controls to thrusters
        #Set x, y, and yaw controls to zero since we don't care about the subs
        #heading or planar orientation for a simple depth move
        self.controlled_thrust(roll_control, pitch_control, 0, 0, 0, z_control)

        return error
Exemple #10
0
class Movement_PID:
    '''
    A movement controller for Perseverance that relies on PID controller to control
    the 6 degrees of freedom.
    '''
    def __init__(self):
        '''
        Initialize the thrusters and PID controllers on Perseverance.

        Parameters:
            N/A

        Returns:
            N/A
        '''
        #Get the mechos network parameters
        configs = MechOS_Network_Configs(
            MECHOS_CONFIG_FILE_PATH)._get_network_parameters()

        #Initialize parameter server client to get and set parameters related to
        #the PID controller. This includes update time and PID contstants for
        #each degree of freedom.
        self.param_serv = mechos.Parameter_Server_Client(
            configs["param_ip"], configs["param_port"])
        self.param_serv.use_parameter_database(configs["param_server_path"])

        #Initialize serial connection to the maestro
        com_port = self.param_serv.get_param("COM_Ports/maestro")
        maestro_serial_obj = serial.Serial(com_port, 9600)

        #Initialize all 8 thrusters (max thrust 80%)
        max_thrust = float(self.param_serv.get_param("Control/max_thrust"))

        self.thrusters = [None, None, None, None, None, None, None, None]
        self.thrusters[0] = Thruster(maestro_serial_obj, 1, [0, 0, 1],
                                     [1, -1, 0], max_thrust, True)
        self.thrusters[1] = Thruster(maestro_serial_obj, 2, [0, 1, 0],
                                     [1, 0, 0], max_thrust, True)
        self.thrusters[2] = Thruster(maestro_serial_obj, 3, [0, 0, 1],
                                     [1, 1, 0], max_thrust, False)
        self.thrusters[3] = Thruster(maestro_serial_obj, 4, [1, 0, 0],
                                     [0, 1, 0], max_thrust, False)
        self.thrusters[4] = Thruster(maestro_serial_obj, 5, [0, 0, 1],
                                     [-1, 1, 0], max_thrust, True)
        self.thrusters[5] = Thruster(maestro_serial_obj, 6, [0, 1, 0],
                                     [-1, 0, 0], max_thrust, True)
        self.thrusters[6] = Thruster(maestro_serial_obj, 7, [0, 0, 1],
                                     [-1, -1, 0], max_thrust, False)
        self.thrusters[7] = Thruster(maestro_serial_obj, 8, [1, 0, 0],
                                     [0, -1, 0], max_thrust, True)

        #Used to notify when to hold depth based on the remote control trigger for depth.
        self.remote_desired_depth = 0
        self.remote_depth_recorded = False
        self.remote_yaw_min_thrust = float(
            self.param_serv.get_param("Control/Remote/yaw_min"))
        self.remote_yaw_max_thrust = float(
            self.param_serv.get_param("Control/Remote/yaw_max"))
        self.remote_x_min_thrust = float(
            self.param_serv.get_param("Control/Remote/x_min"))
        self.remote_x_max_thrust = float(
            self.param_serv.get_param("Control/Remote/x_max"))
        self.remote_y_min_thrust = float(
            self.param_serv.get_param("Control/Remote/y_min"))
        self.remote_y_max_thrust = float(
            self.param_serv.get_param("Control/Remote/y_max"))

        #Initialize the PID controllers for control system
        self.set_up_PID_controllers(True)

    def set_up_PID_controllers(self, initialization=False):
        '''
        Setup the PID controllers with the initial values set in the subs
        parameter xml server file. Also update the strength parameter for
        each thrusters.

        Parameters:
            initialization: If it is the first time that this function is being called,
                            set initialization to true so it creates the pid controllers.
                            If false, it just updates the controller values.

        Returns:
            N/A
        '''
        d_t = float(self.param_serv.get_param("Control/PID/dt"))
        roll_p = float(self.param_serv.get_param("Control/PID/roll_pid/p"))
        roll_i = float(self.param_serv.get_param("Control/PID/roll_pid/i"))
        roll_d = float(self.param_serv.get_param("Control/PID/roll_pid/d"))
        self.roll_min_error = float(
            self.param_serv.get_param("Control/PID/roll_pid/min_error"))
        self.roll_max_error = float(
            self.param_serv.get_param("Control/PID/roll_pid/max_error"))
        self.max_roll = float(
            self.param_serv.get_param("Control/Limits/max_roll"))

        pitch_p = float(self.param_serv.get_param("Control/PID/pitch_pid/p"))
        pitch_i = float(self.param_serv.get_param("Control/PID/pitch_pid/i"))
        pitch_d = float(self.param_serv.get_param("Control/PID/pitch_pid/d"))
        self.pitch_min_error = float(
            self.param_serv.get_param("Control/PID/pitch_pid/min_error"))
        self.pitch_max_error = float(
            self.param_serv.get_param("Control/PID/pitch_pid/max_error"))
        self.max_pitch = float(
            self.param_serv.get_param("Control/Limits/max_pitch"))

        yaw_p = float(self.param_serv.get_param("Control/PID/yaw_pid/p"))
        yaw_i = float(self.param_serv.get_param("Control/PID/yaw_pid/i"))
        yaw_d = float(self.param_serv.get_param("Control/PID/yaw_pid/d"))
        self.yaw_min_error = float(
            self.param_serv.get_param("Control/PID/yaw_pid/min_error"))
        self.yaw_max_error = float(
            self.param_serv.get_param("Control/PID/yaw_pid/max_error"))

        x_p = float(self.param_serv.get_param("Control/PID/x_pid/p"))
        x_i = float(self.param_serv.get_param("Control/PID/x_pid/i"))
        x_d = float(self.param_serv.get_param("Control/PID/x_pid/d"))
        self.x_min_error = float(
            self.param_serv.get_param("Control/PID/x_pid/min_error"))
        self.x_max_error = float(
            self.param_serv.get_param("Control/PID/x_pid/max_error"))

        y_p = float(self.param_serv.get_param("Control/PID/y_pid/p"))
        y_i = float(self.param_serv.get_param("Control/PID/y_pid/i"))
        y_d = float(self.param_serv.get_param("Control/PID/y_pid/d"))
        self.y_min_error = float(
            self.param_serv.get_param("Control/PID/y_pid/min_error"))
        self.y_max_error = float(
            self.param_serv.get_param("Control/PID/y_pid/max_error"))

        #z = depth pid
        z_p = float(self.param_serv.get_param("Control/PID/z_pid/p"))
        z_i = float(self.param_serv.get_param("Control/PID/z_pid/i"))
        z_d = float(self.param_serv.get_param("Control/PID/z_pid/d"))
        self.z_min_error = float(
            self.param_serv.get_param("Control/PID/z_pid/min_error"))
        self.z_max_error = float(
            self.param_serv.get_param("Control/PID/z_pid/max_error"))
        self.min_z = float(self.param_serv.get_param("Control/Limits/min_z"))
        self.max_z = float(self.param_serv.get_param("Control/Limits/max_z"))

        #The bias term is a thruster vale to set to thruster 1, 3, 5, 7 to make the sub neutrally bouyant.
        #This term is added to the proportional gain controller.
        #(K_p * error) + bias
        #Essentially this parameter will make the sub act neutrally bouyant below the activate bias depth.
        self.z_bias = float(
            self.param_serv.get_param("Control/PID/z_pid/bias"))
        self.z_active_bias_depth = float(
            self.param_serv.get_param("Control/PID/z_pid/active_bias_depth"))

        #If running the script with initialization true, create PID_Controller objects
        if (initialization):
            print("[INFO]: Initializing PID Controllers.")
            self.roll_pid_controller = PID_Controller(roll_p, roll_i, roll_d,
                                                      d_t)
            self.pitch_pid_controller = PID_Controller(pitch_p, pitch_i,
                                                       pitch_d, d_t)
            self.yaw_pid_controller = PID_Controller(yaw_p, yaw_i, yaw_d, d_t)
            self.x_pid_controller = PID_Controller(x_p, x_i, x_d, d_t)
            self.y_pid_controller = PID_Controller(y_p, y_i, y_d, d_t)
            self.z_pid_controller = PID_Controller(z_p, z_i, z_d, d_t)

        else:
            print("[INFO]: Updating PID Controller Configurations.")
            self.roll_pid_controller.set_gains(roll_p, roll_i, roll_d, d_t)
            self.pitch_pid_controller.set_gains(pitch_p, pitch_i, pitch_d, d_t)
            self.yaw_pid_controller.set_gains(yaw_p, yaw_i, yaw_d, d_t)
            self.x_pid_controller.set_gains(x_p, x_i, x_d, d_t)
            self.y_pid_controller.set_gains(y_p, y_i, y_d, d_t)
            self.z_pid_controller.set_gains(z_p, z_i, z_d, d_t)

        #TODO: Physically balance the sub so that this can be deprecated.
        #Thruster Strengths (these are used to give more strengths to weeker thrusters in the case that the sub is imbalanced)
        #Each index corresponds to the thruster id.
        self.thruster_strengths = [0, 0, 0, 0, 0, 0, 0, 0]

        for i in range(8):
            param_path = "Control/Thruster_Strengths/T%d" % (i + 1)
            self.thruster_strengths[i] = float(
                self.param_serv.get_param(param_path))

        #Get the depth at which the thruster strength offsets will be used (in ft)
        self.thruster_offset_active_depth = float(
            self.param_serv.get_param(
                "Control/Thruster_Strengths/active_depth"))

    def simple_thrust(self, thrusts):
        '''
        Individually sets each thruster PWM given the PWMs in the thrusts
        list.

        Parameters:
            thrusts: A list of length 8 containing a thrust value (%) between
            [-100, 100]. The list should be in order of the thruster ids.

        Returns:
            N/A
        '''

        for thruster_id, thrust in enumerate(thrusts):
            self.thrusters[thruster_id].set_thrust(thrust)

    def controlled_thrust(self, roll_control, pitch_control, yaw_control,
                          x_control, y_control, z_control, curr_z_pos):
        '''
        Function takes control outputs from calculated by the PID values and applys
        them to the 6 degree control matrix to determine the thrust for each thruster
        to reach desired point.

        Parameters:
            roll_control: The control output from the roll pid controller.
            pitch_control: The control output from the pitch pid controller.
            yaw_control: The control output from the yaw pid controller.
            x_control: The control output from the x translation pid controller.
            y_control: The control output from the y translation pid controller.
            z_control: The control output from the z translation pid controller.
            curr_z_pos: The current depth position of the sub. This is needed to know
                        when to activate thruster offsets to help hold the sub level.
        Returns:
            N/A
        '''
        for thruster_id, thruster in enumerate(self.thrusters):
            #Multiplied roll control and x_control by negative one to account for the thrusters going in the
            #wrong direction.
            #Also only thruster 2 and 6 are used for yaw, achieve better results this way.
            #To add in thrusters 4 and 8 for yaw, add the following comment line to the addition.
            #       (yaw_control * thruster.orientation[0] * thruster.location[1])

            thrust = (-1 * roll_control * thruster.orientation[2] * thruster.location[1]) + \
                     (pitch_control * thruster.orientation[2] * thruster.location[0]) + \
                     (yaw_control * thruster.orientation[1] * thruster.location[0]) + \
                     (-1 * x_control * thruster.orientation[0]) + \
                     (y_control * thruster.orientation[1]) + \
                     (z_control * thruster.orientation[2])

            #Write the thrust to the given thruster. Some thrusters have an additional offset to given them
            #a higher strength. This is used to help balance out weigth distribution issues with the sub.
            #if(curr_z_pos >= self.thruster_offset_active_depth):
            #    thrust = thrust + self.thruster_strengths[thruster_id]

            #Since the center of mass of the sub is not in the center of the axises of the sub,
            #some thruster will need to produce more torque to have movement about that axis,
            #be stable.
            thrust = thrust + (thrust * self.thruster_strengths[thruster_id])

            if (curr_z_pos >= self.z_active_bias_depth):

                thrust = thrust + (
                    self.z_bias * thruster.orientation[2]
                )  #Make sure only thrusters controlling z have this parameter

            self.thrusters[thruster_id].set_thrust(thrust)

    def bound_error(self, unbounded_error, min_error, max_error):
        '''
        This function will check if the unbounded error is within the range
        of the max and min error. If it is not, it will force it to be.

        Parameter:
            unbounded_error: The error that has not yet been checked if it
                            is within the error limits
            min_error: The minimum value that you want your error to be.
            max_error: The maximum value that you want your error to be.
        Returns:
            N/A
        '''
        if (unbounded_error < min_error):
            unbounded_error = min_error
            return (unbounded_error)
        elif (unbounded_error > max_error):
            unbounded_error = max_error
            return (unbounded_error)
        else:
            return (unbounded_error)

    def advance_move(self, current_position, desired_position):
        '''
        Given the current position and desired positions of the AUV, obtain the pid
        control outputs for all 6 degrees of freedom.

        Parameters:
            current_position: A list of the most up-to-date current position.
                            List format: [roll, pitch, yaw, x_pos, y_pos, depth]
            desired_position: A list containing the desired positions of each
                                axis. Same list format as the current_position
                                parameter.

        Returns:
            error: A list of of the errors that where evaluted for each axis.
                    List Format: [roll_error, pitch_error, yaw_error, x_pos_error, y_pos_error, depth_error]
        '''

        #calculate the error of each degree of freedom
        error = [0, 0, 0, 0, 0, 0]

        #Make sure roll is within maximum limit
        if (abs(desired_position[0]) > self.max_roll):
            desired_position[0] = math.copysign(self.max_roll,
                                                desired_position[0])
            print(
                "[WARNING]: Absolute of desired roll %0.2f is greater than the max limit %0.2f"
                % (desired_position[0], self.max_roll))

        error[0] = self.bound_error(desired_position[0] - current_position[0],
                                    self.roll_min_error,
                                    self.roll_max_error)  #roll error

        #Make sure pitch is within maximum limit
        if (abs(desired_position[1]) > self.max_pitch):
            desired_position[1] = math.copysign(self.max_pitch,
                                                desired_position[1])
            print(
                "[WARNING]: Absolute of desired pitch %0.2f is greater than the max limit %0.2f"
                % (desired_position[0], self.max_pitch))

        error[1] = self.bound_error(desired_position[1] - current_position[1],
                                    self.pitch_min_error,
                                    self.pitch_max_error)  #pitch error

        #Calculate yaw error. The logic includes calculating error for choosing shortest angle to travel
        desired_yaw = desired_position[2]
        curr_yaw = current_position[2]

        yaw_error = desired_yaw - curr_yaw
        if (abs(yaw_error) > 180):

            if (yaw_error < 0):
                yaw_error = yaw_error + 360
            else:
                yaw_error = yaw_error - 360

        error[2] = self.bound_error(yaw_error, self.yaw_min_error,
                                    self.yaw_max_error)
        #Calculate the error in the x and y position (relative to the sub) given the current north/east position.
        #Using rotation matrix.
        #North and East error
        north_error = desired_position[3] - current_position[3]
        east_error = desired_position[4] - current_position[4]
        yaw_rad = math.radians(curr_yaw)  #convert yaw from degrees to radians

        x_error = (math.cos(yaw_rad) * north_error) + (math.sin(yaw_rad) *
                                                       east_error)
        y_error = (-1 * math.sin(yaw_rad) * north_error) + (math.cos(yaw_rad) *
                                                            east_error)
        error[3] = self.bound_error(x_error, self.x_min_error,
                                    self.x_max_error)
        error[4] = self.bound_error(y_error, self.y_min_error,
                                    self.y_max_error)

        if (desired_position[5] < self.min_z):
            print(
                "[WARNING]: Desired depth %0.2f is less than the min limit %0.2f"
                % (desired_position[5], self.min_z))
            desired_position[5] = self.min_z

        elif (desired_position[5] > self.max_z):
            print(
                "[WARNING]: Desired depth %0.2f is greater than the max limit %0.2f"
                % (desired_position[5], self.max_z))
            desired_position[5] = self.max_z

        error[5] = self.bound_error(desired_position[5] - current_position[5],
                                    self.z_min_error,
                                    self.z_max_error)  #z_pos (depth)

        #Get the thrusts from the PID controllers to move towards desired pos.
        roll_control = self.roll_pid_controller.control_step(error[0])
        pitch_control = self.pitch_pid_controller.control_step(error[1])
        yaw_control = self.yaw_pid_controller.control_step(error[2])
        x_control = self.x_pid_controller.control_step(error[3])
        y_control = self.y_pid_controller.control_step(error[4])
        z_control = self.z_pid_controller.control_step(error[5])
        #Write the controls to thrusters
        self.controlled_thrust(roll_control, pitch_control, yaw_control,
                               x_control, y_control, z_control,
                               current_position[5])
        return error

    def remote_move(self, current_position, remote_commands):
        '''
        Accepts spoofed error input for each degree of freedom from the xbox controller
        to utilize the pid controllers for remote control movement.

        Parameters:
            current_position: A list of the most up-to-date current position.
                            List format: [roll, pitch, yaw, x_pos, y_pos, depth]
            remote_commands: A list of the spoofed errors for [yaw, x_pos, y_pos, depth] to
                    utilize the remote control to perform movement with the PID
                    controller.

        Returns:
            N/A
        '''
        #always want roll and pitch to be level at 0 degrees
        roll_error = self.bound_error(0.0 - current_position[0],
                                      self.roll_min_error,
                                      self.roll_max_error)  #roll error
        pitch_error = self.bound_error(0.0 - current_position[1],
                                       self.pitch_min_error,
                                       self.pitch_max_error)  #pitch error

        #Interpolate errors to the min and max errors set in the parameter server

        yaw_control = np.interp(
            remote_commands[0], [-1, 1],
            [self.remote_yaw_min_thrust, self.remote_yaw_max_thrust])
        x_control = np.interp(
            remote_commands[1], [-1, 1],
            [self.remote_x_min_thrust, self.remote_x_max_thrust])
        y_control = np.interp(
            remote_commands[2], [-1, 1],
            [self.remote_y_min_thrust, self.remote_y_max_thrust])

        #When the trigger is released for controlling depth, record the depth and hold.
        if (remote_commands[4]):

            if (self.remote_depth_recorded == False):
                self.remote_desired_depth = current_position[5]
                self.remote_depth_recorded = True

            depth_error = self.bound_error(self.remote_desired_depth - current_position[5], \
                              self.z_min_error, self.z_max_error) #z_pos (depth)

        else:
            self.remote_depth_recorded = False
            self.remote_desired_depth = 0

            #Since the min and max error is not the same in terms of absolute value,
            #interpolation needs to be handled seperate for up and down movements.
            if (remote_commands[3] <= 0.0):

                depth_error = np.interp(remote_commands[3], [-1, 0],
                                        [self.z_min_error, 0])
            else:
                depth_error = np.interp(remote_commands[3], [0, 1],
                                        [0, self.z_max_error])

        #Get the thrusts from the PID controllers to move towards desired pos.
        roll_control = self.roll_pid_controller.control_step(roll_error)
        pitch_control = self.pitch_pid_controller.control_step(pitch_error)
        z_control = self.z_pid_controller.control_step(depth_error)
        self.controlled_thrust(roll_control, pitch_control, yaw_control,
                               -1 * x_control, y_control, z_control,
                               current_position[5])

        return

    #This is a helper function to be used initially for tuning the roll, pitch
    #and depth PID values. Once tuned, please use advance_move instead.
    def simple_depth_move_no_yaw(self, curr_roll, curr_pitch, curr_z_pos,
                                 desired_roll, desired_pitch, desired_z_pos):
        '''
        Without carrying about x axis position, y axis position, or yaw, use the PID controllers
        to go down to a give depth (desired_z_pos) with a give orientation.

        Parameters:
            curr_roll: Current roll data
            curr_pitch Current pitch data
            curr_z_pos: Current z position
            desired_roll: Desired roll position
            desired_pitch: Desired pitch position
            desired_z_pos: Desired z (depth) position

        Returns:
            error: The roll, pitch and depth error
        '''
        #Calculate error for each degree of freedom
        error = [0, 0, 0, 0, 0, 0]
        error[0] = desired_roll - curr_roll
        roll_control = self.roll_pid_controller.control_step(error[0])

        error[1] = desired_pitch - curr_pitch
        pitch_control = self.pitch_pid_controller.control_step(error[1])

        #depth error
        error[2] = desired_z_pos - curr_z_pos
        z_control = self.z_pid_controller.control_step(error[2])

        #Write controls to thrusters
        #Set x, y, and yaw controls to zero since we don't care about the subs
        #heading or planar orientation for a simple depth move
        self.controlled_thrust(roll_control, pitch_control, 0, 0, 0, z_control,
                               curr_z_pos)

        return error
Exemple #11
0
    def set_up_PID_controllers(self, initialization=False):
        '''
        Setup the PID controllers with the initial values set in the subs
        parameter xml server file. Also update the strength parameter for
        each thrusters.

        Parameters:
            initialization: If it is the first time that this function is being called,
                            set initialization to true so it creates the pid controllers.
                            If false, it just updates the controller values.

        Returns:
            N/A
        '''
        d_t = float(self.param_serv.get_param("Control/PID/dt"))
        roll_p = float(self.param_serv.get_param("Control/PID/roll_pid/p"))
        roll_i = float(self.param_serv.get_param("Control/PID/roll_pid/i"))
        roll_d = float(self.param_serv.get_param("Control/PID/roll_pid/d"))
        self.roll_min_error = float(
            self.param_serv.get_param("Control/PID/roll_pid/min_error"))
        self.roll_max_error = float(
            self.param_serv.get_param("Control/PID/roll_pid/max_error"))
        self.max_roll = float(
            self.param_serv.get_param("Control/Limits/max_roll"))

        pitch_p = float(self.param_serv.get_param("Control/PID/pitch_pid/p"))
        pitch_i = float(self.param_serv.get_param("Control/PID/pitch_pid/i"))
        pitch_d = float(self.param_serv.get_param("Control/PID/pitch_pid/d"))
        self.pitch_min_error = float(
            self.param_serv.get_param("Control/PID/pitch_pid/min_error"))
        self.pitch_max_error = float(
            self.param_serv.get_param("Control/PID/pitch_pid/max_error"))
        self.max_pitch = float(
            self.param_serv.get_param("Control/Limits/max_pitch"))

        yaw_p = float(self.param_serv.get_param("Control/PID/yaw_pid/p"))
        yaw_i = float(self.param_serv.get_param("Control/PID/yaw_pid/i"))
        yaw_d = float(self.param_serv.get_param("Control/PID/yaw_pid/d"))
        self.yaw_min_error = float(
            self.param_serv.get_param("Control/PID/yaw_pid/min_error"))
        self.yaw_max_error = float(
            self.param_serv.get_param("Control/PID/yaw_pid/max_error"))

        x_p = float(self.param_serv.get_param("Control/PID/x_pid/p"))
        x_i = float(self.param_serv.get_param("Control/PID/x_pid/i"))
        x_d = float(self.param_serv.get_param("Control/PID/x_pid/d"))
        self.x_min_error = float(
            self.param_serv.get_param("Control/PID/x_pid/min_error"))
        self.x_max_error = float(
            self.param_serv.get_param("Control/PID/x_pid/max_error"))

        y_p = float(self.param_serv.get_param("Control/PID/y_pid/p"))
        y_i = float(self.param_serv.get_param("Control/PID/y_pid/i"))
        y_d = float(self.param_serv.get_param("Control/PID/y_pid/d"))
        self.y_min_error = float(
            self.param_serv.get_param("Control/PID/y_pid/min_error"))
        self.y_max_error = float(
            self.param_serv.get_param("Control/PID/y_pid/max_error"))

        #z = depth pid
        z_p = float(self.param_serv.get_param("Control/PID/z_pid/p"))
        z_i = float(self.param_serv.get_param("Control/PID/z_pid/i"))
        z_d = float(self.param_serv.get_param("Control/PID/z_pid/d"))
        self.z_min_error = float(
            self.param_serv.get_param("Control/PID/z_pid/min_error"))
        self.z_max_error = float(
            self.param_serv.get_param("Control/PID/z_pid/max_error"))
        self.min_z = float(self.param_serv.get_param("Control/Limits/min_z"))
        self.max_z = float(self.param_serv.get_param("Control/Limits/max_z"))

        #The bias term is a thruster vale to set to thruster 1, 3, 5, 7 to make the sub neutrally bouyant.
        #This term is added to the proportional gain controller.
        #(K_p * error) + bias
        #Essentially this parameter will make the sub act neutrally bouyant below the activate bias depth.
        self.z_bias = float(
            self.param_serv.get_param("Control/PID/z_pid/bias"))
        self.z_active_bias_depth = float(
            self.param_serv.get_param("Control/PID/z_pid/active_bias_depth"))

        #If running the script with initialization true, create PID_Controller objects
        if (initialization):
            print("[INFO]: Initializing PID Controllers.")
            self.roll_pid_controller = PID_Controller(roll_p, roll_i, roll_d,
                                                      d_t)
            self.pitch_pid_controller = PID_Controller(pitch_p, pitch_i,
                                                       pitch_d, d_t)
            self.yaw_pid_controller = PID_Controller(yaw_p, yaw_i, yaw_d, d_t)
            self.x_pid_controller = PID_Controller(x_p, x_i, x_d, d_t)
            self.y_pid_controller = PID_Controller(y_p, y_i, y_d, d_t)
            self.z_pid_controller = PID_Controller(z_p, z_i, z_d, d_t)

        else:
            print("[INFO]: Updating PID Controller Configurations.")
            self.roll_pid_controller.set_gains(roll_p, roll_i, roll_d, d_t)
            self.pitch_pid_controller.set_gains(pitch_p, pitch_i, pitch_d, d_t)
            self.yaw_pid_controller.set_gains(yaw_p, yaw_i, yaw_d, d_t)
            self.x_pid_controller.set_gains(x_p, x_i, x_d, d_t)
            self.y_pid_controller.set_gains(y_p, y_i, y_d, d_t)
            self.z_pid_controller.set_gains(z_p, z_i, z_d, d_t)

        #TODO: Physically balance the sub so that this can be deprecated.
        #Thruster Strengths (these are used to give more strengths to weeker thrusters in the case that the sub is imbalanced)
        #Each index corresponds to the thruster id.
        self.thruster_strengths = [0, 0, 0, 0, 0, 0, 0, 0]

        for i in range(8):
            param_path = "Control/Thruster_Strengths/T%d" % (i + 1)
            self.thruster_strengths[i] = float(
                self.param_serv.get_param(param_path))

        #Get the depth at which the thruster strength offsets will be used (in ft)
        self.thruster_offset_active_depth = float(
            self.param_serv.get_param(
                "Control/Thruster_Strengths/active_depth"))
class TBC_Controller:
    def __init__(self, PCR_Machine, Peltier, start_time=0, update_period=0.05, volume=10):
        self.pcr = PCR_Machine
        self.peltier = Peltier
        self.time = self.checkpoint = self.start_time = start_time              
        self.period = update_period
        self.volume = volume
        self.set_point = self.pcr.sample_temp
        self.stage = "Hold"
        self.max_block_temp = 109
        self.max_ramp_dist = 35
        self.unachievable = 10
        self.smpWinInRampUpFlag = False
        self.smpWinInRampDownFlag = False
        self.init_pid()        
        self.load_tuning_params()
        self.pid.load(self.pid_const, "Hold")
        self.max_up_ramp = self.calc_poly_eqn(self.upRrEqn, self.volume)
        self.max_down_ramp = self.calc_poly_eqn(self.downRrEqn, self.volume)

    def reset(self, set_point=60):
        self.pid.reset()
        self.start_time = self.time = 0
        self.time_elapsed = 0
        self.set_point = set_point
        self.smpWinInRampUpFlag = False
        self.smpWinInRampDownFlag = False        
    
    def calc_poly_eqn(self, eqn, vol):
        return sum([vol**deg * coeff for deg, coeff in enumerate(eqn)])
    
    def init_pid(self):
        self.pid = PID_Controller()
        self.pid.dt = self.period / 60 # dt is in minute
        self.pid2 = PID_Controller()
        self.pid2.dt = self.period / 60 # dt is in minute

    def load_tuning_params(self):
        self.blockMCP = 10.962
        self.upRrEqn = [
            3.95293071,
           -0.03074861,
            0.00008966,
            0.0
        ]
        self.downRrEqn = [
            3.03500389,
           -0.02403729,
            0.00008891,
            0.0
        ]
        self.pid_const = {}
        self.pid_const["Ramp Up"        ] = {
            "P"  : 2.5, 
            "I"  : 0.1, 
            "D"  : 0.0001, 
            "KI" : 10, 
            "KD" : 20
        }
        self.pid_const["Overshoot Over" ] = {
            "P"  : 6, 
            "I"  : 0.1, 
            "D"  : 0.0, 
            "KI" : 2, 
            "KD" : 5
        }
        self.pid_const["Hold Over"      ] = {
            "P"  : 2, 
            "I"  : 0.02, 
            "D"  : 0.01, 
            "KI" : 5, 
            "KD" : 10
        }
        self.pid_const["Land Over"      ] = {
            "P"  : 7, 
            "I"  : 0.05, 
            "D"  : 0.00, 
            "KI" : 2, 
            "KD" : 5
        }
        self.pid_const["Hold"           ] = {
            "P"  : 0.7, 
            "I"  : 0.02, 
            "D"  : 0.01, 
            "KI" : 5, 
            "KD" : 10
        }
        self.pid_const["Ramp Down"      ] = {
            "P"  : 2.5, 
            "I"  : 0.05, 
            "D"  : 0.0001, 
            "KI" : 10, 
            "KD" : 20
        }
        self.pid_const["Overshoot Under"] = {
            "P"  : 3, 
            "I"  : 0.1, 
            "D"  : 0.00, 
            "KI" : 2, 
            "KD" : 5
        }
        self.pid_const["Hold Under"     ] = {
            "P"  : 1.2, 
            "I"  : 0.02, 
            "D"  : 0.01, 
            "KI" : 5, 
            "KD" : 10
        }
        self.pid_const["Land Under"     ] = {
            "P"  : 7, 
            "I"  : 0.1, 
            "D"  : 0.00, 
            "KI" : 2, 
            "KD" : 5
        }
        self.slow_upramp_qpid = -15
        self.block_slow_rate = 0.5
        self.sample_slow_rate = 0.0226
        self.rampup_minP = 3
        self.rampdown_minP = 3
        self.smoothRegionOverRR = 1.6
        self.smoothRegionUnderRR = 1.5
        self.smoothRegionOverWin = 1.5
        self.smoothRegionUnderWin = 1.75

        if self.volume <= 5:
            pass
        elif self.volume <= 10:
            self.maxHeatBlkOS       = 2.8
            self.maxCoolBlkOS       = 3.4
            self.maxHeatSmpWin      = 2
            self.maxCoolSmpWin      = 2
            self.qMaxHoldPid        = 40
            self.qMaxRampPid        = 150
            self.maxHeatIset        = 6.5
            self.maxCoolIset        = 6.5
            self.maxHeatBlkWin      = 4
            self.maxCoolBlkWin      = 3
            self.qHeatLoss          = 10
            self.heat_brake_const   = 1.2
            self.heatSpCtrlActivRR  = 0.2
            self.heatSpCtrlActivSP  = 0.1
            self.cool_brake_const   = 1.5
            self.coolSpCtrlActivRR  = 0.3
            self.coolSpCtrlActivSP  =-0.1
        elif self.volume <= 30:
            pass
        elif self.volume <= 50:
            pass
        else:
            pass


    def update_feedback(self):
        # update pid process variable (PV)
        if self.stage == "Ramp Up" or self.stage == "Ramp Down":
            self.pid.PV = self.pcr.sample_rate
        elif self.stage == "Overshoot Over" or self.stage == "Overshoot Under":
            if self.smpWinInRampDownFlag or self.smpWinInRampUpFlag:
                self.pid.PV = self.pcr.sample_rate
            else:
                self.pid.PV = self.pcr.block_rate
            self.pid2.PV = self.pcr.block_temp * 0.5
        elif self.stage == "Land Over" or self.stage == "Land Under":
            self.pid.PV = self.pcr.block_rate
        else:
            self.pid.PV = self.pcr.block_temp * 0.5
        self.time_elapsed = self.time - self.start_time        

    def prepare_overshoot_over(self):
        self.pid.reset()
        self.pid.load(self.pid_const, "Overshoot Over")

        self.pid2.reset()
        self.pid2.load(self.pid_const, "Hold Over")        
        if self.pcr.block_temp >= self.max_block_temp or self.set_point + self.calcHeatBlkOS >= self.max_block_temp:
            self.pid2.SP = self.max_block_temp * 0.5
        else:
            self.pid2.SP = (self.set_point + self.calcHeatBlkOS) * 0.5
        self.pid2.ffwd = self.qHeatLoss / self.qMaxRampPid * 100
        self.pid2.y = self.pcr.block_temp * 0.5

        self.stage = "Overshoot Over"
        self.spCtrlFirstActFlag = False
        self.rampUpStageRampTime = self.time_elapsed

    def prepare_hold(self):
        self.stage = "Hold"
        self.pid.reset()
        self.pid.load(self.pid_const, "Hold")
        self.pid.SP = self.set_point * 0.5
        self.pid.y = self.pcr.block_temp * 0.5
        self.pid.ffwd = self.qHeatLoss / self.qMaxHoldPid * 100

    def calcBlockRate(self, timeConst):
        # assume ramp_time and ramp_dist are calculated before calling this function
        if self.ramp_time == 0 or timeConst == 0:
            return self.target_sample_rate
        return self.ramp_dist / (self.ramp_time - timeConst * (1 - exp(-self.ramp_time / timeConst)))

    def prepare_ramp_up(self):
        self.stage = "Ramp Up"    
        self.target_block_rate = self.calcBlockRate(self.pcr.heat_const)

        self.pid.load(self.pid_const, "Ramp Up")
        self.pid.SP = self.target_sample_rate
        initial_qpid = self.blockMCP * self.target_block_rate
        self.pid.ffwd = initial_qpid / self.qMaxRampPid * 100

        if self.target_block_rate < self.block_slow_rate:
            self.pid.ffwd = self.slow_upramp_qpid / self.qMaxRampPid * 100
        
        if self.pid.ffwd > 100:
            self.pid.ffwd = 100

        overshoot_dt_const = (self.ramp_dist - 2) / (self.max_ramp_dist - 2)
        overshoot_rr_const = 1 if self.target_sample_rate >= self.max_up_ramp else self.target_sample_rate / self.max_up_ramp

        if overshoot_dt_const > 1:
            blkOS_const = overshoot_rr_const
            smpWin_const = overshoot_rr_const
        else:
            blkOS_const = overshoot_dt_const * overshoot_rr_const
            smpWin_const = overshoot_dt_const * overshoot_rr_const

        self.calcHeatBlkOS  = blkOS_const * self.maxHeatBlkOS
        self.calcHeatSmpWin = smpWin_const * self.maxHeatSmpWin
        self.calcHeatBlkWin = overshoot_rr_const * self.maxHeatBlkWin
        self.smpWinInRampUpFlag = False

    def prepare_ramp_down(self):
        self.stage = "Ramp Down"        
        self.target_block_rate = self.calcBlockRate(self.pcr.cool_const) # target_block_rate is negative        

        self.pid.load(self.pid_const, "Ramp Down")
        self.pid.SP = self.target_sample_rate

        if abs(self.target_block_rate) < self.block_slow_rate:
            initial_qpid = self.blockMCP * self.block_slow_rate
        else:
            initial_qpid = self.blockMCP * self.target_block_rate

        self.pid.ffwd = -initial_qpid / self.qMaxRampPid * 100        
        
        if self.pid.ffwd < -100:
            self.pid.ffwd = -100

        overshoot_dt_const = (self.ramp_dist - 2)/ (self.max_ramp_dist - 2)
        if abs(self.target_sample_rate) <= self.max_down_ramp:
            overshoot_rr_const = 1
        else:
            overshoot_rr_const = self.target_sample_rate / self.max_down_ramp

        if overshoot_dt_const > 1:
            blkOS_const = overshoot_rr_const            
        else:
            blkOS_const = overshoot_dt_const * overshoot_rr_const
        smpWin_const = overshoot_dt_const * overshoot_rr_const            

        self.calcCoolBlkOS  = blkOS_const * self.maxCoolBlkOS
        self.calcCoolSmpWin = smpWin_const * self.maxCoolSmpWin
        self.calcCoolBlkWin = overshoot_rr_const * self.maxCoolBlkWin        

    def ctrl_ramp_up(self):
        if self.pcr.block_temp < self.set_point \
            and self.pcr.block_temp + self.calcHeatBlkWin <= self.set_point + self.calcHeatBlkOS:

            if self.time_elapsed >= self.ramp_time:
                self.pid.SP = self.unachievable
            else:
                self.pid.SP = (self.set_point - self.pcr.sample_temp) / (self.ramp_time - self.time_elapsed)

            if self.pid.SP > 1.05 * self.target_sample_rate:
                self.pid.SP = 1.05 * self.target_sample_rate

            if self.target_block_rate <= self.block_slow_rate:
                if self.target_sample_rate / self.max_up_ramp < 0.1:
                    stash = self.pid.P
                    factor = self.target_sample_rate / self.max_up_ramp * 10
                    self.pid.P *= factor
                    if self.pid.P < self.rampup_minP:
                        self.pid.P = self.rampup_minP          
                    self.qpid = self.qMaxRampPid * self.pid.update() * 0.01
                    self.pid.P = stash
                else:
                    self.qpid = self.qMaxRampPid * self.pid.update() * 0.01

            else:
                self.qpid = self.qMaxRampPid * self.pid.update() * 0.01

        else: # block temp. has overshot the set point
            if self.pcr.sample_rate <= self.sample_slow_rate:
                self.prepare_hold()
            else:
                self.smpWinInRampUpFlag = False
                overshoot_gap = self.set_point + self.calcHeatBlkOS - self.pcr.block_temp
                if self.pcr.block_rate > self.pcr.sample_rate:
                    time_to_maxOS = overshoot_gap / self.pcr.block_rate
                    self.rampUpStageRate = self.pcr.block_rate
                else:
                    time_to_maxOS = overshoot_gap / self.pcr.sample_rate
                    self.rampUpStageRate = self.pcr.sample_rate
                if self.calcHeatBlkOS > self.calcHeatBlkWin:
                    self.heat_brake = self.heat_brake_const * self.calcHeatBlkOS / time_to_maxOS
                else:
                    self.heat_brake = self.heat_brake_const * self.calcHeatBlkWin / time_to_maxOS

                self.prepare_overshoot_over()

        if self.pcr.sample_temp >= self.set_point - self.calcHeatSmpWin:
            if self.pid.PV <= 0:
                return

            self.smpWinInRampUpFlag = True
            time_to_setpoint = (self.set_point - self.pcr.sample_temp) / self.pid.PV
            self.heat_brake = self.heat_brake_const * self.calcHeatSmpWin / time_to_setpoint
            self.rampUpStageRate = self.pid.PV
            self.prepare_overshoot_over()

        self.peltier.mode = "heat"

    def prepare_hold_over(self):
        if self.pcr.block_temp >= self.max_block_temp:
            self.pid.SP = self.max_block_temp * 0.5
        else:
            self.pid.SP = (self.set_point + self.calcHeatBlkOS) * 0.5

        if self.pid.SP > self.max_block_temp * 0.5:
            self.pid.SP = self.max_block_temp * 0.5

        self.pid.m = self.pid2.m
        self.pid.b = self.pid2.b
        self.pid.y = self.pid2.y
        self.pid.ffwd = self.qHeatLoss / self.qMaxHoldPid * 100
        self.pid.load(self.pid_const, "Hold Over")
        self.stage = "Hold Over"


    def ctrl_overshoot_over(self):
        if not self.smpWinInRampUpFlag:
            if self.pcr.sample_temp >= self.set_point - self.calcHeatSmpWin:
                self.prepare_hold()
                return

            try:
                if self.calcHeatBlkOS >= self.calcHeatBlkWin:
                    tempSP = self.rampUpStageRate * exp(-self.heat_brake * (self.time_elapsed - self.rampUpStageRampTime) / self.calcHeatBlkOS)
                else:
                    tempSP = self.rampUpStageRate * exp(-self.heat_brake * (self.time_elapsed - self.rampUpStageRampTime) / self.calcHeatBlkWin)
            except:
                tempSP = 0
        else:
            try:
                if self.calcHeatSmpWin != 0:
                    tempSP = self.rampUpStageRate * exp(-self.heat_brake * (self.time_elapsed - self.rampUpStageRampTime) / self.calcHeatSmpWin)
                else:
                    tempSP = 0
            except:
                tempSP = 0
            
            if self.pcr.sample_temp >= self.set_point - 0.2 or self.pcr.block_temp > self.set_point:
                tempSP = self.pid.b
                temp_b = self.pid2.b
                temp_ffwd = self.pid2.ffwd
                temp_y = self.pid2.y
                self.prepare_hold()
                self.pid.b  = temp_b + tempSP
                self.pid.m  = self.pid.b
                self.pid.b -= temp_ffwd
                self.pid.y  = temp_y
            return
        
        self.pid.SP = tempSP
        qPower = tempSP / self.rampUpStageRate * self.qMaxRampPid + (1 - tempSP / self.rampUpStageRate) * self.qMaxHoldPid
        self.pid.ffwd = self.pid.SP * self.blockMCP / qPower * 100
        self.qpid = qPower * self.pid.update() / 100

        if self.pid.SP <= self.heatSpCtrlActivRR * self.rampUpStageRate \
            or self.pid.SP <= self.heatSpCtrlActivSP:
            if not self.spCtrlFirstActFlag:
                self.pid2.y = self.pcr.block_temp * 0.5
                self.spCtrlFirstActFlag = True
            self.qpid += qPower * self.pid2.update() / 100

        if self.pcr.block_temp >= self.set_point + self.calcHeatBlkOS \
            or self.pcr.block_temp >= self.max_block_temp:

            self.prepare_hold_over()
        
        self.peltier.mode = "heat"

    def prepare_land_over(self):
        self.pid.reset()
        self.pid.load(self.pid_const, "Land Over")
        self.stage = "Land Over"

    def ctrl_hold_over(self):
        self.qpid = self.qMaxHoldPid * self.pid.update() / 100
        if self.pcr.sample_temp >= self.set_point - self.calcHeatSmpWin:
            self.prepare_land_over()
        self.peltier.mode = "heat"

    def ctrl_land_over(self):
        timeToSetPt = (self.set_point - self.pcr.sample_temp) / self.pcr.sample_rate
        if timeToSetPt > 0:
            self.pid.SP = (self.set_point - self.pcr.block_temp) / timeToSetPt
        else:
            self.pid.SP = -3
        if self.pcr.block_temp - self.smoothRegionOverWin <= self.set_point:
            if self.pid.SP < -self.smoothRegionOverRR:
                self.pid.SP = -self.smoothRegionOverRR
        self.pid.ffwd = self.pid.SP * self.blockMCP / self.qMaxRampPid * 100
        self.qpid = -self.qMaxRampPid * self.pid.update() / 100
        if self.pcr.block_temp - 0.25 <= self.set_point:
            self.prepare_hold()
        self.peltier.mode = "cool"

    def ctrl_ramp_down(self):
        if self.pcr.block_temp > self.set_point \
            and self.pcr.block_temp - self.calcCoolBlkWin >= self.set_point - self.calcCoolBlkOS:

            if self.time_elapsed >= self.ramp_time:
                self.pid.SP = -self.unachievable
            else:
                self.pid.SP = (self.set_point - self.pcr.sample_temp) / (self.ramp_time - self.time_elapsed)

            if self.pid.SP < -1.05 * self.target_sample_rate:
                self.pid.SP = -1.05 * self.target_sample_rate

            if self.target_block_rate <= self.block_slow_rate:
                if self.target_sample_rate / self.max_down_ramp < 0.1:
                    stash = self.pid.P
                    factor = self.target_sample_rate / self.max_down_ramp * 10
                    self.pid.P *= factor
                    if self.pid.P < self.rampdown_minP:
                        self.pid.P = self.rampdown_minP          
                    self.qpid = -self.qMaxRampPid * self.pid.update() * 0.01
                    self.pid.P = stash
                else:
                    self.qpid = -self.qMaxRampPid * self.pid.update() * 0.01

            else:
                self.qpid = -self.qMaxRampPid * self.pid.update() * 0.01

        else: # block temp. has overshot the set point
            if self.pcr.sample_rate >= -self.sample_slow_rate:
                self.prepare_hold()
            else:
                self.smpWinInRampDownFlag = False
                overshoot_gap = self.set_point - self.calcCoolBlkOS - self.pcr.block_temp
                if self.pcr.block_rate < self.pcr.sample_rate:
                    time_to_maxOS = overshoot_gap / self.pcr.block_rate
                    self.rampDownStageRate = abs(self.pcr.block_rate)
                else:
                    time_to_maxOS = overshoot_gap / self.pcr.sample_rate
                    self.rampDownStageRate = abs(self.pcr.sample_rate)

                if self.calcCoolBlkOS > self.calcCoolBlkWin:
                    self.cool_brake = self.cool_brake_const * self.calcCoolBlkOS / time_to_maxOS
                else:
                    self.cool_brake = self.cool_brake_const * self.calcCoolBlkWin / time_to_maxOS
                
                self.prepare_overshoot_under()

        if self.pcr.sample_temp <= self.set_point + self.calcCoolSmpWin:
            if self.pid.PV >= 0:
                return

            self.smpWinInRampDownFlag = True
            time_to_setpoint = (self.set_point - self.pcr.sample_temp) / self.pid.PV
            self.cool_brake = self.cool_brake_const * self.calcCoolSmpWin / time_to_setpoint
            self.rampDownStageRate = self.pid.PV
            self.prepare_overshoot_under()

        self.peltier.mode = "cool"

    def prepare_overshoot_under(self):
        self.rampDownStageRampTime = self.time_elapsed
        self.pid.load(self.pid_const, "Overshoot Under")
        self.pid2.reset()
        self.pid2.load(self.pid_const, "Hold Under")
        self.pid2.SP = (self.set_point - self.calcCoolBlkOS) * 0.5
        self.pid2.ffwd = -self.qHeatLoss / self.qMaxHoldPid * 100
        self.pid2.y = self.pcr.block_temp * 0.5
        self.spCtrlFirstActFlag = False
        self.stage = "Overshoot Under"

    def prepare_land_under(self):
        self.pid.reset()
        self.pid.load(self.pid_const, "Land Under")
        self.stage = "Land Under"

    def ctrl_overshoot_under(self):
        if self.smpWinInRampDownFlag == False:
            if self.calcCoolBlkOS >= self.calcCoolBlkWin:
                tempSP = self.rampDownStageRate * exp(-self.cool_brake / self.calcCoolBlkOS * (self.time_elapsed - self.rampDownStageRampTime))
            else:
                tempSP = self.rampDownStageRate * exp(-self.cool_brake / self.calcCoolBlkWin * (self.time_elapsed - self.rampDownStageRampTime))

            if self.pcr.sample_temp <= self.set_point + self.calcCoolSmpWin:
                self.prepare_land_under()
                return

        else:
            if self.calcCoolSmpWin != 0:
                tempSP = self.rampDownStageRate * exp(-self.cool_brake / self.calcCoolSmpWin *(self.time_elapsed - self.rampDownStageRampTime))
            else:
                tempSP = 0

            if self.pcr.sample_temp <= self.set_point + 1:
                self.prepare_hold()
                self.pid.m = self.pid2.m
                self.pid.b = self.pid2.b
                self.pid.y = self.pid2.y
                return
        qPower  = (tempSP / self.rampDownStageRate)     * self.qMaxRampPid
        qPower += (1 - tempSP / self.rampDownStageRate) * self.qMaxHoldPid
        self.pid.SP = -tempSP
        self.pid.ffwd = self.pid.SP * self.blockMCP / qPower * 100
        self.qpid = -qPower * self.pid.update() / 100
        if self.pid.SP >= self.coolSpCtrlActivRR * self.rampDownStageRate \
            or self.pid.SP >= self.coolSpCtrlActivSP:
            if self.spCtrlFirstActFlag == False:
                self.pid2.y = self.pcr.block_temp * 0.5
                self.spCtrlFirstActFlag = True
            self.qpid -= qPower * self.pid2.update() / 100
        if self.pcr.block_temp <= self.set_point - self.calcCoolBlkOS:
            self.prepare_hold_under()
        self.peltier.mode = "cool"

    def ctrl_land_under(self):
        timeToSetPt = (self.set_point - self.pcr.sample_temp) / self.pcr.sample_rate
        if timeToSetPt < 0:
            self.pid.SP = (self.set_point - self.pcr.block_temp) / timeToSetPt
        else:
            self.pid.SP = 3
        if self.pcr.block_temp + self.smoothRegionOverWin >= self.set_point:
            if self.pid.SP > self.smoothRegionOverRR:
                self.pid.SP = self.smoothRegionOverRR
        self.pid.ffwd = self.pid.SP * self.blockMCP / self.qMaxRampPid * 100
        self.qpid = self.qMaxRampPid * self.pid.update() / 100
        if self.pcr.block_temp + 0.25 >= self.set_point:
            self.prepare_hold()
        self.peltier.mode = "heat"

    def ctrl_hold_under(self):
        self.qpid = self.qMaxHoldPid * self.pid.update() / 100
        if self.pcr.sample_temp <= self.set_point + self.calcCoolSmpWin:
            self.prepare_land_under()
        self.peltier.mode = "heat"

    def ctrl_hold(self):
        self.qpid = self.qMaxHoldPid * self.pid.update() / 100
        self.peltier.mode = "heat"

    def prepare_hold_under(self):
        self.pid.reset()
        self.pid.load(self.pid_const, "Hold Under")
        self.pid.SP = (self.set_point - self.calcCoolBlkOS) * 0.5
        self.pid.m = self.pid2.m
        self.pid.b = self.pid2.b
        self.pid.y = self.pid2.y
        self.pid.ffwd = -self.qHeatLoss / self.qMaxHoldPid * 100        
        self.stage = "Hold Under"        

    def run_control_stage(self):
        if self.stage == "Ramp Up":
            self.ctrl_ramp_up()

        elif self.stage == "Overshoot Over":
            self.ctrl_overshoot_over()

        elif self.stage == "Hold Over":
            self.ctrl_hold_over()

        elif self.stage == "Land Over":
            self.ctrl_land_over()

        elif self.stage == "Ramp Down":
            self.ctrl_ramp_down()

        elif self.stage == "Overshoot Under":
            self.ctrl_overshoot_under()

        elif self.stage == "Overshoot Under":
            self.ctrl_overshoot_under()

        elif self.stage == "Hold Under":
            self.ctrl_hold_under()

        elif self.stage == "Land Under":
            self.ctrl_land_under()

        elif self.stage == "Hold":
            self.ctrl_hold()

    def is_timer_fired(self):
        if round(self.time - self.checkpoint, 3) >= self.period:
            self.checkpoint = self.time
            return True
        return False

    def calc_feed_forward(self):
        return 0

    def ramp_to(self, new_set_point, sample_rate):
        if self.set_point == new_set_point:            
            return            
        self.pid.reset()
        self.start_time = self.time
        self.time_elapsed = 0
        self.set_point = new_set_point
        self.smpWinInRampUpFlag = False
        self.smpWinInRampDownFlag = False

        if new_set_point - self.pcr.block_temp > 2:
            self.ramp_dist = new_set_point - self.pcr.block_temp
            self.target_sample_rate = sample_rate / 100 * self.max_up_ramp
            self.ramp_time = self.ramp_dist / self.target_sample_rate
            self.prepare_ramp_up()
        elif new_set_point - self.pcr.block_temp < -2:
            self.ramp_dist = self.pcr.block_temp - new_set_point
            self.target_sample_rate = sample_rate / 100 * self.max_down_ramp
            self.ramp_time = self.ramp_dist / self.target_sample_rate
            self.prepare_ramp_down()
        else:
            self.prepare_hold()

    def output(self):
        Iset, Vset = self.peltier.output(   self.qpid, 
                                            self.pcr.heat_sink_temp,
                                            self.pcr.block_temp,
                                            self.maxHeatIset,
                                            self.maxCoolIset
                                        )
        self.pcr.Iset = Iset        
        self.pcr.Vset = Vset
 
    def update(self):
        self.update_feedback()
        self.run_control_stage()
        self.output()
    
    def tick(self, tick):
        self.time += tick
        if self.is_timer_fired():
            self.update()
class Lane_Follower:
    '''
    Control the robut to follow simple lanes.
    '''
    def __init__(self, node_name="lane_follower"):
        '''
        Initialize the lane follower.

        Parameters:
            node_name: The name of the lane follower node
        Returns:
            N/A
        '''
        rospy.init_node(node_name)

        #TOPICS
        lane_tracking_topic = rospy.get_namespace() + "lane_tracking"
        odom_topic = rospy.get_namespace() + "raw/odometry"
        desired_movement_topic = rospy.get_namespace() + "desired/movement"

        #Lane tracking data from subscriber. Data comes from the lane detection.
        #node.
        rospy.Subscriber(lane_tracking_topic, Float32MultiArray,
                         self._lane_tracking_callback)

        #Subscriber to the odometry data
        rospy.Subscriber(odom_topic, Odometry, self._odom_data_callback)

        #Publisher to the movement controller api
        self.desired_movement_pub = rospy.Publisher(desired_movement_topic,
                                                    Float32MultiArray,
                                                    queue_size=1)

        #Initialize the pid controller for tracking the lane
        #TODO: Add parameters for lane tracking pid to config file
        param_path = '/vision_params/lane_follower/pid/'

        #Get the pid controller parameters from the parameter server
        k_p = rospy.get_param(param_path + 'k_p')
        k_i = rospy.get_param(param_path + 'k_i')
        k_d = rospy.get_param(param_path + 'k_d')
        integral_min = rospy.get_param(param_path + 'integral_min')
        integral_max = rospy.get_param(param_path + 'integral_max')
        max_control_effort = rospy.get_param(param_path + 'max_control_effort')
        min_control_effort = rospy.get_param(param_path + 'min_control_effort')

        self.lane_tracking_pid_controller = PID_Controller(
            k_p=k_p,
            k_i=k_i,
            k_d=k_d,
            integral_min=integral_min,
            integral_max=integral_max,
            max_control_effort=max_control_effort,
            min_control_effort=min_control_effort)

        #Service the enable or disable line following.
        #This can be called to enable or disable the control of the robot based
        #on line following
        rospy.Service('enable_line_following', bool_call,
                      self._line_following_state)
        self.rate = rospy.Rate(10)

        #Initialize variables
        self.lane_detected = False
        self.camera_center_point = 0.0
        self.lane_center_point = 0.0

        self.lane_follow_enabled = True

        self.measured_velocity = 0.0
        self.measured_orientation = 0.0

        #Base velocity
        self.desired_velocity = 0.5

    def _line_following_state(self, req):
        '''
        The service callback to enable or disable line following.

        Parameters:
            req: The request message
        Returns:
            N/A
        '''
        self.lane_follow_enabled = req.val

        #Set the robots velocity to zero and hold the current orientation
        self.desired_movement_msg.data = [0.0, self.measured_orientation]
        self.desired_movement_pub.publish(self.desired_movement_msg)
        return bool_callResponse()

    def _lane_tracking_callback(self, lane_tracking_msg):
        '''
        Receive the lane tracking data msg. Unpack it

        Parameters:
            lane_tracking_msg. The lane tracking data message from the lane detection
                        node.
                        Format: [lane_detected_bool, set_point, measured_point]
        Returns:
            N/A
        '''
        ret = lane_tracking_msg.data[0]
        if (ret == 1.0):
            self.lane_detected = True

        else:
            self.lane_detected = False

        self.camera_center_point = lane_tracking_msg.data[1]
        self.lane_center_point = lane_tracking_msg.data[2]

    def _odom_data_callback(self, odom_msg):
        '''
        Callback function for the measured odometry data estimated.
        Unpack the data

        Parameters:
            measured_odom_msg: Odometry data message type nav_msgs/Odometry
        Returns:
            N/A
        '''
        self.measured_odom = odom_msg
        self.measured_velocity = self.measured_odom.twist.twist.linear.x

        #Orientation is the direction the robot faces
        #Convert from quaternion to euler
        quaternion = [
            self.measured_odom.pose.pose.orientation.x,
            self.measured_odom.pose.pose.orientation.y,
            self.measured_odom.pose.pose.orientation.z,
            self.measured_odom.pose.pose.orientation.w
        ]
        [roll, pitch, yaw] = euler_from_quaternion(quaternion)

        self.measured_orientation = yaw

    def run(self):
        '''
        Main loop for running the lane following.

        Parameters:
            N/A
        Returns:
            N/A
        '''
        self.desired_movement_msg = Float32MultiArray()
        try:

            while not rospy.is_shutdown():

                if (self.lane_follow_enabled):
                    #print(self.camera_center_point, self.lane_center_point)
                    #Get the pid control effort the determine how much to steer
                    control_effort, error = self.lane_tracking_pid_controller.update(\
                                    self.camera_center_point, self.lane_center_point)

                    #Translate the control effort into a yaw angle for the robot to
                    #face
                    adjustment_angle = self.measured_orientation + control_effort
                    if (adjustment_angle < -1 * math.pi):
                        self.desired_orientation = 2.0 * math.pi + adjustment_angle

                    elif (adjustment_angle > math.pi):
                        self.desired_orientation = adjustment_angle - 2.0 * math.pi

                    else:
                        self.desired_orientation = adjustment_angle

                    #Write the velocity and orientation to the robot.
                    velocity = self.desired_velocity - abs(0.005 * error)
                    self.desired_movement_msg.data = [
                        velocity, self.desired_orientation
                    ]
                    self.desired_movement_pub.publish(
                        self.desired_movement_msg)
                else:
                    self.desired_movement_msg.data = [
                        0.0, self.desired_orientation
                    ]

                self.rate.sleep()

        except rospy.ROSInterruptException:
            pass
class Attitude_Control:
    def __init__(self):
        rospy.Subscriber("/Outdoor_Blimp/setpoint_position", PoseStamped,
                         self.process_setpoint)
        rospy.Subscriber("/Outdoor_Blimp/imu/orientation", Vector3,
                         self.process_current)
        self.blimp = Blimp()
        self.set_yaw = 0
        self.set_pitch = 0
        self.set_roll = 0
        self.set_trans_x = 0
        self.set_trans_z = 0
        self.curr_pitch = 0
        self.curr_roll = 0
        self.curr_yaw = 0
        self.curr_trans_x = 0
        self.curr_trans_z = 0

        yaw_kp = rospy.get_param("~yaw_kp")
        yaw_ki = rospy.get_param("~yaw_ki")
        yaw_kd = rospy.get_param("~yaw_kd")

        trans_x_kp = rospy.get_param("~trans_x_kp")
        trans_x_ki = rospy.get_param("~trans_x_ki")
        trans_x_kd = rospy.get_param("~trans_x_kd")

        trans_z_kp = rospy.get_param("~trans_z_kp")
        trans_z_ki = rospy.get_param("~trans_z_ki")
        trans_z_kd = rospy.get_param("~trans_z_kd")

        self.curr_roll = 0

        self.yaw_control = PID_Controller(yaw_kp, yaw_ki, yaw_kd)

        self.trans_x_control = PID_Controller(trans_x_kp, trans_x_ki,
                                              trans_x_kd)

        self.trans_z_control = PID_Controller(trans_z_kp, trans_z_ki,
                                              trans_z_kd)

        self.servos = pigpio.pi()

        # self.pan_tilt = PanTilt(8, 25)

    def control_loop(self):

        # print("Set Yaw Angle: "+ repr(self.set_yaw))
        # print("Current Yaw Angle: "+repr(self.curr_yaw))
        mv = self.yaw_control.corrective_action(self.set_yaw, self.curr_yaw)
        self.blimp.motors.turn(-mv)
        # self.blimp.motors.downward()
        self.trans_x_control.corrective_action(self.set_trans_x,
                                               self.curr_trans_x)
        self.trans_z_control.corrective_action(self.set_trans_z,
                                               self.curr_trans_z)
        # self.pan_tilt()

    def pan_tilt(self):

        roll = self.curr_roll - self.set_roll
        pitch = self.curr_pitch - self.set_pitch

        servo_pitch_val = int(1600 + 8.88 * (pitch))
        servo_roll_val = int(1250 + 9.44 * roll + 0.0123 * roll**2)

        # print("Roll Val: %5d" % servo_roll_val)
        # print("Pitch Val %5d" % servo_pitch_val)

        servo_roll_val = min(servo_roll_val, 2250)
        servo_roll_val = max(servo_roll_val, 500)

        servo_pitch_val = min(servo_pitch_val, 2500)
        servo_pitch_val = max(servo_pitch_val, 800)

        self.servos.set_servo_pulsewidth(24, servo_roll_val)
        self.servos.set_servo_pulsewidth(23, servo_pitch_val)
        # self.pan_tilt.roll(6)

    def process_setpoint(self, poseStamped):

        pose = poseStamped.pose
        quart = pose.orientation
        trans = pose.position
        #exp_quart = [quart.x,quart.y,quart.z,quart.w]
        #r,p,y = euler_from_quaternion(exp_quart)
        self.set_trans_x = 0
        self.set_trans_z = 0
        self.set_yaw = quart.x
        self.set_pitch = 0
        self.set_roll = 0
        #print("Set Yaw Yaw Angle: "+repr(y))

    def process_current(self, orientation):

        #pose = poseStamped.pose
        #quart = pose.orientation
        #trans = pose.position
        #exp_quart = [quart.x,quart.y,quart.z,quart.w]
        #r,p,y = euler_from_quaternion(exp_quart)
        self.curr_trans_x = 0
        self.curr_trans_z = 0
        self.curr_yaw = orientation.x
        self.curr_pitch = orientation.y
        self.curr_roll = orientation.z
Exemple #15
0
tf = 30 # end time, (sec)
time = np.linspace(t0, tf, N)
dt = time[1] - time[0] # delta t, (sec)

##################################################################################
# Core simulation code
# Inital conditions (i.e., initial state vector)
y = [0, 0]
   #y[0] = initial altitude, (m)
   #y[1] = initial speed, (m/s)

# Initialize array to store values
soln = np.zeros((len(time),len(y)))

# Create instance of PID_Controller class
pid = PID_Controller()

# Set the Kp value of the controller
pid.setKP(kp)

# Set the Ki value of the controller
pid.setKI(ki)

# Set the Kd value of the controller
pid.setKD(kd)

# Set altitude target
r = 10 # meters
pid.setTarget(r)

# Simulate quadrotor motion
Exemple #16
0
class Movement_Controller:
    """
    Control the robot using PID controllers for linear velocity and
    direction orientation.
    """
    def __init__(self, node_name="movement_controller"):
        '''
        Initialize the movement controller as a ros node.

        Parameters:
            N/A
        Returns:
            N/A
        '''
        self.node_name = node_name
        rospy.init_node(node_name)

        #TOPICS - This format of name space is given for ability to simulate multiple
        #by providing different names

        measured_odom_topic = rospy.get_namespace() + "raw/odometry"
        pwm_topic = rospy.get_namespace() + "pwm"
        desired_movement_topic = rospy.get_namespace() + "desired/movement"

        #Initialize subscriber for raw odometry
        rospy.Subscriber(measured_odom_topic, Odometry,
                         self._odom_data_callback)

        #Initialize subscriber for desired_odometry
        rospy.Subscriber(desired_movement_topic, Float32MultiArray,
                         self._desired_movement_callback)

        #Initialize PWM controllers
        self._initialize_pid_controlers()

        self.pwm_pub = rospy.Publisher(pwm_topic,
                                       Int16MultiArray,
                                       queue_size=10)
        self.pwm_msg = Int16MultiArray()

        self.pid_timer = rospy.Rate(100)  #100Hz

        #Initialize publisher for writing PWM
        self.measured_velocity = 0.0
        self.desired_velocity = 0.0
        self.measured_orientation = 0.0
        self.desired_orientation = 0.0
        self.velocity_control = 0.0
        self.steering_control = 0.0

    def _initialize_pid_controlers(self):
        '''
        Initialize communication with the pid controllers.

        Parameters:
            N/A
        Returns:
            N/A
        '''

        pid_params = rospy.get_param("/pid")
        velocity_pid_params = pid_params['velocity']
        steering_pid_params = pid_params['steering']

        velocity_k_p = velocity_pid_params['k_p']
        velocity_k_i = velocity_pid_params['k_i']
        velocity_k_d = velocity_pid_params['k_d']

        self.velocity_pid_controller = PID_Controller(k_p=velocity_k_p,
                                                      k_i=velocity_k_i,
                                                      k_d=velocity_k_d,
                                                      integral_min=-10,
                                                      integral_max=10,
                                                      max_control_effort=255,
                                                      min_control_effort=-255)

        steering_k_p = steering_pid_params['k_p']
        steering_k_i = steering_pid_params['k_i']
        steering_k_d = steering_pid_params['k_d']
        self.steering_pid_controller = PID_Controller(k_p=steering_k_p,
                                                      k_i=steering_k_i,
                                                      k_d=steering_k_d,
                                                      max_control_effort=255,
                                                      min_control_effort=-255,
                                                      integral_min=-10,
                                                      integral_max=10,
                                                      angle_error=True)

        #Initialize the service for updating the pid controller gains
        rospy.Service('update_pid_gains', pid_gains,
                      self.handle_pid_gains_update)

        return

    def handle_pid_gains_update(self, req):
        '''
        Handler function for when a PID gain upate is requested. This will update
        the PIDS in the parameter server.

        Parameters:
            reg: The request message containing the desired gains
        Returns:
            N/A
        '''
        self.velocity_pid_controller.set_gains(req.velocity_k_p,
                                               req.velocity_k_i,
                                               req.velocity_k_d)
        self.steering_pid_controller.set_gains(req.steering_k_p,
                                               req.steering_k_i,
                                               req.steering_k_d)

        #Update the parameter server on the correct pid values
        rospy.set_param(
            "/pid/velocity", {
                'k_p': req.velocity_k_p,
                'k_i': req.velocity_k_i,
                'k_d': req.velocity_k_d
            })
        rospy.set_param(
            "/pid/steering", {
                'k_p': req.steering_k_p,
                'k_i': req.steering_k_i,
                'k_d': req.steering_k_d
            })

        return pid_gainsResponse()

    def _odom_data_callback(self, odom_msg):
        '''
        Callback function for the measured odometry data estimated.
        Unpack the data

        Parameters:
            measured_odom_msg: Odometry data message type nav_msgs/Odometry
        Returns:
            N/A
        '''
        self.measured_odom = odom_msg
        self.measured_velocity = self.measured_odom.twist.twist.linear.x

        #Orientation is the direction the robot faces
        #Convert from quaternion to euler
        quaternion = [
            self.measured_odom.pose.pose.orientation.x,
            self.measured_odom.pose.pose.orientation.y,
            self.measured_odom.pose.pose.orientation.z,
            self.measured_odom.pose.pose.orientation.w
        ]
        [roll, pitch, yaw] = euler_from_quaternion(quaternion)

        self.measured_orientation = yaw

    def _desired_movement_callback(self, desired_movement_msg):
        '''
        Callback function for the desired movement for the robot to hold. The
        message contains the desired velocity and orientation.

        Parameters:
            desired_movement_msg: The desired velocity (m/s) and orientation (rad/s).
                                  The message is of type std_msgs/Float32MultiArray.
                                  [desired_velocity, desired_orientation]
        Returns:
            N/A
        '''
        self.desired_velocity = desired_movement_msg.data[0]

        #Orientation is the direction the robot faces
        self.desired_orientation = desired_movement_msg.data[1]

    def map_control_efforts_to_pwms(self, vel_control_effort,
                                    steering_control_effort):
        '''
        Maps the control efforts for velocity and steering to individual motor
        PWMS.

        Parameters:
            vel_control_effort: Velocity Control effort output from PID.
            steering_control_effort: sterring control effor output from PID
        Returns:
            pwms: [pwm_1, pwm_2, pwm_3, pwm_4]
        '''
        vel_control = vel_control_effort
        steering_control = steering_control_effort

        pwm_1 = vel_control - steering_control
        pwm_2 = vel_control + steering_control
        pwm_3 = vel_control + steering_control
        pwm_4 = vel_control - steering_control

        pwms = [pwm_1, pwm_2, pwm_3, pwm_4]
        for i in range(4):
            if (pwms[i] < -255):
                pwms[i] = -255
            elif (pwms[i] > 255):
                pwms[i] = 255
        return pwms

    def run(self):
        '''
        Run the main loop of the movement controller. Receive desired and
        measured odometry data and control.

        Parameters:
            N/A
        Returns:
            N/A
        '''
        #Publish errror for debug
        error_pub = rospy.Publisher('pid_errors',
                                    Float32MultiArray,
                                    queue_size=1)
        error_msg = Float32MultiArray()

        try:
            while not rospy.is_shutdown():

                #Take the control effort output from PID controller and map to
                #PID's of individual motor
                self.velocity_control, velocity_error = self.velocity_pid_controller.update(
                    self.desired_velocity, self.measured_velocity)
                self.steering_control, steering_error = self.steering_pid_controller.update(
                    self.desired_orientation, self.measured_orientation)
                error_msg.data = [velocity_error, steering_error]
                error_pub.publish(error_msg)

                motor_pwms = self.map_control_efforts_to_pwms(
                    self.velocity_control, self.steering_control)

                self.pwm_msg.data = motor_pwms
                self.pwm_pub.publish(self.pwm_msg)

                #100Hz
                self.pid_timer.sleep()

        except rospy.ROSInterruptException:
            pass
    def __init__(self, node_name="lane_follower"):
        '''
        Initialize the lane follower.

        Parameters:
            node_name: The name of the lane follower node
        Returns:
            N/A
        '''
        rospy.init_node(node_name)

        #TOPICS
        lane_tracking_topic = rospy.get_namespace() + "lane_tracking"
        odom_topic = rospy.get_namespace() + "raw/odometry"
        desired_movement_topic = rospy.get_namespace() + "desired/movement"

        #Lane tracking data from subscriber. Data comes from the lane detection.
        #node.
        rospy.Subscriber(lane_tracking_topic, Float32MultiArray,
                         self._lane_tracking_callback)

        #Subscriber to the odometry data
        rospy.Subscriber(odom_topic, Odometry, self._odom_data_callback)

        #Publisher to the movement controller api
        self.desired_movement_pub = rospy.Publisher(desired_movement_topic,
                                                    Float32MultiArray,
                                                    queue_size=1)

        #Initialize the pid controller for tracking the lane
        #TODO: Add parameters for lane tracking pid to config file
        param_path = '/vision_params/lane_follower/pid/'

        #Get the pid controller parameters from the parameter server
        k_p = rospy.get_param(param_path + 'k_p')
        k_i = rospy.get_param(param_path + 'k_i')
        k_d = rospy.get_param(param_path + 'k_d')
        integral_min = rospy.get_param(param_path + 'integral_min')
        integral_max = rospy.get_param(param_path + 'integral_max')
        max_control_effort = rospy.get_param(param_path + 'max_control_effort')
        min_control_effort = rospy.get_param(param_path + 'min_control_effort')

        self.lane_tracking_pid_controller = PID_Controller(
            k_p=k_p,
            k_i=k_i,
            k_d=k_d,
            integral_min=integral_min,
            integral_max=integral_max,
            max_control_effort=max_control_effort,
            min_control_effort=min_control_effort)

        #Service the enable or disable line following.
        #This can be called to enable or disable the control of the robot based
        #on line following
        rospy.Service('enable_line_following', bool_call,
                      self._line_following_state)
        self.rate = rospy.Rate(10)

        #Initialize variables
        self.lane_detected = False
        self.camera_center_point = 0.0
        self.lane_center_point = 0.0

        self.lane_follow_enabled = True

        self.measured_velocity = 0.0
        self.measured_orientation = 0.0

        #Base velocity
        self.desired_velocity = 0.5
 def init_pid(self):
     self.pid = PID_Controller()
     self.pid.dt = self.period / 60 # dt is in minute
     self.pid2 = PID_Controller()
     self.pid2.dt = self.period / 60 # dt is in minute