Skip to content

Commit e954656

Browse files
authored
Merge pull request #267 from thedropbears/revert-218-vision-limits
2 parents 5e9f53a + 82c18d5 commit e954656

File tree

3 files changed

+44
-91
lines changed

3 files changed

+44
-91
lines changed

components/vision.py

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222

2323
from components.chassis import ChassisComponent
2424
from utilities.caching import HasPerLoopCache, cache_per_loop
25-
from utilities.functions import clamp
2625
from utilities.game import APRILTAGS, apriltag_layout
2726
from utilities.rev import configure_through_bore_encoder
27+
from utilities.scalers import scale_value
2828

2929

3030
@wpiutil.wpistruct.make_wpistruct
@@ -37,12 +37,6 @@ class VisibleTag:
3737
range: float
3838

3939

40-
@dataclass
41-
class ServoOffsets:
42-
neutral: Rotation2d
43-
full_range: Rotation2d
44-
45-
4640
class VisualLocalizer(HasPerLoopCache):
4741
"""
4842
This localizes the robot from AprilTags on the field,
@@ -55,8 +49,12 @@ class VisualLocalizer(HasPerLoopCache):
5549
# Time since the last target sighting we allow before informing drivers
5650
TIMEOUT = 1.0 # s
5751

52+
# Calibration constants for servo and encoder
53+
# range between +/-135 deg
54+
SERVO_HALF_ANGLE = math.radians(135)
55+
5856
CAMERA_FOV = math.radians(
59-
68
57+
65
6058
) # photon vision says 69.8, but we are being conservative
6159
CAMERA_MAX_RANGE = 4.0 # m
6260

@@ -77,46 +75,33 @@ def __init__(
7775
# The name of the camera in PhotonVision.
7876
name: str,
7977
# Position of the camera relative to the center of the robot
80-
turret_pos: Translation3d,
81-
# The turret rotation at its neutral position (ie centred).
82-
turret_rot: Rotation2d,
83-
# The camera relative to the turret (ie without servo rotation)
84-
camera_offset: Translation3d,
85-
# The camera pitch on the mount, relative to horizontal
86-
camera_pitch: float,
78+
pos: Translation3d,
79+
# The camera rotation at its neutral position (ie centred).
80+
rot: Rotation3d,
8781
servo_id: int,
88-
servo_offsets: ServoOffsets,
82+
servo_offset: Rotation2d,
8983
encoder_id: int,
9084
encoder_offset: Rotation2d,
91-
# Encoder rotations at min and max of desired rotation range
92-
rotation_range: tuple[Rotation2d, Rotation2d],
9385
field: wpilib.Field2d,
9486
data_log: wpiutil.log.DataLog,
9587
chassis: ChassisComponent,
9688
) -> None:
9789
super().__init__()
9890
self.camera = PhotonCamera(name)
99-
self.encoder = wpilib.DutyCycleEncoder(encoder_id, math.pi, -math.pi)
91+
self.encoder = wpilib.DutyCycleEncoder(encoder_id, math.tau, 0)
10092
configure_through_bore_encoder(self.encoder)
10193
# Offset of encoder in radians when facing forwards (the desired zero)
10294
# To find this value, manually point the camera forwards and record the encoder value
10395
# This has nothing to do with the servo - do it by hand!!
10496
self.encoder_offset = encoder_offset
10597

106-
# To find the servo offsets, command the servo to neutral in test mode and record the encoder value
107-
# Repeat for full range
108-
self.servo_offsets = servo_offsets
109-
110-
relative_rotations = [(r - encoder_offset) for r in rotation_range]
111-
self.min_rotation, self.max_rotation = relative_rotations
98+
# To find the servo offset, command the servo to neutral in test mode and record the encoder value
99+
self.servo_offset = servo_offset
112100

113101
self.servo = wpilib.Servo(servo_id)
114-
self.pos = turret_pos
115-
self.robot_to_turret = Transform3d(turret_pos, Rotation3d(turret_rot))
116-
self.robot_to_turret_2d = Transform2d(turret_pos.toTranslation2d(), turret_rot)
117-
self.turret_to_camera = Transform3d(
118-
camera_offset, Rotation3d(roll=0.0, pitch=camera_pitch, yaw=0.0)
119-
)
102+
self.pos = pos
103+
self.robot_to_turret = Transform3d(pos, rot)
104+
self.robot_to_turret_2d = Transform2d(pos.toTranslation2d(), rot.toRotation2d())
120105
self.turret_rotation_buffer = TimeInterpolatableRotation2dBuffer(2.0)
121106

122107
self.last_timestamp = -1.0
@@ -176,8 +161,7 @@ def visible_tags(self) -> list[VisibleTag]:
176161
distance = turret_to_tag.norm()
177162
relative_facing = tag.pose.toPose2d().rotation() - turret_to_tag.angle()
178163
if (
179-
(relative_bearing - self.min_rotation).radians() >= 0.0
180-
and (self.max_rotation - relative_bearing).radians() >= 0.0
164+
abs(relative_bearing.radians()) <= self.SERVO_HALF_ANGLE
181165
and abs(relative_facing.degrees()) > 100
182166
and distance < self.CAMERA_MAX_RANGE
183167
):
@@ -192,53 +176,41 @@ def desired_turret_rotation(self) -> Rotation2d:
192176
# Read encoder angle and account for offset
193177
return self.relative_bearing_to_best_cluster()
194178

195-
@feedback
196-
def desired_servo_rotation(self) -> Rotation2d:
197-
return self.turret_to_servo(self.desired_turret_rotation())
198-
199179
def turret_to_servo(self, turret: Rotation2d) -> Rotation2d:
200-
return turret - (self.servo_offsets.neutral - self.encoder_offset)
180+
return turret - (self.servo_offset - self.encoder_offset)
201181

202182
@property
203183
def turret_rotation(self) -> Rotation2d:
204184
return self.raw_encoder_rotation() - self.encoder_offset
205185

206186
def robot_to_camera(self, timestamp: float) -> Transform3d:
187+
robot_to_turret_rotation = self.robot_to_turret.rotation()
207188
turret_rotation = self.turret_rotation_buffer.sample(timestamp)
208189
if turret_rotation is None:
209190
return self.robot_to_turret
210-
r2t = self.robot_to_turret.translation()
211-
t2c = (
212-
self.turret_to_camera.translation()
213-
.rotateBy(self.turret_to_camera.rotation())
214-
.rotateBy(Rotation3d(turret_rotation))
215-
)
216-
trans = r2t + t2c
217-
rot = (
218-
self.robot_to_turret.rotation()
219-
.rotateBy(Rotation3d(turret_rotation))
220-
.rotateBy(self.turret_to_camera.rotation())
191+
return Transform3d(
192+
self.robot_to_turret.translation(),
193+
Rotation3d(
194+
robot_to_turret_rotation.x,
195+
robot_to_turret_rotation.y,
196+
robot_to_turret_rotation.z + turret_rotation.radians(),
197+
),
221198
)
222-
return Transform3d(trans, rot)
223199

224200
def zero_servo_(self) -> None:
225201
# ONLY CALL THIS IN TEST MODE!
226202
# This is used to put the servo in a neutral position to record the encoder value at that point
227203
self.servo.set(0.5)
228204

229-
def full_range_servo_(self) -> None:
230-
# ONLY CALL THIS IN TEST MODE!
231-
# This is used to put the servo to the full range position to record the encoder value at that point
232-
self.servo.set(1.0)
233-
234205
def execute(self) -> None:
235-
desired = self.turret_to_servo(self.desired_turret_rotation())
236-
neutral = self.servo_offsets.neutral
237-
full = self.servo_offsets.full_range
238-
half_range = full - neutral
239-
delta = desired - neutral
240206
self.servo.set(
241-
clamp((delta.radians() / half_range.radians() + 1.0) / 2.0, 0.0, 1.0)
207+
scale_value(
208+
self.turret_to_servo(self.desired_turret_rotation()).radians(),
209+
-self.SERVO_HALF_ANGLE,
210+
self.SERVO_HALF_ANGLE,
211+
0.0,
212+
1.0,
213+
)
242214
)
243215

244216
self.turret_rotation_buffer.addSample(

physics.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,10 @@ def update_sim(self, now: float, tm_diff: float) -> None:
232232
self.vision_encoder_sim.set(
233233
constrain_angle(
234234
(
235-
(
236-
self.visual_localiser.servo_offsets.full_range
237-
- self.visual_localiser.servo_offsets.neutral
238-
)
239-
* (2.0 * self.visual_localiser.servo.getPosition() - 1.0)
240-
+ self.visual_localiser.servo_offsets.neutral
241-
).radians()
235+
(2.0 * self.visual_localiser.servo.getPosition() - 1.0)
236+
* self.visual_localiser.SERVO_HALF_ANGLE
237+
)
238+
+ self.visual_localiser.servo_offset.radians()
242239
)
243240
)
244241

robot.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import math
2-
31
import magicbot
42
import ntcore
53
import wpilib
@@ -17,7 +15,7 @@
1715
from components.intake import IntakeComponent
1816
from components.led_component import LightStrip
1917
from components.shooter import ShooterComponent
20-
from components.vision import ServoOffsets, VisualLocalizer
18+
from components.vision import VisualLocalizer
2119
from components.wrist import WristComponent
2220
from controllers.algae_measurement import AlgaeMeasurement
2321
from controllers.algae_shooter import AlgaeShooter
@@ -94,6 +92,7 @@ def createObjects(self) -> None:
9492
self.vision_name = "ardu_cam"
9593
self.vision_encoder_id = DioChannel.VISION_ENCODER
9694
self.vision_servo_id = PwmChannel.VISION_SERVO
95+
9796
if wpilib.RobotController.getSerialNumber() == RioSerialNumber.TEST_BOT:
9897
self.chassis_swerve_config = SwerveConfig(
9998
drive_ratio=(14.0 / 50.0) * (25.0 / 19.0) * (15.0 / 45.0),
@@ -121,14 +120,6 @@ def createObjects(self) -> None:
121120
self.vision_rot = Rotation3d.fromDegrees(0, -20.0, 0)
122121
self.vision_servo_offset = Rotation2d(3.107)
123122
self.vision_encoder_offset = Rotation2d(3.052)
124-
# TODO set on test bot
125-
self.vision_camera_offset = Translation3d(0.05, 0, 0)
126-
self.vision_camera_pitch = math.radians(-20.0)
127-
self.vision_servo_offsets = ServoOffsets(
128-
neutral=Rotation2d(0.5757), full_range=Rotation2d(1.9234)
129-
)
130-
self.vision_rotation_range = (Rotation2d(1.525), Rotation2d(4.33))
131-
132123
else:
133124
self.chassis_swerve_config = SwerveConfig(
134125
drive_ratio=(14.0 / 50.0) * (27.0 / 17.0) * (15.0 / 45.0),
@@ -152,15 +143,10 @@ def createObjects(self) -> None:
152143
# metres between centre of front and back wheels
153144
self.chassis_wheel_base = 0.517
154145

155-
self.vision_turret_pos = Translation3d(-0.030, -0.300, 0.660)
156-
self.vision_turret_rot = Rotation2d.fromDegrees(-90.0)
157-
self.vision_camera_offset = Translation3d(0.021, 0, 0)
158-
self.vision_camera_pitch = math.radians(10.0)
159-
self.vision_encoder_offset = Rotation2d(0.9796)
160-
self.vision_servo_offsets = ServoOffsets(
161-
neutral=Rotation2d(0.044), full_range=Rotation2d(1.37)
162-
)
163-
self.vision_rotation_range = (Rotation2d(4.611), Rotation2d(2.020))
146+
self.vision_pos = Translation3d(-0.050, -0.300, 0.660)
147+
self.vision_rot = Rotation3d.fromDegrees(0, 10.0, -135.0)
148+
self.vision_servo_offset = Rotation2d(6.235)
149+
self.vision_encoder_offset = Rotation2d(0.183795)
164150

165151
self.coast_button = wpilib.DigitalInput(DioChannel.SWERVE_COAST_SWITCH)
166152
self.coast_button_pressed_event = wpilib.event.BooleanEvent(
@@ -287,10 +273,8 @@ def testPeriodic(self) -> None:
287273
# self.coral_placer_component.execute()
288274
self.shooter_component.execute()
289275
self.injector_component.execute()
290-
if self.gamepad.getLeftStickButton():
276+
if self.gamepad.getStartButton():
291277
self.vision.zero_servo_()
292-
elif self.gamepad.getRightStickButton():
293-
self.vision.full_range_servo_()
294278
else:
295279
self.vision.execute()
296280
self.wrist.execute()

0 commit comments

Comments
 (0)