-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathmotor_group.py
More file actions
158 lines (131 loc) · 5.18 KB
/
motor_group.py
File metadata and controls
158 lines (131 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from .encoded_motor import EncodedMotor
from .controller import Controller
from .pid import PID
from .timeout import Timeout
import time
class MotorGroup(EncodedMotor):
def __init__(self, *motors: EncodedMotor):
"""
A wrapper class for multiple motors, allowing them to be treated as one motor.
:param motors: The motors to add to this group
:type motors: tuple<EncodedMotor>
"""
self.motors = []
for motor in motors:
self.add_motor(motor)
def add_motor(self, motor:EncodedMotor):
"""
:param motor: The motor to add to this group
:type motor: EncodedMotor
"""
self.motors.append(motor)
def remove_motor(self, motor:EncodedMotor):
"""
:param motor: The motor to remove from this group
:type motor: EncodedMotor
"""
try:
self.motors.remove(motor)
except:
print("Failed to remove motor from Motor Group")
def set_effort(self, effort: float):
"""
:param effort: The effort to set all motors in this group to, from -1 to 1
:type effort: float
"""
for motor in self.motors:
motor.set_effort(effort)
def get_position(self) -> float:
"""
:return: The average position of all motors in this group, in revolutions, relative to the last time reset was called.
:rtype: float
"""
avg = 0
for motor in self.motors:
avg += motor.get_position()
return avg / len(self.motors)
def get_position_counts(self) -> int:
"""
:return: The average position of all motors in this group, in encoder counts, relative to the last time reset was called.
:rtype: int
"""
avg = 0
for motor in self.motors:
avg += motor.get_position_counts()
return round(avg / len(self.motors))
def reset_encoder_position(self):
"""
Resets the encoder position of all motors in this group back to zero.
"""
for motor in self.motors:
motor.reset_encoder_position()
def get_speed(self) -> float:
"""
:return: The average speed of the motors, in rpm
:rtype: float
"""
avg = 0
for motor in self.motors:
avg += motor.get_speed()
return avg / len(self.motors)
def set_speed(self, target_speed_rpm: float = None):
"""
Sets target speed (in rpm) to be maintained passively by all motors in this group
Call with no parameters to turn off speed control
:param target_speed_rpm: The target speed for these motors in rpm, or None
:type target_speed_rpm: float, or None
"""
for motor in self.motors:
motor.set_speed(target_speed_rpm)
def set_speed_controller(self, new_controller):
"""
:param new_controller: The new Controller for speed control
:type new_controller: Controller
"""
for motor in self.motors:
motor.set_speed_controller(new_controller)
def rotate(self, degrees: float, max_effort: float = 0.5, timeout: float = None, main_controller: Controller = None) -> bool:
"""
Rotate all motors in this group by some number of degrees, and exit function when distance has been traveled.
Max_effort is bounded from -1 (reverse at full speed) to 1 (forward at full speed)
:param degrees: The distance for the motor to rotate (In Degrees)
:type degrees: float
:param max_effort: The max effort for which the robot to travel (Bounded from -1 to 1). Default is half effort forward
:type max_effort: float
:param timeout: The amount of time before the robot stops trying to move forward and continues to the next step (In Seconds)
:type timeout: float
:param main_controller: The main controller, for handling the motor's rotation
:type main_controller: Controller
:return: if the distance was reached before the timeout
:rtype: bool
"""
# ensure effort is always positive while distance could be either positive or negative
if max_effort < 0:
max_effort *= -1
degrees *= -1
time_out = Timeout(timeout)
starting = self.get_position_counts()
degrees *= self._encoder.resolution/360
if main_controller is None:
main_controller = PID(
kp = 58.5,
ki = 38.025,
kd = 16.0875,
min_output = 0.3,
max_output = max_effort,
max_integral = 0.04,
tolerance = 0.01,
tolerance_count = 3,
)
while True:
# calculate the distance traveled
delta = self.get_position_counts() - starting
# PID for distance
error = degrees - delta
effort = main_controller.update(error)
if main_controller.is_done() or time_out.is_done():
break
self.set_effort(effort)
time.sleep(0.01)
self.set_effort(0)
return not time_out.is_done()