1

I am trying to emulate the reading of a NFC chip by pressing the cmd key in a little python script that acts as a socket-io server. It's job is to notify a client after the cmd key has been pressed. It works, but emulate_nfc_tag() takes ages to execute after i press cmd. I suspect it has to do with how create_task works. I hope someone can spot how i could optimize this code.

from aiohttp import web
import socketio
import asyncio
from pynput import keyboard
import random
import string


sio = socketio.AsyncServer(cors_allowed_origins='*',
                           async_mode='aiohttp')
app = web.Application()
sio.attach(app)


def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))


async def emulate_nfc_tag():
    print("sending out nfc id")
    await sio.emit("nfc", id_generator())


def on_press(key):
    if key == keyboard.Key.shift:
        print("here we go")
        loop.create_task(emulate_nfc_tag())
    if key == keyboard.Key.esc:
        # Stop listener
        return False


async def index(request):
    """Serve the client-side application."""
    with open('../client/dist/index.html') as f:
        return web.Response(text=f.read(), content_type='text/html')


@sio.event
def connect(sid, environ):
    print("connect ", sid)


@sio.event
def disconnect(sid):
    print('disconnect ', sid)


@sio.event
async def msg(sid, data):
    print('message ', data)
    await sio.emit("msg", "Hi back")


app.router.add_static('/assets', '../client/dist/assets')
app.router.add_get('/', index)


if __name__ == '__main__':
    # web.run_app(app)
    loop = asyncio.get_event_loop()
    listener = keyboard.Listener(on_press=on_press)
    listener.start()
    # set up aiohttp - like run_app, but non-blocking
    runner = web.AppRunner(app)
    loop.run_until_complete(runner.setup())
    site = web.TCPSite(runner)
    loop.run_until_complete(site.start())
    # add more stuff to the loop
    loop.run_forever()
Ben
  • 43
  • 4
  • 1
    some of your function arguments are names of functions themselves. it is easy to accidentally add `()` after functions that are only meant to be passed by name, not called. When a function is used as a callback, for instance, it should not have `()` when it is passed to another function. – RufusVS Aug 07 '22 at 15:29
  • can you specify this? Cannot spot an instance of what you are referring to here. – Ben Aug 07 '22 at 15:42
  • I have recently copied your code to see if I can run it here, but one possible candidate might be `loop.create_task(emulate_nfc_tag())` should perhaps be `loop.create_task(emulate_nfc_tag)` But remember: I'm just guessing because of the name. It's a common error in this type of code. – RufusVS Aug 07 '22 at 15:47
  • nope, thats not it i am afraid. here i need to pass the function with () for the code to execute properly. thanks anyway! – Ben Aug 07 '22 at 15:48
  • I got the program running, but I guess I need a copy of your index.html file for it to do anything? You should add it to your question. – RufusVS Aug 07 '22 at 16:11
  • Commenting out the 2 app.router lines should still have you run the code without having to worry about any frontend stuff. Pressing cmd will then still trigger the nfc chip emulation and you can see how long it takes by waiting for the print statement to appear in the terminal – Ben Aug 07 '22 at 16:37
  • I am in windows, so I had to change the command key. I could see it create tasks (by added print statements), but they never executed until I pressed "escape". Did you say it was just slow? I couldn't wait long enough for it to return normally. Although that could be a Windows thing. – RufusVS Aug 07 '22 at 17:50
  • you don't have to run `listener` in async loop because it already runs in separated thread. You can run your `AppRunner` between `with keyboard.Listener(on_press=on_press) as listener:` and `listener.join()` – furas Aug 07 '22 at 20:33
  • I edited the code somewhat, but the core issue remains. It takes seconds until emulate_nfc_tag() is executed after the key was pressed – Ben Aug 07 '22 at 22:04

1 Answers1

1

The handlers that you configure for your keyboard run on a background thread and not in the thread that holds your asyncio application. You are calling the create_task() function from this handler, which is invalid because this function must be called from the asyncio thread.

Instead, you may want to try with the run_coroutine_threadsafe(), which is specifically designed for your use case in which you want to start a coroutine in the asyncio thread, but doing it from another thread.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152