Skip to content

Commit

Permalink
code review
Browse files Browse the repository at this point in the history
  • Loading branch information
falkoschindler committed Dec 17, 2024
1 parent f7df7ce commit 8e0b9f5
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 161 deletions.
2 changes: 0 additions & 2 deletions field_friend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from . import interface, log_configuration
from .robot_locator import RobotLocator
from .system import System

__all__ = [
'interface',
'log_configuration',
'RobotLocator',
'System',
]
4 changes: 2 additions & 2 deletions field_friend/automations/automation_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, system: System) -> None:
rosys.on_repeat(self.check_field_bounds, 1.0)
if self.field_friend.bumper:
self.field_friend.bumper.BUMPER_TRIGGERED.register(lambda name: self.pause(f'Bumper {name} was triggered'))
# TODO
# self.gnss.GNSS_CONNECTION_LOST.register(lambda: self.pause('GNSS connection lost'))
# self.gnss.RTK_FIX_LOST.register(lambda: self.pause('GNSS RTK fix lost'))

Expand Down Expand Up @@ -114,8 +115,7 @@ def try_resume(self) -> None:
self.resume_delay = DEFAULT_RESUME_DELAY

def start_field_watch(self, field_boundaries: list[GeoPoint]) -> None:
self.field_polygon = ShapelyPolygon(
[point.to_local().tuple for point in field_boundaries])
self.field_polygon = ShapelyPolygon([point.to_local().tuple for point in field_boundaries])
self.field_watch_active = True

def stop_field_watch(self) -> None:
Expand Down
29 changes: 8 additions & 21 deletions field_friend/automations/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ def line_segment(self) -> rosys.geometry.LineSegment:
return rosys.geometry.LineSegment(point1=self.points[0].to_local(),
point2=self.points[-1].to_local())

@property
def points_as_tuples(self) -> list[tuple[float, float]]:
return [p.tuple for p in self.points]


@dataclass(slots=True, kw_only=True)
class RowSupportPoint(GeoPoint):
Expand Down Expand Up @@ -166,11 +162,11 @@ def to_dict(self) -> dict:
'name': self.name,
'first_row_start': {
'lat': math.degrees(self.first_row_start.lat),
'long': math.degrees(self.first_row_start.lon),
'lon': math.degrees(self.first_row_start.lon),
},
'first_row_end': {
'lat': math.degrees(self.first_row_end.lat),
'long': math.degrees(self.first_row_end.lon),
'lon': math.degrees(self.first_row_end.lon),
},
'row_spacing': self.row_spacing,
'row_count': self.row_count,
Expand All @@ -189,18 +185,9 @@ def args_from_dict(cls, data: dict[str, Any]) -> dict:

@classmethod
def from_dict(cls, data: dict[str, Any]) -> Self:
# TODO: handle old fields
if 'long' in data['first_row_start']:
data['first_row_start'] = GeoPoint.from_degrees(lat=data['first_row_start']['lat'],
lon=data['first_row_start']['long'])
data['first_row_end'] = GeoPoint.from_degrees(lat=data['first_row_end']['lat'],
lon=data['first_row_end']['long'])
else:
data['first_row_start'] = GeoPoint.from_degrees(lat=data['first_row_start']['lat'],
lon=data['first_row_start']['lon'])
data['first_row_end'] = GeoPoint.from_degrees(lat=data['first_row_end']['lat'],
lon=data['first_row_end']['lon'])
data['row_support_points'] = [rosys.persistence.from_dict(
RowSupportPoint, sp) for sp in data['row_support_points']] if 'row_support_points' in data else []
field_data = cls(**cls.args_from_dict(data))
return field_data
for key in ['first_row_start', 'first_row_end']:
data[key] = GeoPoint.from_degrees(lat=data[key]['lat'],
lon=data[key]['lon'] if 'lon' in data[key] else data[key]['long'])
data['row_support_points'] = [rosys.persistence.from_dict(RowSupportPoint, sp)
for sp in data.get('row_support_points', [])]
return cls(**cls.args_from_dict(data))
5 changes: 0 additions & 5 deletions field_friend/automations/field_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ def restore(self, data: dict[str, Any]) -> None:
fields_data: dict[str, dict] = data.get('fields', {})
for field in list(fields_data.values()):
new_field = Field.from_dict(field)
# TODO: import old fields?
# new_field.first_row_start = GeoPoint(lat=np.deg2rad(new_field.first_row_start.lat),
# lon=np.deg2rad(new_field.first_row_start.lon))
# new_field.first_row_end = GeoPoint(lat=np.deg2rad(new_field.first_row_end.lat),
# lon=np.deg2rad(new_field.first_row_end.lon))
self.fields.append(new_field)
selected_field_id: str | None = data.get('selected_field')
if selected_field_id:
Expand Down
1 change: 0 additions & 1 deletion field_friend/automations/navigation/field_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ async def _run_approaching_row_start(self) -> State:
self.create_simulation()
else:
self.plant_provider.clear()
await rosys.sleep(2)
return State.FOLLOWING_ROW

