diff --git a/config/f15_config_f15/hardware.py b/config/f15_config_f15/hardware.py index 85477d76..61529c16 100644 --- a/config/f15_config_f15/hardware.py +++ b/config/f15_config_f15/hardware.py @@ -63,7 +63,11 @@ 'status_pin': 13, }, 'flashlight': { - 'version': 'none', + 'version': 'flashlight_pwm_v2', + 'name': 'flashlight', + 'on_expander': True, + 'front_pin': 12, + 'back_pin': 23, }, 'bumper': { 'name': 'bumper', diff --git a/field_friend/automations/navigation/__init__.py b/field_friend/automations/navigation/__init__.py index 794c902f..23f0f50e 100644 --- a/field_friend/automations/navigation/__init__.py +++ b/field_friend/automations/navigation/__init__.py @@ -5,6 +5,7 @@ from .navigation import Navigation, WorkflowException from .row_on_field_navigation import RowsOnFieldNavigation from .straight_line_navigation import StraightLineNavigation +from .crossglide_demo_navigation import CrossglideDemoNavigation __all__ = [ 'Navigation', @@ -14,4 +15,5 @@ 'FollowCropsNavigation', 'CoverageNavigation', 'ABLineNavigation', + 'CrossglideDemoNavigation', ] diff --git a/field_friend/automations/navigation/crossglide_demo_navigation.py b/field_friend/automations/navigation/crossglide_demo_navigation.py new file mode 100644 index 00000000..e5b39526 --- /dev/null +++ b/field_friend/automations/navigation/crossglide_demo_navigation.py @@ -0,0 +1,84 @@ +from random import randint +from typing import TYPE_CHECKING, Any + +import numpy as np +import rosys +from nicegui import ui + +from ...automations.implements.implement import Implement +from .navigation import Navigation + +if TYPE_CHECKING: + from system import System + + +class WorkflowException(Exception): + pass + + +class CrossglideDemoNavigation(Navigation): + + def __init__(self, system: 'System', tool: Implement) -> None: + super().__init__(system, tool) + self.MAX_STRETCH_DISTANCE: float = 5.0 + self.detector = system.detector + self.name = 'Crossglide Demo' + self.origin: rosys.geometry.Point + self.target: rosys.geometry.Point + + async def prepare(self) -> bool: + await super().prepare() + self.log.info(f'Activating {self.implement.name}...') + await self.implement.activate() + return True + + async def start(self) -> None: + try: + await self.implement.stop_workflow() + if not await self.implement.prepare(): + self.log.error('Tool-Preparation failed') + return + if not await self.prepare(): + self.log.error('Preparation failed') + return + if isinstance(self.driver.wheels, rosys.hardware.WheelsSimulation) and not rosys.is_test: + self.create_simulation() + self.log.info('Navigation started') + while not self._should_finish(): + # put a random simulated plant in the dict + self.implement.next_punch_y_position = np.random.uniform(-0.11, 0.1) + self.log.info(f'🥵 Weeds to handle: {self.implement.weeds_to_handle}') + await self.implement.start_workflow() + # await rosys.sleep(5) + except WorkflowException as e: + self.kpi_provider.increment_weeding_kpi('automation_stopped') + self.log.error(f'WorkflowException: {e}') + finally: + self.kpi_provider.increment_weeding_kpi('weeding_completed') + await self.implement.finish() + await self.finish() + await self.driver.wheels.stop() + + async def finish(self) -> None: + await super().finish() + await self.implement.deactivate() + + async def _drive(self, distance: float) -> None: + pass + + def _should_finish(self) -> bool: + return False + + def create_simulation(self): + pass + # TODO: implement create_simulation + + def settings_ui(self) -> None: + super().settings_ui() + + def backup(self) -> dict: + return super().backup() | { + } + + def restore(self, data: dict[str, Any]) -> None: + super().restore(data) diff --git a/field_friend/automations/plant_locator.py b/field_friend/automations/plant_locator.py index ba2f0201..d82c407d 100644 --- a/field_friend/automations/plant_locator.py +++ b/field_friend/automations/plant_locator.py @@ -117,7 +117,7 @@ async def _detect_plants(self) -> None: if world_point_3d is None: self.log.error('could not generate world point of detection, calibration error') continue - if abs(world_point_3d.y) > 0.12: + if abs(world_point_3d.y) >= 0.11: continue world_point_3d.z = 0.0 plant = Plant(type=d.category_name, @@ -127,7 +127,7 @@ async def _detect_plants(self) -> None: plant.confidences.append(d.confidence) if d.category_name in self.weed_category_names and d.confidence >= self.minimum_weed_confidence: # self.log.info('weed found') - self.log.info(f'image_point: {image_point} -> world_point_3d: {world_point_3d}') + # self.log.info(f'image_point: {image_point} -> world_point_3d: {world_point_3d}') await self.plant_provider.add_weed(plant) elif d.category_name in self.crop_category_names and d.confidence >= self.minimum_crop_confidence: # self.log.info(f'{d.category_name} found') diff --git a/field_friend/interface/components/hardware_control.py b/field_friend/interface/components/hardware_control.py index 33f796bd..b6eed5c8 100644 --- a/field_friend/interface/components/hardware_control.py +++ b/field_friend/interface/components/hardware_control.py @@ -3,8 +3,8 @@ from nicegui.events import ValueChangeEventArguments from ...automations import Puncher -from ...hardware import (ChainAxis, FieldFriend, FieldFriendHardware, Flashlight, FlashlightPWM, FlashlightPWMV2, - FlashlightV2, Mower, MowerHardware, MowerSimulation, Tornado, Axis, YAxisCanOpenHardware, +from ...hardware import (Axis, ChainAxis, FieldFriend, FieldFriendHardware, Flashlight, FlashlightPWM, FlashlightPWMV2, + FlashlightV2, Mower, MowerHardware, MowerSimulation, Tornado, YAxisCanOpenHardware, ZAxisCanOpenHardware) from .confirm_dialog import ConfirmDialog as confirm_dialog from .status_bulb import StatusBulb as status_bulb @@ -142,7 +142,7 @@ async def toggle_flashlight(e: ValueChangeEventArguments) -> None: puncher.punch(field_friend.y_axis.max_position, depth=depth.value))) ui.button(on_click=lambda: automator.start(puncher.punch(0, depth=depth.value))) ui.button(on_click=lambda: automator.start( - puncher.punch(field_friend.y_axis.min_position, depth=depth.value))) + puncher.punch(field_friend.y_axis.min_position - field_friend.WORK_Y, depth=depth.value))) if isinstance(field_friend.mower, Mower): with ui.column(): diff --git a/field_friend/system.py b/field_friend/system.py index d6fcc0a2..fd1601c0 100644 --- a/field_friend/system.py +++ b/field_friend/system.py @@ -11,8 +11,8 @@ from .automations import (AutomationWatcher, BatteryWatcher, FieldProvider, KpiProvider, PathProvider, PlantLocator, PlantProvider, Puncher) from .automations.implements import ChopAndScrew, ExternalMower, Implement, Recorder, Tornado, WeedingScrew -from .automations.navigation import (ABLineNavigation, CoverageNavigation, FollowCropsNavigation, Navigation, - RowsOnFieldNavigation, StraightLineNavigation) +from .automations.navigation import (ABLineNavigation, CoverageNavigation, CrossglideDemoNavigation, + FollowCropsNavigation, Navigation, RowsOnFieldNavigation, StraightLineNavigation) from .hardware import FieldFriend, FieldFriendHardware, FieldFriendSimulation, TeltonikaRouter from .interface.components.info import Info from .kpi_generator import generate_kpis @@ -136,11 +136,13 @@ def watch_robot() -> None: 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.crossglide_demo_navigation = CrossglideDemoNavigation(self, self.monitoring) self.navigation_strategies = {n.name: n for n in [self.field_navigation, self.straight_line_navigation, self.follow_crops_navigation, self.coverage_navigation, - self.a_b_line_navigation + self.a_b_line_navigation, + self.crossglide_demo_navigation ]} implements: list[Implement] = [self.monitoring] match self.field_friend.implement_name: