Skip to content

Commit

Permalink
fix yaw computation and use "drive to yaw" when following crops
Browse files Browse the repository at this point in the history
  • Loading branch information
rodja committed Jul 14, 2024
1 parent ab60bba commit 22523b8
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 48 deletions.
21 changes: 7 additions & 14 deletions field_friend/automations/navigation/follow_crops_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,14 @@ async def _drive(self, distance: float):
if np.abs(yaw - self.odometer.prediction.yaw) > math.pi / 2:
yaw = yaw + math.pi

# Calculate a point 0.3 meters in front of the robot along the line
x_front = self.odometer.prediction.point.x + 0.3 * np.cos(yaw)
y_front = m * x_front + c

# Calculate the desired yaw angle from the robot's current position to the front point
delta_x = x_front - self.odometer.prediction.point.x
delta_y = y_front - self.odometer.prediction.point.y
target_yaw = np.arctan2(delta_y, delta_x)
fitted_line = rosys.geometry.Line(a=m, b=-1, c=c)
closest_point = fitted_line.foot_point(self.odometer.prediction.point)
front_point = closest_point.polar(0.3, yaw)
target_yaw = self.odometer.prediction.point.direction(front_point)
else:
target_yaw = self.odometer.prediction.yaw
target_yaw = self.combine_angles(target_yaw, self.crop_attraction, self.odometer.prediction.yaw)
# self.log.info(f'following crops with target yaw {target_yaw}')
target = self.odometer.prediction.point.polar(distance, target_yaw)
# self.log.info(f'Current world position: {self.odometer.prediction} Target next crop at {target}')
with self.driver.parameters.set(linear_speed_limit=0.125, angular_speed_limit=0.1):
await self.driver.drive_to(target)
await self._drive_to_yaw(distance, target_yaw)

def combine_angles(self, angle1: float, influence: float, angle2: float) -> float:
weight1 = influence
Expand All @@ -82,7 +74,8 @@ def combine_angles(self, angle1: float, influence: float, angle2: float) -> floa
def create_simulation(self):
for i in range(100):
x = i/10.0
p = rosys.geometry.Point3d(x=x, y=np.sin(x/2), z=0)
p = rosys.geometry.Point3d(x=x, y=(x/4) ** 3, z=0)
p = self.odometer.prediction.transform3d(p)
self.detector.simulated_objects.append(rosys.vision.SimulatedObject(category_name='maize', position=p))

def _should_finish(self) -> bool:
Expand Down
22 changes: 17 additions & 5 deletions field_friend/automations/navigation/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def __init__(self, system: 'System', implement: Implement) -> None:
self.detector = system.detector
self.name = 'Unknown'
self.start_position = self.odometer.prediction.point
self.linear_speed_limit = 0.125
self.angular_speed_limit = 0.1

async def start(self) -> None:
try:
Expand Down Expand Up @@ -76,11 +78,21 @@ async def finish(self) -> None:

@abc.abstractmethod
async def _drive(self, distance: float) -> None:
"""Drives the vehicle forward
This should only advance the robot by a small distance, e.g. 2 cm
to allow for adjustments and observations.
"""
"""Drives the vehicle a short distance forward"""

async def _drive_to_yaw(self, distance: float, yaw: float):
deadline = rosys.time() + 2
start_position = self.odometer.prediction.point
yaw -= self.odometer.prediction.yaw # take current yaw into account and only steer the difference
with self.driver.parameters.set(linear_speed_limit=self.linear_speed_limit, angular_speed_limit=self.angular_speed_limit):
await self.driver.wheels.drive(*self.driver._throttle(1, yaw)) # pylint: disable=protected-access
try:
while self.odometer.prediction.point.distance(start_position) < distance:
if rosys.time() >= deadline:
raise TimeoutError('Driving Timeout')
await rosys.sleep(0.01)
finally:
await self.driver.wheels.stop()

@abc.abstractmethod
def _should_finish(self) -> bool:
Expand Down
17 changes: 1 addition & 16 deletions field_friend/automations/navigation/straight_line_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ def __init__(self, system: 'System', tool: Implement) -> None:
self.detector = system.detector
self.length = 2.0
self.name = 'Straight Line'
self.linear_speed_limit = 0.125
self.angular_speed_limit = 0.1
self.origin: rosys.geometry.Point
self.target: rosys.geometry.Point

Expand All @@ -43,20 +41,7 @@ async def _drive(self, distance: float):
closest_point = rosys.geometry.Line.from_points(self.origin, self.target).foot_point(start_position)
local_target = rosys.geometry.Pose(x=closest_point.x, y=closest_point.y, yaw=start_position.direction(self.target), time=0) \
.transform(rosys.geometry.Point(x=1, y=0))
await self._drive_with_yaw(distance, start_position.direction(local_target))