# TODO: growing error because of the threshold
Expand Down
15 changes: 8 additions & 7 deletions field_friend/automations/navigation/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
from ...system import System


def check_distance_to_reference(gnss: Gnss, *, max_distance: float = 5000.0) -> bool:
# TODO: still needed?
def is_reference_invalid(gnss: Gnss, *, max_distance: float = 5000.0) -> bool:
if GeoReference.current is None:
return True
if gnss.last_measurement is not None and gnss.last_measurement.gps_quality > 0 and gnss.last_measurement.point.distance(GeoReference.current.origin) > max_distance:
# TODO: show dialog
# reference_alert_dialog(gnss)
return True
return False
if gnss.last_measurement is None:
return False
if gnss.last_measurement.gps_quality == 0:
return False
return gnss.last_measurement.point.distance(GeoReference.current.origin) <= max_distance


class WorkflowException(Exception):
Expand Down Expand Up @@ -58,7 +59,7 @@ async def start(self) -> None:
if not await self.prepare():
self.log.error('Preparation failed')
return
if check_distance_to_reference(self.gnss):
if is_reference_invalid(self.gnss):
raise WorkflowException('reference to far away from robot')
self.start_position = self.robot_locator.pose.point
if isinstance(self.driver.wheels, rosys.hardware.WheelsSimulation) and not rosys.is_test:
Expand Down
40 changes: 20 additions & 20 deletions field_friend/interface/components/development.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import math
from typing import TYPE_CHECKING

Expand All @@ -14,24 +16,23 @@
from ...system import System


def create_development_ui(system: 'System') -> None:
with ui.row().style('width: calc(100vw - 2rem); flex-wrap: nowrap;'):
with ui.card().style('background-color: #2E5396; width: 100%;'):
with ui.column().style('width: 100%;'):
ui.label('Development Tools').style('font-size: 1.5rem; color: white;')
create_settings_ui(system)
with ui.row().style('width: 100%'):
with ui.card().style('background-color: #3E63A6; color: white;'):
if isinstance(system.field_friend, rosys.hardware.RobotHardware):
with ui.row():
with ui.column():
system.field_friend.robot_brain.developer_ui()
with ui.column():
system.field_friend.robot_brain.communication.debug_ui()
else:
rosys.simulation_ui()
create_hardware_control_ui(system.field_friend, system.automator, system.puncher)
status_dev_page(system.field_friend, system)
def create_development_ui(system: System) -> None:
with ui.card().style('background-color: #2E5396; width: 100%;'):
with ui.column().style('width: 100%;'):
ui.label('Development Tools').style('font-size: 1.5rem; color: white;')
create_settings_ui(system)
with ui.row().style('width: 100%'):
with ui.card().style('background-color: #3E63A6; color: white;'):
if isinstance(system.field_friend, rosys.hardware.RobotHardware):
with ui.row():
with ui.column():
system.field_friend.robot_brain.developer_ui()
with ui.column():
system.field_friend.robot_brain.communication.debug_ui()
else:
rosys.simulation_ui()
create_hardware_control_ui(system.field_friend, system.automator, system.puncher)
status_dev_page(system.field_friend, system)

with ui.row():
with ui.card():
Expand Down Expand Up @@ -75,5 +76,4 @@ async def turn_to_yaw(yaw: float) -> None:
esp_pins_p0 = EspPins(name='p0', robot_brain=system.field_friend.robot_brain)
esp_pins_p0.developer_ui()

with ui.row().style('width: calc(100vw - 2rem); flex-wrap: nowrap;'):
io_overview(system)
io_overview(system)
51 changes: 11 additions & 40 deletions field_friend/interface/components/field_friend_object.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,29 @@
from __future__ import annotations

import numpy as np
from nicegui import ui
from nicegui.elements.scene_objects import Box, Cylinder, Extrusion, Group, Stl
from rosys import config
from rosys.geometry import Prism
from nicegui.elements.scene_objects import Box, Cylinder, Extrusion, Group
from rosys.driving import robot_object
from rosys.geometry import Pose, Prism
from rosys.vision import CameraProjector, CameraProvider, camera_objects

from ...hardware import Axis, ChainAxis, FieldFriend, Tornado
from ...robot_locator import RobotLocator


class RobotObject(Group):
"""The RobotObject UI element displays the robot with its given shape in a 3D scene.
class RobotLocatorOdometer:

The current pose is taken from a given robot_locator.
The `debug` argument can be set to show a wireframe instead of a closed polygon.
"""

