diff --git a/field_friend/automations/automation_watcher.py b/field_friend/automations/automation_watcher.py index c3e1a148..c2965706 100644 --- a/field_friend/automations/automation_watcher.py +++ b/field_friend/automations/automation_watcher.py @@ -7,6 +7,8 @@ from shapely.geometry import Point as ShapelyPoint from shapely.geometry import Polygon as ShapelyPolygon +from field_friend.localization import GeoPoint + if TYPE_CHECKING: from system import System @@ -105,8 +107,8 @@ def try_resume(self) -> None: self.log.info('resetting resume_delay') self.resume_delay = DEFAULT_RESUME_DELAY - def start_field_watch(self, field_boundaries: list[rosys.geometry.Point]) -> None: - self.field_polygon = ShapelyPolygon([(point.x, point.y) for point in field_boundaries]) + def start_field_watch(self, field_boundaries: list[GeoPoint]) -> None: + self.field_polygon = ShapelyPolygon([point.cartesian().tuple for point in field_boundaries]) self.field_watch_active = True def stop_field_watch(self) -> None: diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index 33fcd2b3..7f7a0767 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -25,6 +25,31 @@ def __init__(self, gnss: Gnss) -> None: self.needs_backup: bool = False + field = Field(id='dummy', name='dummy', points=[]) + for i, row_list in enumerate([[GeoPoint(lat=51.98316780897856, long=7.434212), GeoPoint(lat=51.98321232423919, long=7.434212)], + [GeoPoint(lat=51.983167808956075, long=7.4342847762085835), + GeoPoint(lat=51.98321232421671, long=7.434284776280727)], + [GeoPoint(lat=51.98316780888862, long=7.4343575524171674), + GeoPoint(lat=51.98321232414926, long=7.434357552561454)], + [GeoPoint(lat=51.9831678087762, long=7.4344303286257505), + GeoPoint(lat=51.98321232403683, long=7.43443032884218)], + [GeoPoint(lat=51.98316780861881, long=7.4345031048343335), + GeoPoint(lat=51.98321232387944, long=7.434503105122906)], + [GeoPoint(lat=51.98316780841646, long=7.434575881042917), + GeoPoint(lat=51.98321232367709, long=7.434575881403632)], + [GeoPoint(lat=51.983167808169135, long=7.434648657251498), + GeoPoint(lat=51.98321232342976, long=7.434648657684356)], + [GeoPoint(lat=51.983167807876846, long=7.434721433460079), + GeoPoint(lat=51.98321232313747, long=7.4347214339650805)], + [GeoPoint(lat=51.98316780753959, long=7.434794209668659), + GeoPoint(lat=51.983212322800206, long=7.434794210245803)], + [GeoPoint(lat=51.98316780715736, long=7.434866985877237), + GeoPoint(lat=51.983212322417984, long=7.434866986526525)], + [GeoPoint(lat=51.98316780673016, long=7.434939762085815), GeoPoint(lat=51.98321232199079, long=7.434939762807245)],]): + row = Row(id=f'row_{i}', name=f'row_{i}', points=list(row_list)) + field.rows.append(row) + self.fields.append(field) + def get_field(self, id_: str | None) -> Field | None: for field in self.fields: if field.id == id_: diff --git a/field_friend/automations/navigation/__init__.py b/field_friend/automations/navigation/__init__.py index 032c7c51..6cabac1f 100644 --- a/field_friend/automations/navigation/__init__.py +++ b/field_friend/automations/navigation/__init__.py @@ -1,7 +1,7 @@ -from .a_b_field_navigation import ABFieldNavigatoin from .a_b_line_navigation import ABLineNavigation from .coverage_navigation import CoverageNavigation +from .field_navigation import FieldNavigation from .follow_crops_navigation import FollowCropsNavigation from .navigation import Navigation, WorkflowException from .row_on_field_navigation import RowsOnFieldNavigation @@ -15,5 +15,5 @@ 'FollowCropsNavigation', 'CoverageNavigation', 'ABLineNavigation', - 'ABFieldNavigatoin', + 'FieldNavigation', ] diff --git a/field_friend/automations/navigation/a_b_field_navigation.py b/field_friend/automations/navigation/a_b_field_navigation.py deleted file mode 100644 index 9b593f76..00000000 --- a/field_friend/automations/navigation/a_b_field_navigation.py +++ /dev/null @@ -1,206 +0,0 @@ -import math -from enum import Enum, auto -from typing import TYPE_CHECKING, Any - -import rosys -from nicegui import ui -from rosys.geometry import Point, Pose, Spline - -from ..field import Field, Row -from ..implements import Implement, WeedingImplement -from .follow_crops_navigation import FollowCropsNavigation - -if TYPE_CHECKING: - from system import System - - -class ABFieldNavigatoin(FollowCropsNavigation): - class State(Enum): - APPROACHING_ROW_START = auto() - FOLLOWING_ROW = auto() - ROW_COMPLETED = auto() - FIELD_COMPLETED = auto() - - def __init__(self, system: 'System', tool: Implement) -> None: - super().__init__(system, tool) - - self.name = 'Field Navigation' - self.gnss = system.gnss - self.bms = system.field_friend.bms - self.automation_watcher = system.automation_watcher - self.field_provider = system.field_provider - - self.state = self.State.APPROACHING_ROW_START - self.field: Field | None = None - self.row_index = 0 - self.start_point: Point | None = None - self.end_point: Point | None = None - - @property - def current_row(self) -> Row: - assert self.field - return self.field.rows[self.row_index] - - async def prepare(self) -> bool: - await super().prepare() - if self.field is None: - rosys.notify('No field selected', 'negative') - return False - if not self.field.rows: - rosys.notify('No rows available', 'negative') - return False - if self.gnss.device is None: - rosys.notify('GNSS is not available', 'negative') - return False - for idx, row in enumerate(self.field.rows): - if not len(row.points) >= 2: - rosys.notify(f'Row {idx} on field {self.field.name} has not enough points', 'negative') - return False - self.current_row = self.get_nearest_row() - self.row_index = self.field.rows.index(row) - if self.state == self.State.FIELD_COMPLETED: - self.clear() - if self.state == self.State.FOLLOWING_ROW: - if self.current_row.line_segment().distance(self.odometer.prediction.point) > 0.1: - self.clear() - else: - self.plant_provider.clear() - - await rosys.sleep(1) # wait for GNSS to update - self.automation_watcher.start_field_watch(self.field.outline) - - # determine start and end point - self.set_start_and_end_points() - assert self.start_point is not None - assert self.end_point is not None - self.log.info(f'Start point: {self.start_point} End point: {self.end_point}') - - self.log.info(f'Activating {self.implement.name}...') - await self.implement.activate() - return True - - def _should_finish(self) -> bool: - return self.state == self.State.FIELD_COMPLETED - - async def finish(self) -> None: - await super().finish() - self.automation_watcher.stop_field_watch() - await self.implement.deactivate() - - def get_nearest_row(self) -> Row: - assert self.field is not None - assert self.gnss.device is not None - row = min(self.field.rows, key=lambda r: r.line_segment().line.foot_point( - self.odometer.prediction.point).distance(self.odometer.prediction.point)) - self.log.info(f'Nearest row is {row.name}') - self.row_index = self.field.rows.index(row) - return row - - def set_start_and_end_points(self): - assert self.field is not None - self.start_point = None - self.end_point = None - relative_point_0 = self.odometer.prediction.relative_point(self.current_row.points[0].cartesian()) - relative_point_1 = self.odometer.prediction.relative_point(self.current_row.points[-1].cartesian()) - if relative_point_0.x < 0 or relative_point_0.x < relative_point_1.x: - self.start_point = self.current_row.points[0].cartesian() - self.end_point = self.current_row.points[-1].cartesian() - elif relative_point_1.x < 0 or relative_point_1.x < relative_point_0.x: - self.start_point = self.current_row.points[-1].cartesian() - self.end_point = self.current_row.points[0].cartesian() - - def update_target(self) -> None: - pass - - async def _drive(self, distance: float) -> None: - assert self.field is not None - if self.state == self.State.APPROACHING_ROW_START: - # TODO only drive to row if we are not on any rows and near the row start - self.set_start_and_end_points() - await self._drive_to_row() - self.state = self.State.FOLLOWING_ROW - self.log.info(f'Following "{self.current_row.name}"...') - self.plant_provider.clear() - if self.state == self.State.FOLLOWING_ROW: - if not self.implement.is_active: - await self.implement.activate() - if self.odometer.prediction.point.distance(self.end_point) >= 0.1: - await super()._drive(distance) - else: # TODO: drive forward to clear the row and not drive over the plants - await self.implement.deactivate() - self.state = self.State.ROW_COMPLETED - if self.state == self.State.ROW_COMPLETED: - if self.current_row == self.field.rows[-1]: - self.state = self.State.FIELD_COMPLETED - await rosys.sleep(0.1) # wait for base class to finish navigation - else: - self.row_index += 1 - self.state = self.State.APPROACHING_ROW_START - - async def _drive_to_row(self): - self.log.info(f'Driving to "{self.current_row.name}"...') - assert self.field - target = self.start_point - direction = target.direction(self.end_point) - end_pose = Pose(x=target.x, y=target.y, yaw=direction) - spline = Spline.from_poses(self.odometer.prediction, end_pose) - await self.driver.drive_spline(spline) - self.log.info(f'Arrived at row {self.current_row.name} starting at {target}') - - def backup(self) -> dict: - return super().backup() | { - 'field_id': self.field.id if self.field else None, - 'row_index': self.row_index, - 'state': self.state.name, - } - - def restore(self, data: dict[str, Any]) -> None: - super().restore(data) - field_id = data.get('field_id', self.field_provider.fields[0].id if self.field_provider.fields else None) - self.field = self.field_provider.get_field(field_id) - self.row_index = data.get('row_index', 0) - self.state = self.State[data.get('state', self.State.APPROACHING_ROW_START.name)] - - def clear(self) -> None: - self.state = self.State.APPROACHING_ROW_START - self.row_index = 0 - - def settings_ui(self) -> None: - super().settings_ui() - field_selection = ui.select( - {f.id: f.name for f in self.field_provider.fields if len(f.rows) >= 1 and len(f.points) >= 3}, - on_change=lambda args: self._set_field(args.value), - label='Field')\ - .classes('w-32') \ - .tooltip('Select the field to work on') - field_selection.bind_value_from(self, 'field', lambda f: f.id if f else None) - - def _set_field(self, field_id: str) -> None: - field = self.field_provider.get_field(field_id) - if field is not None: - self.field = field - if isinstance(self.implement, WeedingImplement): - self.implement.cultivated_crop = field.crop - self.clear() - else: - if isinstance(self.implement, WeedingImplement): - self.implement.cultivated_crop = None - - def create_simulation(self) -> None: - self.detector.simulated_objects.clear() - if self.field is None: - return - for row in self.field.rows: - if len(row.points) < 2: - continue - cartesian = row.cartesian() - start = cartesian[0] - end = cartesian[-1] - length = start.distance(end) - self.log.info(f'Adding plants from {start} to {end} (length {length:.1f} m)') - crop_count = length / 0.15 - for i in range(int(crop_count)): - p = start.interpolate(end, (0.15 * i) / length) - p3d = rosys.geometry.Point3d(x=p.x, y=p.y, z=0) - plant = rosys.vision.SimulatedObject(category_name='maize', position=p3d) - self.detector.simulated_objects.append(plant) diff --git a/field_friend/automations/navigation/field_navigation.py b/field_friend/automations/navigation/field_navigation.py new file mode 100644 index 00000000..f50da249 --- /dev/null +++ b/field_friend/automations/navigation/field_navigation.py @@ -0,0 +1,276 @@ +from enum import Enum, auto +from random import randint +from typing import TYPE_CHECKING, Any + +import numpy as np +import rosys +from nicegui import ui +from rosys.geometry import Point, Pose, Spline + +from ..field import Field, Row +from ..implements import Implement, WeedingImplement +from .follow_crops_navigation import FollowCropsNavigation +from .straight_line_navigation import StraightLineNavigation + +if TYPE_CHECKING: + from system import System + + +class State(Enum): + APPROACHING_ROW_START = auto() + FOLLOWING_ROW = auto() + ROW_COMPLETED = auto() + CLEAR_ROW = auto() + FIELD_COMPLETED = auto() + + +class FieldNavigation(FollowCropsNavigation): + CLEAR_ROW_DISTANCE = 0.5 + + def __init__(self, system: 'System', implement: Implement) -> None: + super().__init__(system, implement) + + self.name = 'Field Navigation' + self.gnss = system.gnss + self.bms = system.field_friend.bms + self.automation_watcher = system.automation_watcher + self.field_provider = system.field_provider + + self._state = State.APPROACHING_ROW_START + self.row_index = 0 + self.start_point: Point | None = None + self.end_point: Point | None = None + + self.field: Field | None = None + self.clear_row_distance: float = self.CLEAR_ROW_DISTANCE + + @property + def current_row(self) -> Row: + assert self.field + return self.field.rows[self.row_index] + + async def prepare(self) -> bool: + await super().prepare() + # self.field = self.field_provider.fields[0] + if self.field is None: + rosys.notify('No field selected', 'negative') + return False + if not self.field.rows: + rosys.notify('No rows available', 'negative') + return False + if self.gnss.device is None: + rosys.notify('GNSS is not available', 'negative') + return False + for idx, row in enumerate(self.field.rows): + if not len(row.points) >= 2: + rosys.notify(f'Row {idx} on field {self.field.name} has not enough points', 'negative') + return False + # row = self.get_nearest_row() + # self.row_index = self.field.rows.index(row) + self.row_index = 0 + # if self._state == State.FIELD_COMPLETED: + # self.clear() + # else: + self._state = State.APPROACHING_ROW_START + self.plant_provider.clear() + + await rosys.sleep(1) # wait for GNSS to update + # self.automation_watcher.start_field_watch(self.field.outline) + + self.log.info(f'Activating {self.implement.name}...') + await self.implement.activate() + return True + + def _should_finish(self) -> bool: + return self._state == State.FIELD_COMPLETED + + async def finish(self) -> None: + await super().finish() + self.automation_watcher.stop_field_watch() + await self.implement.deactivate() + + def get_nearest_row(self) -> Row: + # currently not used, starts on row 0 + # assert self.field is not None + # assert self.gnss.device is not None + # row = min(self.field.rows, key=lambda r: r.line_segment().line.foot_point( + # self.odometer.prediction.point).distance(self.odometer.prediction.point)) + # self.log.info(f'Nearest row is {row.name}') + # self.row_index = self.field.rows.index(row) + # return row + return Row(id='dummy', name='dummy', points=[]) + + def set_start_and_end_points(self): + assert self.field is not None + self.start_point = None + self.end_point = None + relative_point_0 = self.odometer.prediction.distance(self.current_row.points[0].cartesian()) + relative_point_1 = self.odometer.prediction.distance(self.current_row.points[-1].cartesian()) + # self.log.info(f'Relative point 0: {relative_point_0} Relative point 1: {relative_point_1}') + if relative_point_0 < relative_point_1: + self.start_point = self.current_row.points[0].cartesian() + self.end_point = self.current_row.points[-1].cartesian() + elif relative_point_1 < relative_point_0: + self.start_point = self.current_row.points[-1].cartesian() + self.end_point = self.current_row.points[0].cartesian() + # self.log.info(f'Start point: {self.start_point} End point: {self.end_point}') + + def update_target(self) -> None: + self.origin = self.odometer.prediction.point + if self.end_point is None: + return + self.target = self.end_point + + async def _drive(self, distance: float) -> None: + assert self.field is not None + + # self.update_target() + if self._state == State.APPROACHING_ROW_START: + self._state = await self._run_approaching_row_start() + elif self._state == State.FOLLOWING_ROW: + self._state = await self._run_following_row(distance) + elif self._state == State.CLEAR_ROW: + self._state = await self._run_clear_row() + elif self._state == State.ROW_COMPLETED: + self._state = await self._run_row_completed() + + async def _run_approaching_row_start(self) -> State: + # TODO only drive to row if we are not on any rows and near the row start + self.set_start_and_end_points() + await self._wait_for_gnss() + await self._drive_to_row() + await self._wait_for_gnss() + # self.log.info(f'Following "{self.current_row.name}"...') + self.plant_provider.clear() + self.set_start_and_end_points() + self.update_target() + return State.FOLLOWING_ROW + + async def _run_following_row(self, distance: float) -> State: + if not self.implement.is_active: + await self.implement.activate() + if StraightLineNavigation._should_finish(self): # type: disable=W0212 + await self.implement.deactivate() + return State.CLEAR_ROW + await super()._drive(distance) + return State.FOLLOWING_ROW + + async def _run_clear_row(self) -> State: + target = self.odometer.prediction.transform_pose(rosys.geometry.Pose(x=self.clear_row_distance, y=0)) + await self._drive_towards_target(0.02, target) + await self._wait_for_gnss() + return State.ROW_COMPLETED + + async def _run_row_completed(self) -> State: + assert self.field + next_state: State = State.ROW_COMPLETED + if self.current_row == self.field.rows[-1]: + return State.FIELD_COMPLETED + self.row_index += 1 + next_state = State.APPROACHING_ROW_START + + # TODO: remove later, when any direction is possible + if self.row_index >= len(self.field.rows): + next_state = State.FIELD_COMPLETED + return next_state + + async def _wait_for_gnss(self, waiting_time: float = 2.0) -> None: + self.gnss.is_paused = False + # TODO: dont wait so long + await rosys.sleep(waiting_time) + await self.gnss.update_robot_pose() + self.gnss.is_paused = True + + async def _drive_to_row(self) -> None: + # self.log.info(f'Driving to "{self.current_row.name}"...') + assert self.field + assert self.start_point + assert self.end_point + direction = self.start_point.direction(self.end_point) + row_start_pose = Pose(x=self.start_point.x, y=self.start_point.y, yaw=direction) + target_pose = row_start_pose.transform_pose(Pose(x=-self.clear_row_distance)) + # self.log.info(f'Driving to {end_pose} from {self.odometer.prediction}...') + spline = Spline.from_poses(self.odometer.prediction, target_pose) + # self.log.info(f'Driving spline {spline}...') + await self.driver.drive_spline(spline) + # self.log.info(f'Arrived at row {self.current_row.name} starting at {target}') + + def backup(self) -> dict: + return super().backup() | { + 'field_id': self.field.id if self.field else None, + 'row_index': self.row_index, + 'state': self._state.name, + 'clear_row_distance': self.clear_row_distance, + } + + def restore(self, data: dict[str, Any]) -> None: + super().restore(data) + field_id = data.get('field_id', self.field_provider.fields[0].id if self.field_provider.fields else None) + self.field = self.field_provider.get_field(field_id) + self.row_index = data.get('row_index', 0) + self._state = State[data.get('state', State.APPROACHING_ROW_START.name)] + self.clear_row_distance = data.get('clear_row_distance', self.CLEAR_ROW_DISTANCE) + + def clear(self) -> None: + return + self._state = State.APPROACHING_ROW_START + self.row_index = 0 + + def settings_ui(self) -> None: + super().settings_ui() + field_selection = ui.select( + {f.id: f.name for f in self.field_provider.fields if len(f.rows) >= 1}, + on_change=lambda args: self._set_field(args.value), + label='Field')\ + .classes('w-32') \ + .tooltip('Select the field to work on') + field_selection.bind_value_from(self, 'field', lambda f: f.id if f else None) + ui.number('Clear Row Distance', step=0.05, min=0.01, max=5.0, format='%.2f', on_change=self.request_backup) \ + .props('dense outlined') \ + .classes('w-24') \ + .bind_value(self, 'clear_row_distance') \ + .tooltip(f'Safety distance to row in m (default: {self.CLEAR_ROW_DISTANCE:.2f})') + + def developer_ui(self) -> None: + # super().developer_ui() + ui.label('').bind_text_from(self, '_state', lambda state: f'State: {state.name}') + ui.label('').bind_text_from(self, 'row_index', lambda row_index: f'Row Index: {row_index}') + ui.label('').bind_text_from(self, 'start_point', lambda start_point: f'Start Point: {start_point}') + ui.label('').bind_text_from(self, 'end_point', lambda end_point: f'End Point: {end_point}') + + def _set_field(self, field_id: str) -> None: + field = self.field_provider.get_field(field_id) + if field is not None: + self.field = field + # if isinstance(self.implement, WeedingImplement): + # self.implement.cultivated_crop = field.crop + self.clear() + else: + if isinstance(self.implement, WeedingImplement): + self.implement.cultivated_crop = None + + def create_simulation(self, crop_distance: float = 0.5) -> None: + self.detector.simulated_objects.clear() + if self.field is None: + return + for row in self.field.rows: + if len(row.points) < 2: + continue + cartesian = row.cartesian() + start = cartesian[0] + end = cartesian[-1] + length = start.distance(end) + # self.log.info(f'Adding plants from {start} to {end} (length {length:.1f} m)') + crop_count = length / crop_distance + for i in range(int(crop_count)): + p = start.interpolate(end, (crop_distance * i) / length) + p3d = rosys.geometry.Point3d(x=p.x, y=p.y, z=0) + plant = rosys.vision.SimulatedObject(category_name='maize', position=p3d) + self.detector.simulated_objects.append(plant) + + for _ in range(1, 7): + p = start.polar(crop_distance * i + randint(-5, 5) * 0.01, + start.direction(end)) \ + .polar(randint(-15, 15)*0.01, self.odometer.prediction.yaw + np.pi/2) + self.detector.simulated_objects.append(rosys.vision.SimulatedObject(category_name='weed', + position=rosys.geometry.Point3d(x=p.x, y=p.y, z=0))) diff --git a/field_friend/automations/navigation/follow_crops_navigation.py b/field_friend/automations/navigation/follow_crops_navigation.py index 34e8741f..e085dbe7 100644 --- a/field_friend/automations/navigation/follow_crops_navigation.py +++ b/field_friend/automations/navigation/follow_crops_navigation.py @@ -57,9 +57,6 @@ async def _drive(self, distance: float) -> None: # flip if it is pointing backwards if np.abs(yaw - self.odometer.prediction.yaw) > math.pi / 2: yaw = yaw + math.pi - if np.abs(yaw - self.odometer.prediction.yaw) > 0.2: - self.log.error(f'Yaw difference is too high: {np.abs(yaw - self.odometer.prediction.yaw)}') - return fitted_line = rosys.geometry.Line(a=m, b=-1, c=c) closest_point = fitted_line.foot_point(self.odometer.prediction.point) target = rosys.geometry.Pose(x=closest_point.x, y=closest_point.y, yaw=yaw) diff --git a/field_friend/interface/components/field_object.py b/field_friend/interface/components/field_object.py index 3921419b..aab0d453 100644 --- a/field_friend/interface/components/field_object.py +++ b/field_friend/interface/components/field_object.py @@ -2,6 +2,8 @@ from nicegui.elements.scene_objects import Box, Curve, Cylinder, Extrusion, Group from rosys.geometry import Spline +from field_friend.localization import GeoPoint + from ...automations import Field, FieldProvider @@ -31,7 +33,8 @@ def create_fence(self, start, end): Box(length, height, depth).move(x=center_x, y=center_y, z=height / 2 + 0.2).with_name('field_').material('#8b4513').rotate(np.pi/2, 0, angle) Box(length, height, depth).move(x=center_x, y=center_y, - z=height / 2 + 0.5).with_name('field_').material('#8b4513').rotate(np.pi/2, 0, angle) # Convert angle from radians to degrees + # Convert angle from radians to degrees + z=height / 2 + 0.5).with_name('field_').material('#8b4513').rotate(np.pi/2, 0, angle) Box(length, height, depth).move(x=center_x, y=center_y, z=height / 2 + 0.8).with_name('field_').material('#8b4513').rotate(np.pi/2, 0, angle) Cylinder(0.1, 0.1, 1.0).move(x=start[0], y=start[1], z=0.5).with_name( @@ -45,7 +48,7 @@ def update(self, active_field: Field | None) -> None: [obj.delete() for obj in list(self.scene.objects.values()) if obj.name and obj.name.startswith('row_')] if active_field: field = active_field - outline = [[point.x, point.y] for point in field.outline] + outline = [point.cartesian().tuple for point in field.outline] if len(outline) > 1: # Make sure there are at least two points to form a segment for i in range(len(outline)): start = outline[i] diff --git a/field_friend/interface/components/leaflet_map.py b/field_friend/interface/components/leaflet_map.py index 0fcb8a38..4a9065bc 100644 --- a/field_friend/interface/components/leaflet_map.py +++ b/field_friend/interface/components/leaflet_map.py @@ -165,8 +165,8 @@ def update_robot_position(self, position: GeoPoint, dialog=None) -> None: dialog.close() self.gnss.relocate(position) self.robot_marker = self.robot_marker or self.m.marker(latlng=position.tuple) - icon = 'L.icon({iconUrl: "assets/robot_position_side.png", iconSize: [50,50], iconAnchor:[20,20]})' - self.robot_marker.run_method(':setIcon', icon) + # icon = 'L.icon({iconUrl: "assets/robot_position_side.png", iconSize: [50,50], iconAnchor:[20,20]})' + # self.robot_marker.run_method(':setIcon', icon) self.robot_marker.move(*position.tuple) def zoom_to_robot(self) -> None: diff --git a/field_friend/interface/pages/dev_page.py b/field_friend/interface/pages/dev_page.py index 89bc98eb..65de5d7c 100644 --- a/field_friend/interface/pages/dev_page.py +++ b/field_friend/interface/pages/dev_page.py @@ -19,4 +19,6 @@ def page() -> None: self.content() def content(self) -> None: + with ui.card(): + self.system.field_navigation.developer_ui() development(self.system) diff --git a/field_friend/system.py b/field_friend/system.py index 90badfdb..c14804e1 100644 --- a/field_friend/system.py +++ b/field_friend/system.py @@ -27,9 +27,9 @@ WeedingScrew, ) from .automations.navigation import ( - ABFieldNavigatoin, ABLineNavigation, CoverageNavigation, + FieldNavigation, FollowCropsNavigation, Navigation, RowsOnFieldNavigation, @@ -151,18 +151,18 @@ def watch_robot() -> None: self.automation_watcher = AutomationWatcher(self) self.monitoring = Recorder(self) self.timelapse_recorder = rosys.analysis.TimelapseRecorder() - self.timelapse_recorder.frame_info_builder = lambda _: f'{self.version}, { - self.current_navigation.name}, tags: {", ".join(self.plant_locator.tags)}' + self.timelapse_recorder.frame_info_builder = lambda _: f'''{self.version},\ { + self.current_navigation.name}, tags: {", ".join(self.plant_locator.tags)}''' rosys.NEW_NOTIFICATION.register(self.timelapse_recorder.notify) rosys.on_startup(self.timelapse_recorder.compress_video) # NOTE: cleanup JPEGs from before last shutdown - self.field_navigation = RowsOnFieldNavigation(self, self.monitoring) + self.rows_on_field_navigation = RowsOnFieldNavigation(self, self.monitoring) self.straight_line_navigation = StraightLineNavigation(self, self.monitoring) self.follow_crops_navigation = FollowCropsNavigation(self, self.monitoring) self.coverage_navigation = CoverageNavigation(self, self.monitoring) self.a_b_line_navigation = ABLineNavigation(self, self.monitoring) - self.field_navigation = ABFieldNavigatoin(self, self.monitoring) + self.field_navigation = FieldNavigation(self, self.monitoring) - self.navigation_strategies = {n.name: n for n in [self.field_navigation, + self.navigation_strategies = {n.name: n for n in [self.rows_on_field_navigation, self.straight_line_navigation, self.follow_crops_navigation, self.coverage_navigation,