diff --git a/Makefile b/Makefile index 6110bc7..5d4bd4d 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ format-python: ## Format the Python code .PHONY: lint lint: ## Lints the python code and documents markdownlint -c .markdownlint.yaml **/*.md - pylint src/ --ignore Microdot.py + pylint src/ --ignore-paths src/server/microdot,src/server/utemplate help: ## Show this help. @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' diff --git a/src/server/webserver.py b/src/server/webserver.py index 1b633bc..ba1fca9 100644 --- a/src/server/webserver.py +++ b/src/server/webserver.py @@ -29,11 +29,12 @@ async def index(self, request: Request): """ led_list = [{"name": led.name()} for led in self.gundam.get_all_leds()] show_list = self.gundam.config['lightshow'] - + running_show = self._is_lightshow_running() return await Template('index.html').render_async( name_of_title="Gundam LED Control", all_leds=led_list, - lightshows=show_list + lightshows=show_list, + running_show=running_show ), 200, {'Content-Type': 'text/html'} @safe_execution @@ -45,14 +46,28 @@ async def canary(self, request: Request): return "chirp", 202 def all_on(self, request: Request): + """ + Turns on all LEDs. + :param request: Ignored. + :return: HTTP 202 and message + """ self.gundam.all_on() return "All leds are on", 202 def all_off(self, request: Request): + """ + Turns off all LEDs. + :param request: Ignored. + :return: HTTP 202 and message + """ self.gundam.all_off() return "All leds are off", 202 async def _connect_to_wifi(self): + """ + Attempts to connect the Pico to WiFi. If succesful, will blink the onboard LED. + If it fails, it will halt the system. + """ ipaddress: str = await self.hardware.networking().connect_to_wifi(self.settings['ssid'], self.settings['password']) if ipaddress: print(f"Server started on {ipaddress}") @@ -73,6 +88,13 @@ async def run(self): await self.app.start_server(host='0.0.0.0', port=80, debug=True) + def _is_lightshow_running(self): + """ + :return: True if lightshow is running, False otherwise + """ + existing_task = getattr(self.gundam, "current_task", None) + return existing_task is not None + def _add_routes(self): """ Given a server adds all endpoints for Leds and lightshows @@ -80,7 +102,6 @@ def _add_routes(self): self.app.route("/")(self.index) self.app.route("/index")(self.index) self.app.route("/canary")(self.canary) - # TODO add a /stop route to stop all lightshows @self.app.route("/led//on") @safe_execution @@ -102,6 +123,28 @@ async def led_off_handler(request, led_name): self.app.route(path)(create_show_handler(method_func, self.gundam)) + @self.app.route("/lightshow/stop") + @safe_execution + async def stop_lightshow(request): + """ + Stops any currently running lightshow task on the gundam instance. + """ + existing_task = getattr(self.gundam, "current_task", None) + + if existing_task and not existing_task.done(): + existing_task.cancel() + try: + # Wait for the task to acknowledge the cancellation + await existing_task + except asyncio.CancelledError: + pass + + self.gundam.all_off() + return {"status": "stopped", "message": "Lightshow terminated"}, 200 + + self.gundam.all_off() + return {"status": "idle", "message": "No active lightshow to stop"}, 200 + # 404 Handler @self.app.errorhandler(404) async def not_found(request): @@ -112,7 +155,7 @@ async def not_found(request): path = route[1].url_pattern # The regex pattern of the URL if "" in path: - x = [led.name() for led in self.gundam.get_all_leds() if led.enabled() ] + x = [led.name() for led in self.gundam.get_all_leds() if led.enabled()] for led_name in x: complete_path = path.replace("", led_name) urls.append(complete_path) diff --git a/src/templates/index.html b/src/templates/index.html index daabc8c..9ff515f 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -1,4 +1,4 @@ -{% args name_of_title, all_leds, lightshows %} +{% args name_of_title, all_leds, lightshows, running_show %} @@ -22,6 +22,11 @@

Control individual LEDs

Light Shows

+{% if running_show %} +

A lightshow is currently running.

+{% else %} +

No lightshow is running.

+{% endif %}
    {% for show in lightshows %}
  • @@ -30,6 +35,11 @@

    Light Shows

  • {% endfor %} +
  • +
    + +
    +

diff --git a/tests/LocalServerTest.py b/tests/LocalServerTest.py index 476192a..0d2f610 100644 --- a/tests/LocalServerTest.py +++ b/tests/LocalServerTest.py @@ -27,6 +27,15 @@ async def activation(self): print("Mobile Doll activation") return + async def infinite(self): + try: + while True: + await asyncio.sleep(1) + print("Hi") + except asyncio.CancelledError: + print("Infinite loop cancelled") + raise + def main(): @@ -42,6 +51,11 @@ def main(): "name": "Activate Virgo", "path": "activation", "method": "activation" + }, + { + "name": "Infinitelooop test", + "path": "infinite", + "method": "infinite" } ] }