Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autoplot app save #11

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright (c) farm-ng, inc. Amiga Development Kit License, Version 0.1
from __future__ import annotations

import argparse
import asyncio
import os
from pathlib import Path

# global constants (in pixels)
FARM_NG_SCREEN_WIDTH_PX = 1280
FARM_NG_SCREEN_HEIGHT_PX = 800

# global constants for data paths
# TODO: make this configurable or get from launch file
FARM_NG_DEFAULT_DATA_PATH = Path("/data/farm_ng")

# Must come before kivy imports
os.environ["KIVY_NO_ARGS"] = "1"

# gui configs must go before any other kivy import
from kivy.config import Config # noreorder # noqa: E402

Config.set("graphics", "resizable", False)
Config.set("graphics", "width", str(FARM_NG_SCREEN_WIDTH_PX))
Config.set("graphics", "height", str(FARM_NG_SCREEN_HEIGHT_PX))
Config.set("graphics", "fullscreen", "false")
Config.set("input", "mouse", "mouse,disable_on_activity")
Config.set("kivy", "keyboard_mode", "systemanddock")

# kivy imports
from boundary_screen import BoundaryScreen
from farm_ng.gps_utils.coordinates import GpsCoordinates
from kivy.app import App # noqa: E402
from kivy.uix.screenmanager import ScreenManager
from main_screen import MainScreen
from path_screen import PathScreen
from map_view import FullMapView

class AutoPlot(App):
"""The main application class."""

def __init__(self, data_path: Path) -> None:
super().__init__()
self._data_path = data_path

self.async_tasks: list[asyncio.Task] = []

self.base_gps_coordinates = GpsCoordinates(
lat=36.910233, lon=-121.756897, alt=0.0
)
self.current_location: GpsCoordinates | None = None

@property
def data_path(self) -> Path:
"""The path to the data directory."""
return self._data_path

@property
def paths_path(self) -> Path:
"""The path to the saved paths directory."""
return "/data/farm_ng/autoplot_files/paths"

@property
def guide_path(self) -> Path:
"""The path to the saved paths directory."""
return "/data/farm_ng/autoplot_files/guide"

@property
def boundary_path(self) -> Path:
"""The path to the saved paths directory."""
return "/data/farm_ng/autoplot_files/boundary"

def build(self) -> ScreenManager:
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main_screen"))
self.sm.add_widget(BoundaryScreen(name="boundary_screen"))
self.sm.add_widget(PathScreen(name="path_screen"))
# set the current screen to the MainScreen
self.sm.current = "main_screen"
return self.sm

async def app_func(self) -> None:
async def run_wrapper() -> None:
# we don't actually need to set asyncio as the lib because it is
# the default, but it doesn't hurt to be explicit
await self.async_run(async_lib="asyncio")
for task in self.async_tasks:
task.cancel()

# task to read from the gps
self.async_tasks.append(asyncio.create_task(self.stream_gps()))

return await asyncio.gather(run_wrapper(), *self.async_tasks)

async def stream_gps(self) -> None:
# TODO: implement this for real using the client
import torch

while True:
# add some noise to the gps coordinates
lat, lon, alt = torch.randn(3).mul(0.0001).tolist()
self.current_location = GpsCoordinates(
lat=self.base_gps_coordinates.lat + lat,
lon=self.base_gps_coordinates.lon + lon,
alt=self.base_gps_coordinates.alt + alt,
)
await asyncio.sleep(0.1)


if __name__ == "__main__":
parser = argparse.ArgumentParser(prog="autoplot-app")
args = parser.parse_args()

loop = asyncio.get_event_loop()

app = AutoPlot(data_path=FARM_NG_DEFAULT_DATA_PATH)

try:
loop.run_until_complete(app.app_func())
except asyncio.CancelledError:
pass
finally:
loop.close()
144 changes: 144 additions & 0 deletions main_screen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from __future__ import annotations

from pathlib import Path

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen
from kivy.uix.tabbedpanel import TabbedPanelItem
from utils import FileDescription, get_list_of_files

Builder.load_string(
"""
<MainScreen>
BoxLayout:
orientation: 'vertical'
Widget:
size_hint_y: 0.1
RelativeLayout:
id: content_view
size_hint_y: 0.9
TabbedPanel:
id: tabbed_panel
do_default_tab: False
FilesTabbedPanel:
id: guide_line_panel
text: 'Guidance Line'
on_press: root.update_tabs_scroll_views(app.guide_path)
FilesTabbedPanel:
id: path_panel
text: 'Path'
on_press: root.update_tabs_scroll_views(app.paths_path)
FilesTabbedPanel:
id: field_bound_panel
text: 'Field Boundary'
on_press: root.update_tabs_scroll_views(app.boundary_path)
Button:
id: tab_action_button
text: '+'
pos_hint: {'top': 1.0, 'right': 1.0}
size_hint: 0.1, 0.1
on_release: root.on_tab_action_button()
<FilesTabbedPanel>:
ScrollView:
id: scroll_view
do_scroll_x: False
GridLayout:
id: layout_grid
cols: 1
size_hint_y: None
height: self.minimum_height
<FileDescriptionWidget@BoxLayout>:
orientation: 'horizontal'
size_hint_y: None
Button:
id: root.id
text: root.text
on_release: root.printing(root.text, root.id)
"""

)

# ui classes


class FilesTabbedPanel(TabbedPanelItem):
pass


class FileDescriptionWidget(BoxLayout):
id = StringProperty()
text = StringProperty()

def printing(self, text, id):
print('Hello, you tapped a button', text)
print('id is', id)
print('Loading other file')

# TODO URGENT Find a better way to change screens
# self.parent.parent.parent.parent.parent.parent.parent.parent.current = "boundary_screen"
# print(self.parent)


class MainScreen(Screen):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
# initialize tabbed panel
# methods = [method_name for method_name in dir(MainScreen) if callable(getattr(MainScreen, method_name))]
# print(methods)

self.update_tabs_scroll_views(self.app.guide_path)

@property
def app(self) -> App:
"""Get app instance."""
return App.get_running_app()


# TODO: improve this later, now just to prove concept
def update_tabs_scroll_views(self, path) -> None:
"""Update tabbed panel scroll views."""
# get list of files to update
print(path)
data_path = Path(path)
files: list[FileDescription] = get_list_of_files(data_path)

# update tabbed panel
for tab in self.ids.tabbed_panel.tab_list:
# get tab scroll view
tab_grid_layout: GridLayout = tab.ids.layout_grid

# clear scroll view grid layout
tab_grid_layout.clear_widgets()

# add files to scroll view grid layout
file: FileDescription
for file in files:
tab_grid_layout.add_widget(
FileDescriptionWidget(
id = file.name,
text = f"{tab.text} -- {file.name} -- {file.size_bytes} bytes -- {file.modification_time_str}"
)
)

def on_tab_action_button(self) -> None:
"""Handle tab action button press.
This method is called when the tab action button is pressed and redirects to the correct screen.
"""
# get current tab text
current_tab_text: str = self.ids.tabbed_panel.current_tab.text.lower().replace(
" ", "_"
)

# change screen
if current_tab_text == "field_boundary":
self.manager.current = "boundary_screen"
self.manager.transition.direction = "left"
elif current_tab_text == "path":
self.manager.current = "path_screen"
self.manager.transition.direction = "left"
else:
print("do nothing for now")
Loading