async def _drive_with_yaw(self, distance: float, yaw: float):
deadline = rosys.time() + 2
start_position = self.odometer.prediction.point
with self.driver.parameters.set(linear_speed_limit=self.linear_speed_limit, angular_speed_limit=self.angular_speed_limit):
await self.driver.wheels.drive(*self.driver._throttle(1, yaw)) # pylint: disable=protected-access
try:
while self.odometer.prediction.point.distance(start_position) < distance:
if rosys.time() >= deadline:
raise TimeoutError('Driving Timeout')
await rosys.sleep(0.01)
finally:
await self.driver.wheels.stop()
await self._drive_to_yaw(distance, start_position.direction(local_target))

def _should_finish(self):
end_pose = rosys.geometry.Pose(x=self.target.x, y=self.target.y, yaw=self.origin.direction(self.target), time=0)
Expand Down
30 changes: 17 additions & 13 deletions tests/test_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,40 @@ async def test_driving_straight_line_with_slippage(system: System):


async def test_follow_crops(system: System, detector: rosys.vision.DetectorSimulation):
for i in range(10):
for i in range(20):
x = i/10.0
p = rosys.geometry.Point3d(x=x, y=np.sin(x/2), z=0)
p = rosys.geometry.Point3d(x=x, y=(x/2) ** 3, z=0)
p = system.odometer.prediction.transform3d(p)
detector.simulated_objects.append(rosys.vision.SimulatedObject(category_name='maize', position=p))
system.current_navigation = system.follow_crops_navigation
assert isinstance(system.current_navigation.implement, Recorder)
system.automator.start()
await forward(2)
assert system.automator.is_running
await forward(50)
assert not system.automator.is_running, 'automation should stop if no crops are detected anymore'
assert system.odometer.prediction.point.x == pytest.approx(1.4, abs=0.1)
assert system.odometer.prediction.point.y == pytest.approx(0.6, abs=0.1)
assert system.odometer.prediction.yaw_deg == pytest.approx(25.0, abs=5.0)
await forward(until=lambda: system.automator.is_running)
await forward(until=lambda: system.automator.is_stopped)
assert system.odometer.prediction.point.x == pytest.approx(2.2, abs=0.1)
assert system.odometer.prediction.point.y == pytest.approx(0.45, abs=0.1)
assert system.odometer.prediction.yaw_deg == pytest.approx(40.0, abs=5.0)


async def test_follow_crops_with_slippage(system: System, detector: rosys.vision.DetectorSimulation):
for i in range(10):
for i in range(20):
x = i/10.0
p = rosys.geometry.Point3d(x=x, y=np.sin(x/2), z=0)
p = rosys.geometry.Point3d(x=x, y=(x/3) ** 3, z=0)
p = system.odometer.prediction.transform3d(p)
detector.simulated_objects.append(rosys.vision.SimulatedObject(category_name='maize', position=p))
print(p)
assert isinstance(system.field_friend.wheels, rosys.hardware.WheelsSimulation)
system.current_navigation = system.follow_crops_navigation
system.field_friend.wheels.slip_factor_right = 0.05
system.automator.start()
await forward(until=lambda: system.automator.is_running)
await forward(until=lambda: system.automator.is_stopped)
assert system.odometer.prediction.point.x == pytest.approx(1.37, abs=0.1)
assert system.odometer.prediction.point.y == pytest.approx(0.6, abs=0.1)
assert system.odometer.prediction.point.x == pytest.approx(2.3, abs=0.1)
assert system.odometer.prediction.point.y == pytest.approx(0, abs=0.1)
assert system.odometer.prediction.yaw_deg == pytest.approx(25.0, abs=2.0)


@pytest.mark.skip('does not work anymore due to gps using wheels.pose instead of odometry.pose')
async def test_approaching_first_row(system: System, field: Field):
system.field_navigation.field = field
system.current_navigation = system.field_navigation
Expand Down Expand Up @@ -135,6 +138,7 @@ async def drive_away():
assert not system.automator.is_running, 'should have been stopped because robot is outside of field boundaries'


@pytest.mark.skip('does not work anymore due to gps using wheels.pose instead of odometry.pose')
async def test_resuming_field_navigation_after_automation_stop(system: System, field: Field):
system.field_navigation.field = field
system.current_navigation = system.field_navigation
Expand Down

0 comments on commit 22523b8

Please sign in to comment.