From 6ebe7a4db7691d0770659f5749fc06540f037d8e Mon Sep 17 00:00:00 2001 From: Johannes-Thiel Date: Mon, 7 Oct 2024 16:10:04 +0200 Subject: [PATCH] add time tracking --- .../automations/implements/recorder.py | 26 ++++++++++++++++++- .../implements/weeding_implement.py | 2 ++ field_friend/automations/kpi_provider.py | 14 ++++++++++ .../interface/components/status_drawer.py | 18 ++++++++++--- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/field_friend/automations/implements/recorder.py b/field_friend/automations/implements/recorder.py index 1d0bdcbc..75cd3eca 100644 --- a/field_friend/automations/implements/recorder.py +++ b/field_friend/automations/implements/recorder.py @@ -1,6 +1,6 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import rosys @@ -15,6 +15,10 @@ class Recorder(Implement): def __init__(self, system: 'System') -> None: super().__init__('Recorder') self.system = system + self.kpi_provider = system.kpi_provider + self.state: str = 'idle' + self.start_time: Optional[float] = None + rosys.on_repeat(self._update_time_and_distance, 0.1) async def activate(self): self.system.plant_provider.clear() @@ -22,8 +26,28 @@ async def activate(self): await rosys.sleep(3) # NOTE: we wait for the camera to adjust self.system.plant_locator.resume() await super().activate() + + async def prepare(self) -> bool: + self.state = 'running' + return True + + async def finish(self) -> None: + self.state = 'idle' + await super().finish() + async def deactivate(self): self.system.plant_locator.pause() await self.system.field_friend.flashlight.turn_off() await super().deactivate() + + def _update_time_and_distance(self): + # TODO move this to base class? + if self.state == 'idle': + return + if self.start_time is None: + self.start_time = rosys.time() + passed_time = rosys.time() - self.start_time + if passed_time > 1: + self.kpi_provider.increment_all_time_kpi('time') + self.start_time = rosys.time() diff --git a/field_friend/automations/implements/weeding_implement.py b/field_friend/automations/implements/weeding_implement.py index d9859301..4eaff71d 100644 --- a/field_friend/automations/implements/weeding_implement.py +++ b/field_friend/automations/implements/weeding_implement.py @@ -65,6 +65,7 @@ async def finish(self) -> None: self.system.plant_locator.pause() await self.system.field_friend.stop() await self.system.timelapse_recorder.compress_video() + self.state = 'idle' await super().finish() async def activate(self): @@ -206,6 +207,7 @@ def _update_time_and_distance(self): passed_time = rosys.time() - self.start_time if passed_time > 1: self.kpi_provider.increment_weeding_kpi('time') + self.kpi_provider.increment_all_time_kpi('time') self.start_time = rosys.time() def settings_ui(self): diff --git a/field_friend/automations/kpi_provider.py b/field_friend/automations/kpi_provider.py index bd743e79..f5fb2d57 100644 --- a/field_friend/automations/kpi_provider.py +++ b/field_friend/automations/kpi_provider.py @@ -49,6 +49,8 @@ def __init__(self, plant_provider: PlantProvider) -> None: self.WEEDING_KPIS_UPDATED = rosys.event.Event() """one of the KPIs of the running weeding automation has been updated.""" + self.all_time_kpis: KPIs = KPIs() + self.current_mowing_kpis: Mowing_KPIs = Mowing_KPIs() self.MOWING_KPIS_UPDATED = rosys.event.Event() """one of the KPIs of the running mowing automation has been updated.""" @@ -59,6 +61,7 @@ def backup(self) -> dict: logger_backup = super().backup() return {'current_weeding_kpis': rosys.persistence.to_dict(self.current_weeding_kpis), 'current_mowing_kpis': rosys.persistence.to_dict(self.current_mowing_kpis), + 'all_time_kpis': rosys.persistence.to_dict(self.all_time_kpis), 'days': logger_backup['days'], 'months': logger_backup['months']} @@ -66,12 +69,23 @@ def restore(self, data: dict[str, Any]) -> None: super().restore(data) rosys.persistence.replace_dataclass(self.current_weeding_kpis, data.get('current_weeding_kpis', Weeding_KPIs())) rosys.persistence.replace_dataclass(self.current_mowing_kpis, data.get('current_mowing_kpis', Mowing_KPIs())) + rosys.persistence.replace_dataclass(self.all_time_kpis, data.get('all_time_kpis', KPIs())) def invalidate(self) -> None: self.request_backup() self.WEEDING_KPIS_UPDATED.emit() self.MOWING_KPIS_UPDATED.emit() + def increment_all_time_kpi(self, indicator: str) -> None: + self.increment(indicator) + if getattr(self.all_time_kpis, indicator) is None: + new_value = 1 + else: + new_value = getattr(self.all_time_kpis, indicator)+1 + setattr(self.all_time_kpis, indicator, new_value) + self.invalidate() + return + def increment_weeding_kpi(self, indicator: str) -> None: self.increment(indicator) if getattr(self.current_weeding_kpis, indicator) is None: diff --git a/field_friend/interface/components/status_drawer.py b/field_friend/interface/components/status_drawer.py index b55d71cc..9d9144d0 100644 --- a/field_friend/interface/components/status_drawer.py +++ b/field_friend/interface/components/status_drawer.py @@ -100,13 +100,16 @@ def status_drawer(system: 'System', robot: FieldFriend, gnss: Gnss, odometer: ro with ui.column().bind_visibility_from(system.automator, 'is_running'): with ui.row().classes('place-items-center'): ui.markdown('**Current Field:**').style('color: #6E93D6') + current_field_label = ui.label() with ui.row().classes('place-items-center'): ui.markdown('**Time on Field:**').style('color: #6E93D6') kpi_fieldtime_label = ui.label() with ui.row().classes('place-items-center'): ui.markdown('**Distance:**').style('color: #6E93D6') kpi_distance_label = ui.label() - current_field_label = ui.label() + with ui.row().classes('place-items-center'): + ui.markdown('**Time in Automation:**').style('color: #6E93D6') + kpi_time_in_automation = ui.label() with ui.row().classes('place-items-center'): ui.markdown('**Current Row:**').style('color: #6E93D6') current_row_label = ui.label() @@ -123,8 +126,13 @@ def status_drawer(system: 'System', robot: FieldFriend, gnss: Gnss, odometer: ro ui.markdown('**Punches:**').style('color: #6E93D6') kpi_punches_label = ui.label() - with ui.row().classes('place-items-center').bind_visibility_from(system.automator, 'is_running', backward=lambda x: not x): - ui.markdown('**No automation running**').style('color: #6E93D6') + with ui.column().classes('place-items-center').bind_visibility_from(system.automator, 'is_running', backward=lambda x: not x): + with ui.row().classes('place-items-center'): + ui.markdown('**No automation running**').style('color: #6E93D6') + with ui.row().classes('place-items-center'): + ui.markdown('**Time in Automation**').style('color: #6E93D6') + kpi_time_in_automation_off = ui.label() + ui.markdown('**Positioning**').style('color: #6E93D6').classes('w-full text-center') ui.separator() @@ -224,6 +232,9 @@ def update_status() -> None: if automator.is_running: kpi_fieldtime_label.text = f'{system.kpi_provider.current_weeding_kpis.time}s' kpi_distance_label.text = f'{system.kpi_provider.current_weeding_kpis.distance}m' + kpi_time_in_automation.text = f'{system.kpi_provider.all_time_kpis.time}s' + else: + kpi_time_in_automation_off.text = f'{system.kpi_provider.all_time_kpis.time}s' # TODO reimplement with navigation and tools # current_automation = next(key for key, value in system.tools.items() @@ -243,6 +254,5 @@ def update_status() -> None: heading_label.text = f'{system.gnss.current.heading:.2f}° {direction_flag}' if system.gnss.current is not None and system.gnss.current.heading is not None else 'No heading' rtk_fix_label.text = f'gps_qual: {system.gnss.current.gps_qual}, mode: {system.gnss.current.mode}' if system.gnss.current is not None else 'No fix' odometry_label.text = str(odometer.prediction) - ui.timer(rosys.config.ui_update_interval, update_status) return status_drawer