From b8a3561f23bed376156386278374246879451507 Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Mon, 25 Sep 2023 06:54:31 +0000 Subject: [PATCH 1/2] Modified Websocket example to use asyncio --- docs/examples.rst | 5 ++- examples/httpserver_sse.py | 2 +- examples/httpserver_websocket.py | 55 ++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index f37551a..312690c 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -315,12 +315,15 @@ the client and the server. Remember, that because Websockets also receive data, you have to explicitly call ``.receive()`` on the ``Websocket`` object to get the message. This is anologous to calling ``.poll()`` on the ``Server`` object. +The following example uses ``asyncio``, which has to be installed separately. It is not necessary to use ``asyncio`` to use Websockets, +but it is recommended as it makes it easier to handle multiple tasks. It can be used in any of the examples, but here it is particularly useful. + **Because of the limited number of concurrently open sockets, it is not possible to process more than one Websocket response at the same time. This might change in the future, but for now, it is recommended to use Websocket only with one client at a time.** .. literalinclude:: ../examples/httpserver_websocket.py :caption: examples/httpserver_websocket.py - :emphasize-lines: 12,21,67-73,83,90 + :emphasize-lines: 12,20,65-72,88,99 :linenos: Multiple servers diff --git a/examples/httpserver_sse.py b/examples/httpserver_sse.py index 3ad6c73..51b93a8 100644 --- a/examples/httpserver_sse.py +++ b/examples/httpserver_sse.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 Dan Halbert for Adafruit Industries +# SPDX-FileCopyrightText: 2023 Michał Pokusa # # SPDX-License-Identifier: Unlicense diff --git a/examples/httpserver_websocket.py b/examples/httpserver_websocket.py index 396a6c1..ac8e86f 100644 --- a/examples/httpserver_websocket.py +++ b/examples/httpserver_websocket.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2023 Dan Halbert for Adafruit Industries +# SPDX-FileCopyrightText: 2023 Michał Pokusa # # SPDX-License-Identifier: Unlicense -from time import monotonic +from asyncio import create_task, gather, run, sleep as async_sleep import board import microcontroller import neopixel @@ -17,9 +17,7 @@ pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) - websocket: Websocket = None -next_message_time = monotonic() HTML_TEMPLATE = """ @@ -75,17 +73,40 @@ def connect_client(request: Request): server.start(str(wifi.radio.ipv4_address)) -while True: - server.poll() - # Check for incoming messages from client - if websocket is not None: - if (data := websocket.receive(True)) is not None: - r, g, b = int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16) - pixel.fill((r, g, b)) - - # Send a message every second - if websocket is not None and next_message_time < monotonic(): - cpu_temp = round(microcontroller.cpu.temperature, 2) - websocket.send_message(str(cpu_temp)) - next_message_time = monotonic() + 1 + +async def handle_http_requests(): + while True: + server.poll() + + await async_sleep(0) + + +async def handle_websocket_requests(): + while True: + if websocket is not None: + if (data := websocket.receive(fail_silently=True)) is not None: + r, g, b = int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16) + pixel.fill((r, g, b)) + + await async_sleep(0) + + +async def send_websocket_messages(): + while True: + if websocket is not None: + cpu_temp = round(microcontroller.cpu.temperature, 2) + websocket.send_message(str(cpu_temp), fail_silently=True) + + await async_sleep(1) + + +async def main(): + await gather( + create_task(handle_http_requests()), + create_task(handle_websocket_requests()), + create_task(send_websocket_messages()), + ) + + +run(main()) From 600a69235c938dea2e594884d7c1cbc42ecb49dc Mon Sep 17 00:00:00 2001 From: michalpokusa <72110769+michalpokusa@users.noreply.github.com> Date: Mon, 25 Sep 2023 06:55:12 +0000 Subject: [PATCH 2/2] Added simple poll example with asyncio --- docs/examples.rst | 12 ++++ examples/httpserver_start_and_poll_asyncio.py | 62 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 examples/httpserver_start_and_poll_asyncio.py diff --git a/docs/examples.rst b/docs/examples.rst index 312690c..c310821 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -88,6 +88,18 @@ a running total of the last 10 samples. :emphasize-lines: 29,38 :linenos: + +If you need to perform some action periodically, or there are multiple tasks that need to be done, +it might be better to use ``asyncio`` module to handle them, which makes it really easy to add new tasks +without needing to manually manage the timing of each task. + +``asyncio`` **is not included in CircuitPython by default, it has to be installed separately.** + +.. literalinclude:: ../examples/httpserver_start_and_poll_asyncio.py + :caption: examples/httpserver_start_and_poll_asyncio.py + :emphasize-lines: 5,33,42,45,50,55-62 + :linenos: + Server with MDNS ---------------- diff --git a/examples/httpserver_start_and_poll_asyncio.py b/examples/httpserver_start_and_poll_asyncio.py new file mode 100644 index 0000000..3fea850 --- /dev/null +++ b/examples/httpserver_start_and_poll_asyncio.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2023 Michał Pokusa +# +# SPDX-License-Identifier: Unlicense + +from asyncio import create_task, gather, run, sleep as async_sleep +import socketpool +import wifi + +from adafruit_httpserver import ( + Server, + REQUEST_HANDLED_RESPONSE_SENT, + Request, + FileResponse, +) + + +pool = socketpool.SocketPool(wifi.radio) +server = Server(pool, "/static", debug=True) + + +@server.route("/") +def base(request: Request): + """ + Serve the default index.html file. + """ + return FileResponse(request, "index.html") + + +# Start the server. +server.start(str(wifi.radio.ipv4_address)) + + +async def handle_http_requests(): + while True: + # Process any waiting requests + pool_result = server.poll() + + if pool_result == REQUEST_HANDLED_RESPONSE_SENT: + # Do something only after handling a request + pass + + await async_sleep(0) + + +async def do_something_useful(): + while True: + # Do something useful in this section, + # for example read a sensor and capture an average, + # or a running total of the last 10 samples + await async_sleep(1) + + # If you want you can stop the server by calling server.stop() anywhere in your code + + +async def main(): + await gather( + create_task(handle_http_requests()), + create_task(do_something_useful()), + ) + + +run(main())