diff --git a/python_payload/apps/gr33nhouse/__init__.py b/python_payload/apps/gr33nhouse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0dfc5157fe77ead098f5ceb6bc61d9c3e3a1440f --- /dev/null +++ b/python_payload/apps/gr33nhouse/__init__.py @@ -0,0 +1,82 @@ +from st3m.application import Application, ApplicationContext +from st3m.input import InputController, InputState +from st3m.ui import colours +from st3m.ui.view import ViewManager +from ctx import Context +from .applist import AppList +from .background import Flow3rView +from .record import RecordView +from .manual import ManualInputView + + +class Gr33nhouseApp(Application): + items = ["Browse apps", "Record App Seed", "Enter App Seed"] + selection = 0 + + input: InputController + background: Flow3rView + + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx=app_ctx) + + self.input = InputController() + self.background = Flow3rView() + + def on_enter(self, vm: ViewManager | None) -> None: + super().on_enter(vm) + + if self.vm is None: + raise RuntimeError("vm is None") + + def draw(self, ctx: Context) -> None: + self.background.draw(ctx) + ctx.save() + + ctx.gray(1.0) + ctx.rectangle( + -120.0, + -15.0, + 240.0, + 30.0, + ).fill() + + ctx.translate(0, -30 * self.selection) + + offset = 0 + + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + + for idx, item in enumerate(self.items): + if idx == self.selection: + ctx.gray(0.0) + else: + ctx.gray(1.0) + + ctx.move_to(0, offset) + ctx.text(item) + offset += 30 + + ctx.restore() + + def think(self, ins: InputState, delta_ms: int) -> None: + self.background.think(ins, delta_ms) + self.input.think(ins, delta_ms) + + if self.input.buttons.app.left.pressed: + if self.selection > 0: + self.selection -= 1 + + elif self.input.buttons.app.right.pressed: + if self.selection < len(self.items) - 1: + self.selection += 1 + + elif self.input.buttons.app.middle.pressed: + if self.selection == 0: + self.vm.push(AppList()) + elif self.selection == 1: + self.vm.push(RecordView()) + elif self.selection == 2: + self.vm.push(ManualInputView()) diff --git a/python_payload/apps/gr33nhouse/applist.py b/python_payload/apps/gr33nhouse/applist.py new file mode 100644 index 0000000000000000000000000000000000000000..c1514ef15215682f1b9422ddebf538ebba3a719d --- /dev/null +++ b/python_payload/apps/gr33nhouse/applist.py @@ -0,0 +1,169 @@ +from st3m.goose import Optional, Enum +from st3m.input import InputController, InputState +from st3m.ui import colours +from st3m.ui.view import BaseView, ViewManager +from ctx import Context +import urequests +import time +from .background import Flow3rView +from .confirmation import ConfirmationView + + +class ViewState(Enum): + INITIAL = 1 + LOADING = 2 + ERROR = 3 + LOADED = 4 + + +class AppList(BaseView): + initial_ticks: int = 0 + _state: ViewState = ViewState.INITIAL + + apps: list[any] = [] + selection: int = 0 + + input: InputController + background: Flow3rView + + def __init__(self) -> None: + self.input = InputController() + self.vm = None + self.background = Flow3rView() + + def on_enter(self, vm: Optional[ViewManager]) -> None: + self.vm = vm + self.initial_ticks = time.ticks_ms() + + def draw(self, ctx: Context) -> None: + ctx.move_to(0, 0) + + if self._state == ViewState.INITIAL or self._state == ViewState.LOADING: + ctx.rgb(*colours.BLACK) + ctx.rectangle( + -120.0, + -120.0, + 240.0, + 240.0, + ).fill() + + ctx.save() + ctx.rgb(*colours.WHITE) + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.text("Collecting seeds...") + ctx.restore() + return + + elif self._state == ViewState.ERROR: + ctx.rgb(*colours.BLACK) + ctx.rectangle( + -120.0, + -120.0, + 240.0, + 240.0, + ).fill() + + ctx.save() + ctx.rgb(*colours.WHITE) + ctx.gray(1.0) + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.text("Something went wrong") + ctx.restore() + return + + elif self._state == ViewState.LOADED: + self.background.draw(ctx) + + ctx.save() + ctx.gray(1.0) + ctx.rectangle( + -120.0, + -15.0, + 240.0, + 30.0, + ).fill() + + ctx.translate(0, -30 * self.selection) + + offset = 0 + + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + + ctx.move_to(0, 0) + for idx, app in enumerate(self.apps): + if idx == self.selection: + ctx.gray(0.0) + else: + ctx.gray(1.0) + + ctx.move_to(0, offset) + ctx.text(app["name"]) + offset += 30 + + ctx.restore() + else: + raise RuntimeError(f"Invalid view state {self._state}") + + def think(self, ins: InputState, delta_ms: int) -> None: + if self.initial_ticks == 0 or time.ticks_ms() < self.initial_ticks + 300: + return + + self.input.think(ins, delta_ms) + + if self._state == ViewState.INITIAL: + try: + self._state = ViewState.LOADING + print("Loading app list...") + res = urequests.get("https://flow3r.garden/api/apps.json") + self.apps = res.json()["apps"] + + if self.apps == None: + print(f"Invalid JSON or no apps: {res.json()}") + self._state = ViewState.ERROR + return + + self._state = ViewState.LOADED + print("App list loaded") + except Exception as e: + print(f"Load failed: {e}") + self._state = ViewState.ERROR + return + elif self._state == ViewState.LOADING: + raise RuntimeError(f"Invalid view state {self._state}") + elif self._state == ViewState.ERROR: + return + + self.background.think(ins, delta_ms) + + if self.input.buttons.app.left.pressed: + if self.selection > 0: + self.selection -= 1 + + elif self.input.buttons.app.right.pressed: + if self.selection < len(self.apps) - 1: + self.selection += 1 + + elif self.input.buttons.app.middle.pressed: + print(f"state {self._state}") + print(f">> {self.apps[self.selection]}") + app = self.apps[self.selection] + url = app["tarDownloadUrl"] + name = app["name"] + author = app["author"] + # self.vm.push(DownloadView(url)) + self.vm.push( + ConfirmationView( + url=url, + name=name, + author=author, + ) + ) diff --git a/python_payload/apps/gr33nhouse/background.py b/python_payload/apps/gr33nhouse/background.py new file mode 100644 index 0000000000000000000000000000000000000000..516f33be7d21c4547e3f92424c5f328a4ff13707 --- /dev/null +++ b/python_payload/apps/gr33nhouse/background.py @@ -0,0 +1,109 @@ +import random +from st3m.input import InputController, InputState + +from st3m.ui.view import BaseView +from ctx import Context + + +class Flow3rView(BaseView): + input: InputController + + def __init__(self) -> None: + self.vm = None + self.input = InputController() + + self.flowers = [] + for i in range(8): + self.flowers.append( + Flower( + ((random.getrandbits(16) - 32767) / 32767.0) * 200, + ((random.getrandbits(16)) / 65535.0) * 240 - 120, + ((random.getrandbits(16)) / 65535.0) * 400 + 25, + ) + ) + + def think(self, ins: InputState, delta_ms: int) -> None: + super().think(ins, delta_ms) + for c in self.flowers: + c.y += (10 * delta_ms / 1000.0) * 200 / c.z + if c.y > 300: + c.y = -300 + c.rot += delta_ms * c.rot_speed + self.flowers = sorted(self.flowers, key=lambda c: -c.z) + + def draw(self, ctx: Context) -> None: + ctx.save() + ctx.rectangle(-120, -120, 240, 240) + ctx.rgb(0.1, 0.4, 0.3) + ctx.fill() + + for f in self.flowers: + f.draw(ctx) + ctx.restore() + + +class Flower: + def __init__(self, x: float, y: float, z: float) -> None: + self.x = x + self.y = y + self.z = z + self.rot = 0 + self.rot_speed = (((random.getrandbits(16) - 32767) / 32767.0) - 0.5) / 800 + + def draw(self, ctx: Context): + ctx.save() + ctx.translate(-78 + self.x, -70 + self.y) + ctx.translate(50, 40) + + ctx.rotate(self.rot) + ctx.translate(-50, -40) + ctx.scale(100 / self.z, 100.0 / self.z) + ctx.move_to(76.221727, 3.9788409).curve_to( + 94.027758, 31.627675, 91.038918, 37.561293, 94.653428, 48.340473 + ).rel_curve_to( + 25.783102, -3.90214, 30.783332, -1.52811, 47.230192, 4.252451 + ).rel_curve_to( + -11.30184, 19.609496, -21.35729, 20.701768, -35.31018, 32.087063 + ).rel_curve_to( + 5.56219, 12.080061, 12.91196, 25.953973, 9.98735, 45.917643 + ).rel_curve_to( + -19.768963, -4.59388, -22.879866, -10.12216, -40.896842, -23.93099 + ).rel_curve_to( + -11.463256, 10.23025, -17.377386, 18.2378, -41.515124, 25.03533 + ).rel_curve_to( + 0.05756, -29.49286, 4.71903, -31.931936, 10.342734, -46.700913 + ).curve_to( + 33.174997, 77.048676, 19.482194, 71.413009, 8.8631648, 52.420793 + ).curve_to( + 27.471602, 45.126773, 38.877997, 45.9184, 56.349456, 48.518302 + ).curve_to( + 59.03275, 31.351935, 64.893201, 16.103886, 76.221727, 3.9788409 + ).close_path().rgba( + 1.0, 0.6, 0.4, 0.4 + ).fill() + ctx.restore() + return + ctx.move_to(116.89842, 17.221179).rel_curve_to( + 6.77406, 15.003357, 9.99904, 35.088466, 0.27033, 47.639569 + ).curve_to( + 108.38621, 76.191194, 87.783414, 86.487988, 75.460015, 75.348373 + ).curve_to( + 64.051094, 64.686361, 61.318767, 54.582827, 67.499384, 36.894251 + ).curve_to( + 79.03955, 16.606134, 103.60918, 15.612261, 116.89842, 17.221179 + ).close_path().rgb( + 0.5, 0.3, 0.4 + ).fill() + + ctx.move_to(75.608612, 4.2453713).curve_to( + 85.516707, 17.987709, 93.630911, 33.119248, 94.486497, 49.201225 + ).curve_to( + 95.068862, 60.147617, 85.880014, 75.820834, 74.919761, 75.632395 + ).curve_to( + 63.886159, 75.442695, 57.545631, 61.257211, 57.434286, 50.22254 + ).curve_to( + 57.257291, 32.681814, 65.992688, 16.610811, 75.608612, 4.2453713 + ).close_path().rgb( + 0.2, 0.5, 0.8 + ).fill() + ctx.restore() diff --git a/python_payload/apps/gr33nhouse/confirmation.py b/python_payload/apps/gr33nhouse/confirmation.py new file mode 100644 index 0000000000000000000000000000000000000000..7974cb35748f78b569dbbf2d8fefdda86a819bee --- /dev/null +++ b/python_payload/apps/gr33nhouse/confirmation.py @@ -0,0 +1,70 @@ +from st3m.input import InputController, InputState +from st3m.ui import colours +from st3m.ui.view import BaseView, ViewManager +from ctx import Context +from .background import Flow3rView + + +class ConfirmationView(BaseView): + background: Flow3rView + input: InputController + + url: str + name: str + author: str + + def __init__(self, url: str, name: str, author: str) -> None: + self.input = InputController() + self.vm = None + self.background = Flow3rView() + + self.url = url + self.name = name + self.author = author + + def on_enter(self, vm: ViewManager | None) -> None: + super().on_enter(vm) + + if self.vm is None: + raise RuntimeError("vm is None") + + def draw(self, ctx: Context) -> None: + ctx.move_to(0, 0) + + self.background.draw(ctx) + + ctx.save() + ctx.rgb(*colours.WHITE) + ctx.rectangle( + -120.0, + -80.0, + 240.0, + 160.0, + ).fill() + + ctx.rgb(*colours.BLACK) + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + + ctx.move_to(0, -60) + ctx.text("Install") + + ctx.move_to(0, -30) + ctx.text(self.name) + + ctx.move_to(0, 0) + ctx.text("by") + + ctx.move_to(0, 30) + ctx.text(self.author) + + ctx.move_to(0, 60) + ctx.text("?") + + ctx.restore() + + def think(self, ins: InputState, delta_ms: int) -> None: + self.input.think(ins, delta_ms) + self.background.think(ins, delta_ms) diff --git a/python_payload/apps/gr33nhouse/download.py b/python_payload/apps/gr33nhouse/download.py new file mode 100644 index 0000000000000000000000000000000000000000..84dd89f368adcfa72293c5a9ed415cd33115ca63 --- /dev/null +++ b/python_payload/apps/gr33nhouse/download.py @@ -0,0 +1,72 @@ +import network +from st3m.input import InputState +import urequests +import gzip +import utarfile +import io +import os +from st3m.ui.view import BaseView +from ctx import Context + + +class DownloadView(BaseView): + def __init__(self, url: str) -> None: + super().__init__() + self._state = 1 + self._try = 1 + self._url = url + + def draw(self, ctx: Context) -> None: + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + + if self._state == 1 or self._state == 2: + # Fetching + ctx.rgb(255, 0, 0).rectangle(-20, -20, 40, 40).fill() + self._state = 2 + elif self._state == 3 or self._state == 4: + # Extracting + ctx.rgb(0, 0, 255).rectangle(-20, -20, 40, 40).fill() + self._state = 4 + elif self._state == 5: + # Done + ctx.rgb(0, 255, 0).rectangle(-20, -20, 40, 40).fill() + + def think(self, ins: InputState, delta_ms: int) -> None: + super().think(ins, delta_ms) # Let BaseView do its thing + + if self._state == 2: + try: + print("Getting it") + self.response = urequests.get(self._url) + if self.response.content is not None: + print("Got something") + self._state = 3 + return + print("no content...") + except: + print("Exception") + print("Next try") + self._try += 1 + + elif self._state == 4: + tar = gzip.decompress(self.response.content) + self.response = None + t = utarfile.TarFile(fileobj=io.BytesIO(tar)) + for i in t: + print(i.name) + if i.type == utarfile.DIRTYPE: + print("dirtype") + dirname = "/flash/sys/apps/" + i.name + if not os.path.exists(dirname): + print("making", dirname) + os.mkdir(dirname) + else: + print("dir", dirname, "exists") + else: + filename = "/flash/sys/apps/" + i.name + print("writing to", filename) + f = t.extractfile(i) + with open(filename, "wb") as of: + of.write(f.read()) + self._state = 5 + self.vm.pop() diff --git a/python_payload/apps/gr33nhouse/flow3r.toml b/python_payload/apps/gr33nhouse/flow3r.toml new file mode 100644 index 0000000000000000000000000000000000000000..2beaa7baf8ba26461e6b87f7db17fec7c5249ea3 --- /dev/null +++ b/python_payload/apps/gr33nhouse/flow3r.toml @@ -0,0 +1,11 @@ +[app] +name = "Get Apps" +menu = "Apps" + +[entry] +class = "Gr33nhouseApp" + +[metadata] +author = "Flow3r Badge Authors" +license = "LGPL-3.0-only" +url = "https://git.flow3r.garden/flow3r/flow3r-firmware" diff --git a/python_payload/apps/gr33nhouse/manual.py b/python_payload/apps/gr33nhouse/manual.py new file mode 100644 index 0000000000000000000000000000000000000000..3e93bb69ef5cde3940e8fcc4cc93173b964bd0f0 --- /dev/null +++ b/python_payload/apps/gr33nhouse/manual.py @@ -0,0 +1,42 @@ +from st3m.input import InputController, InputState +from st3m.ui import colours +from st3m.ui.view import BaseView, ViewManager +from ctx import Context +from .background import Flow3rView + + +class ManualInputView(BaseView): + input: InputController + + def __init__(self) -> None: + self.input = InputController() + self.vm = None + self.background = Flow3rView() + + def on_enter(self, vm: ViewManager | None) -> None: + super().on_enter(vm) + + if self.vm is None: + raise RuntimeError("vm is None") + + def draw(self, ctx: Context) -> None: + ctx.move_to(0, 0) + ctx.save() + ctx.rgb(*colours.BLACK) + ctx.rectangle( + -120.0, + -120.0, + 240.0, + 240.0, + ).fill() + + ctx.rgb(*colours.WHITE) + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.text("Coming soon") + ctx.restore() + + def think(self, ins: InputState, delta_ms: int) -> None: + self.input.think(ins, delta_ms) diff --git a/python_payload/apps/gr33nhouse/record.py b/python_payload/apps/gr33nhouse/record.py new file mode 100644 index 0000000000000000000000000000000000000000..be2e969e33e2f5af7e16b2f7c3494c2ea91557ae --- /dev/null +++ b/python_payload/apps/gr33nhouse/record.py @@ -0,0 +1,42 @@ +from st3m.input import InputController, InputState +from st3m.ui import colours +from st3m.ui.view import BaseView, ViewManager +from ctx import Context +from .background import Flow3rView + + +class RecordView(BaseView): + input: InputController + + def __init__(self) -> None: + self.input = InputController() + self.vm = None + self.background = Flow3rView() + + def on_enter(self, vm: ViewManager | None) -> None: + super().on_enter(vm) + + if self.vm is None: + raise RuntimeError("vm is None") + + def draw(self, ctx: Context) -> None: + ctx.move_to(0, 0) + ctx.save() + ctx.rgb(*colours.BLACK) + ctx.rectangle( + -120.0, + -120.0, + 240.0, + 240.0, + ).fill() + + ctx.rgb(*colours.WHITE) + ctx.font = "Camp Font 3" + ctx.font_size = 24 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.text("Coming soon") + ctx.restore() + + def think(self, ins: InputState, delta_ms: int) -> None: + self.input.think(ins, delta_ms)