forked from Telos4/RoboRally
121 lines
3.8 KiB
Python
121 lines
3.8 KiB
Python
import numpy as np
|
|
import math
|
|
import time
|
|
|
|
from controller import ControllerBase
|
|
|
|
|
|
class PIDController(ControllerBase):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.t = None
|
|
|
|
self.P_angle = 0.4
|
|
self.I_angle = 0.35
|
|
self.D_angle = 0.1
|
|
|
|
self.P_pos = 0.50
|
|
self.I_pos = 0.3
|
|
self.D_pos = 0.1
|
|
|
|
self.mode = 'combined'
|
|
|
|
self.e_angle_old = 0.0
|
|
self.e_pos_old = 0.0
|
|
|
|
self.i = 0.0
|
|
self.i_angle = 0.0
|
|
self.i_pos = 0.0
|
|
|
|
def set_target_position(self, target_pos):
|
|
super(PIDController, self).set_target_position(target_pos)
|
|
self.mode = 'combined'
|
|
self.e_angle_old = 0.0
|
|
self.e_pos_old = 0.0
|
|
|
|
self.i = 0.0
|
|
self.i_angle = 0.0
|
|
self.i_pos = 0.0
|
|
|
|
def compute_control(self, state):
|
|
# measure state
|
|
x_pred = state[1:]
|
|
|
|
if self.t is None:
|
|
dt = 0.1
|
|
else:
|
|
dt = time.time() - self.t # time since last control was applied
|
|
|
|
if self.mode == 'angle':
|
|
# compute angle such that robot faces to target point
|
|
target_angle = self.target_pos[2]
|
|
|
|
angles_unwrapped = np.unwrap([x_pred[2], target_angle]) # unwrap angle to avoid jump in data
|
|
|
|
e_angle = angles_unwrapped[0] - angles_unwrapped[1] # angle difference
|
|
p = self.P_angle * e_angle
|
|
self.i += self.I_angle * dt * (e_angle + self.e_angle_old)/2.0 # trapezoidal rule
|
|
d = self.D_angle * (e_angle - self.e_angle_old)/dt
|
|
|
|
u1 = p + self.i + d
|
|
u2 = - u1
|
|
|
|
self.e_angle_old = e_angle
|
|
|
|
elif self.mode == 'combined':
|
|
# compute angle such that robot faces to target point
|
|
v = self.target_pos[0:2] - x_pred[0:2]
|
|
target_angle = math.atan2(v[1], v[0])
|
|
|
|
angles_unwrapped = np.unwrap([x_pred[2], target_angle]) # unwrap angle to avoid jump in data
|
|
|
|
e_angle = angles_unwrapped[0] - angles_unwrapped[1] # angle difference
|
|
e_pos = np.linalg.norm(v[0:2])
|
|
|
|
if e_pos < 0.03:
|
|
self.mode = 'angle'
|
|
self.e_angle_old = 0
|
|
self.e_pos_old = 0
|
|
self.i_angle = 0
|
|
self.i_pos = 0
|
|
u1 = 0
|
|
u2 = 0
|
|
else:
|
|
forward = abs(e_angle) < np.pi/2.0
|
|
|
|
if not forward:
|
|
if e_angle > np.pi/2.0:
|
|
e_angle -= np.pi
|
|
else:
|
|
e_angle += np.pi
|
|
|
|
p_angle = self.P_angle * e_angle
|
|
self.i_angle += self.I_angle * dt * (e_angle + self.e_angle_old) / 2.0 # trapezoidal rule
|
|
d_angle = self.D_angle * (e_angle - self.e_angle_old) / dt
|
|
|
|
p_pos = self.P_pos * e_pos
|
|
self.i_pos += self.I_pos * dt * (e_pos + self.e_pos_old) / 2.0 # trapezoidal rule
|
|
d_pos = self.D_pos * (e_pos - self.e_pos_old) / dt
|
|
|
|
if forward:
|
|
u1 = p_angle + p_pos + self.i_angle + self.i_pos + d_angle + d_pos
|
|
u2 = - p_angle + p_pos - self.i_angle + self.i_pos - d_angle + d_pos
|
|
else:
|
|
u1 = p_angle - p_pos + self.i_angle - self.i_pos + d_angle - d_pos
|
|
u2 = - p_angle - p_pos - self.i_angle - self.i_pos - d_angle - d_pos
|
|
|
|
self.e_pos_old = e_pos
|
|
self.e_angle_old = e_angle
|
|
else:
|
|
u1 = 0.0
|
|
u2 = 0.0
|
|
return u1, u2
|
|
|
|
def stop(self):
|
|
super(PIDController, self).stop()
|
|
self.t = None
|
|
|
|
def apply_control(self, control):
|
|
self.t = time.time() # save time when the most recent control was applied
|
|
super(PIDController, self).apply_control(control)
|