asyncio.start_server example (!)

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
User avatar
cemox
Posts: 34
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Wed May 06, 2020 12:22 pm

I will change the utime.sleep() to asyncio.sleep(). @pythoncoder and @jomas, thank you both for your comments.

I can listen to the radio channels pretty well now.

Now, I have another problem. I want to control my web radio from a remote web browser, my esp8266 board now works as a (AP) server and (STA) client now. For keeping things simple, I just want to change the radio channel by clicking a button. On the client side,

Code: Select all

reader, writer = await asyncio.open_connection(host, port)
hadles the incoming audio data pretty well. On the server side

Code: Select all

loop.create_task(asyncio.start_server(myWeb.serve, "0.0.0.0", 80))
does the job. All project runs in a asyncio loop. But when I click "Next Station" button on the web browser, it succesfully triggers "change channel function" (I confirmed that this function works), ESP8266 looses internet connection. So the work flow is like this:

1) system initiation
2) picks the firs radio station link
3) plays it
4) starts server, shows a button
5) pushing button triggers "change the channel" function (normallly works fine, tested)
6) cannot connect internet (!)

I suspect these two lines of code clash with each other, bu I cannot figure out how.

As a last resort, I will use two esp8266 boards, one will be responsible for the server side and the other for the client side.

User avatar
cemox
Posts: 34
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Wed May 06, 2020 1:42 pm

Thank you Jomas, waiting for the blank lines solved both the server and the client side communication problems :)
jomas wrote:
Tue May 05, 2020 7:34 pm
cemox wrote:
Tue May 05, 2020 3:59 pm
At last it worked !

Putting a small delay right after

Code: Select all

writer.awrite(resp)
, solved the problem. The whole working code is :

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n".format(TICKS=t) 
    l = await reader.read(256)
    print(l)
    await writer.awrite(resp)
    utime.sleep(0.2)
    await writer.wait_closed()

loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(serve, "0.0.0.0", 80))
try: 
    loop.run_forever()
except KeyboardInterrupt:
    print("closing")
    loop.close()
You really should have a look how the http protocol works.
In your code you read 256 bytes. Instead you should read lines until you hit a double blank line.
Then you should send your response. But this should better be html code not just text.
After you have send the response you can close the connection

A delay should not be needed if you follow the rules and certainly you should not use 'utime.sleep' because that will block asyncio.
Also better not use 'awrite' because it is legacy code.

MasterOfGizmo
Posts: 50
Joined: Sun Nov 29, 2020 8:17 pm

Re: asyncio.start_server example (!)

Post by MasterOfGizmo » Thu Feb 10, 2022 9:11 pm

pythoncoder wrote:
Tue May 05, 2020 3:11 pm

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n".format(TICKS=t) 
    l = await reader.read(256)  # yield from may not work
    print(l)
    for _ in range(100):  # More RAM friendly than sending one huge string?
        await writer.awrite(resp)
    await writer.wait_closed()  # The close() call does nothing. This closes the socket.

try: 
    asyncio.run(asyncio.start_server(serve, "0.0.0.0", 80))
except KeyboardInterrupt:
    print("closing")
finally:
    asyncio.new_event_loop()  # Clear uasyncio stored state
This example does not close the servers listen port which in turn prevents it from being restarted after being stopped:

Code: Select all

Task exception wasn't retrieved
future: <Task> coro= <generator object 'start_server' at 3ffe72a0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "uasyncio/stream.py", line 1, in start_server
OSError: [Errno 112] EADDRINUSE
But where in this setup is the server socket accessible so it can be closed? This should happen in the finally clause. Calling loop.close() IMHO has nothing to do with closing the server socket itself. And in fact adding loop.close() doesn't fix this.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Fri Feb 11, 2022 11:55 am

I take your point. I guess it was written on the basis that the server would run forever, as is typical of a firmware solution. But there is nothing to stop you from writing your own start_server code based on the official one. And/or raising a ticket.
Peter Hinch
Index to my micropython libraries.

MasterOfGizmo
Posts: 50
Joined: Sun Nov 29, 2020 8:17 pm

Re: asyncio.start_server example (!)

Post by MasterOfGizmo » Fri Feb 11, 2022 1:17 pm

Thanks for the link. That code indeed tries to solve the same problem by allowing to re-use ports:

https://github.com/micropython/micropyt ... am.py#L138

But that doesn't seem to work.

I do have a completely different async server derived from tinyweb. I was just curious if this simpler approach here has an disadvantages.

Edit: The Server actually does close its socket when it receives a core.CancelledError (https://github.com/micropython/micropyt ... am.py#L115).

I think I just need to figure out how to send that signal into the server task.

MasterOfGizmo
Posts: 50
Joined: Sun Nov 29, 2020 8:17 pm

Re: asyncio.start_server example (!)

Post by MasterOfGizmo » Fri Feb 11, 2022 3:14 pm

I was hoping that something like this would do the trick:

Code: Select all

loop = asyncio.get_event_loop()
server_task = loop.create_task(asyncio.start_server(serve, "0.0.0.0", 80))
try: 
    loop.run_forever()
except KeyboardInterrupt:
    print("closing")
finally:
    server_task.cancel()
But it doesn't. Maybe because the entire loop processing has already stopped at that point and the cancel is never propagated into the server.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Fri Feb 11, 2022 4:58 pm

That is correct. At that point uasyncio has shut down.
Peter Hinch
Index to my micropython libraries.

MasterOfGizmo
Posts: 50
Joined: Sun Nov 29, 2020 8:17 pm

Re: asyncio.start_server example (!)

Post by MasterOfGizmo » Fri Feb 11, 2022 5:55 pm

So how do i catch a Keyboard interrupt (or any other exception) without interrupting the loop?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Sat Feb 12, 2022 9:58 am

I suggest you read this discussion of exceptions.

tl;dr Trapping an exception which may occur anywhere in the program is easy if you want to stop the whole script. If you want the script to recover, it's difficult; I don't know a reliable way to do this. It's an unusual requirement: a given exception is usually confined to an individual coroutine. The purpose of a keyboard interrupt is usually to stop the program.

Maybe someone else can figure this out...
Peter Hinch
Index to my micropython libraries.

Post Reply