def __init__(self, shape: Prism, robot_locator: RobotLocator, *, debug: bool = False) -> None:
super().__init__()
self.shape = shape
def __init__(self, robot_locator: RobotLocator) -> None:
self.robot_locator = robot_locator
self.robot_object: Extrusion | Stl
with self:
outline = [list(point) for point in self.shape.outline]
self.robot_object = Extrusion(outline, self.shape.height, wireframe=debug)
self.robot_object.material('#4488ff', 0.5)
ui.timer(config.ui_update_interval, self.update)

def with_stl(self, url: str, *,
x: float = 0, y: float = 0, z: float = 0,
omega: float = 0, phi: float = 0, kappa: float = 0,
scale: float = 1.0,
color: str = '#ffffff', opacity: float = 1.0) -> RobotObject:
"""Sets an STL to be displayed as the robot.

The file can be served from a local directory with [app.add_static_files(url, path)](https://nicegui.io/reference#static_files).
"""
self.robot_object.delete()
with self:
self.robot_object = Stl(url).move(x, y, z).rotate(omega, phi, kappa).scale(scale).material(color, opacity)
return self

def update(self) -> None:
self.move(self.robot_locator.pose.x, self.robot_locator.pose.y)
self.rotate(0, 0, self.robot_locator.pose.yaw)
@property
def pose(self) -> Pose:
return self.robot_locator.pose


class FieldFriendObject(RobotObject):
class FieldFriendObject(robot_object):

def __init__(self, robot_locator: RobotLocator, camera_provider: CameraProvider,
field_friend: FieldFriend) -> None:
super().__init__(Prism(outline=[], height=0), robot_locator)
def __init__(self, robot_locator: RobotLocator, camera_provider: CameraProvider, field_friend: FieldFriend) -> None:
super().__init__(Prism(outline=[], height=0), RobotLocatorOdometer(robot_locator)) # type: ignore

self.robot = field_friend

Expand Down
6 changes: 3 additions & 3 deletions field_friend/interface/components/field_object.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from itertools import pairwise
from typing import TYPE_CHECKING

import numpy as np
Expand All @@ -17,7 +18,6 @@ class FieldObject(Group):
def __init__(self, system: System) -> None:
super().__init__()
self.system = system
self.gnss = system.gnss
self.field_provider: FieldProvider = system.field_provider
self._update()
self.field_provider.FIELDS_CHANGED.register_ui(self._update)
Expand Down Expand Up @@ -69,8 +69,8 @@ def update(self, active_field: Field | None) -> None:
if len(row.points) == 1:
continue
row_points = [point.to_local() for point in row.points]
for i in range(len(row_points) - 1):
spline = Spline.from_points(row_points[i], row_points[i + 1])
for i, (point1, point2) in enumerate(pairwise(row_points)):
spline = Spline.from_points(point1, point2)
Curve(
[spline.start.x, spline.start.y, 0],
[spline.control1.x, spline.control1.y, 0],
Expand Down
8 changes: 3 additions & 5 deletions field_friend/interface/components/leaflet_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, system: System, draw_tools: bool) -> None:
},
'edit': False,
}
center_point = GeoPoint(lat=0.9072773, lon=0.1297515)
center_point = GeoPoint.from_degrees(51.983159, 7.434212)
if self.system.gnss.last_measurement is not None:
center_point = self.system.gnss.last_measurement.point
self.m: ui.leaflet
Expand Down Expand Up @@ -82,16 +82,14 @@ def update_layers(self) -> None:
for field in self.field_provider.fields:
color = '#6E93D6' if self.field_provider.selected_field is not None and field.id == self.field_provider.selected_field.id else '#999'
field_outline = [p.degree_tuple for p in field.outline]
self.field_layers.append(self.m.generic_layer(name='polygon',
args=[field_outline, {'color': color}]))
self.field_layers.append(self.m.generic_layer(name='polygon', args=[field_outline, {'color': color}]))
for layer in self.row_layers:
self.m.remove_layer(layer)
self.row_layers = []
if self.field_provider.selected_field is not None:
for row in self.field_provider.selected_field.rows:
row_points = [p.degree_tuple for p in row.points]
self.row_layers.append(self.m.generic_layer(name='polyline',
args=[row_points, {'color': '#F2C037'}]))
self.row_layers.append(self.m.generic_layer(name='polyline', args=[row_points, {'color': '#F2C037'}]))

def update_robot_position(self, measurement: GnssMeasurement, dialog=None) -> None:
# TODO: where does the dialog come from?
Expand Down
Loading

0 comments on commit 8e0b9f5

Please sign in to comment.