使用Python Asyncio等待GPIO中断
Use Python Asyncio to wait for GPIO interrupt
我正在尝试将程序从 Tornado 转换为 Asyncio,第一步是使用实际的 asyncio 事件循环作为 described here。
此应用程序在嵌入式 Linux 机器上运行,我通过 sysfs/gpio subsystem 使用 GPIO,并且在其中一些 GPIO 上我正在等待中断。我可以通过以下方式将其直接集成到 Tornado IOLoop 中:
# Register with the queue
self.io_loop.add_handler(
self.gpio._value_file, self._handle_interrupt, self.io_loop._EPOLLPRI | self.io_loop.ERROR
)
在代码段中,_value_file
是可以从中读取 GPIO 的文件的文件句柄。只要该 GPIO 上的中断可用,就会触发事件 EPOLLPRI。在 Tornado 上,这非常有效。它会在中断到来后不久调用 _handle_interrupt
函数。
我的问题是,我无法将其转换为本机异步事件循环。在 the documentation for watching file descriptors 中,我只找到添加读者和作者的函数,但没有发现文件描述符上的通用事件掩码。我无法深入研究代码,因为它进入了 C。但是,查看 Tornado 层以将来自 Tornado IOLoop 的调用转换为 asyncio IOLoop,情况似乎是这样:
def add_handler(self, fd, handler, events):
fd, fileobj = self.split_fd(fd)
if fd in self.handlers:
raise ValueError("fd %s added twice" % fd)
self.handlers[fd] = (fileobj, stack_context.wrap(handler))
if events & IOLoop.READ:
self.asyncio_loop.add_reader(
fd, self._handle_events, fd, IOLoop.READ)
self.readers.add(fd)
if events & IOLoop.WRITE:
self.asyncio_loop.add_writer(
fd, self._handle_events, fd, IOLoop.WRITE)
self.writers.add(fd)
仅转换 READ 和 WRITE 标志,忽略所有其他标志。
有人可以确认目前无法使用 asyncio 来监视文件描述符上除 READ 和 WRITE 事件之外的任何事件吗?还是我做错了什么,其实有办法?
我现在自己找到了解决方案。我的主要信息来源是 this thread in the Python-tulip group and this piece of code,我不得不稍微采用一下。
主要观点是,可用于监视 POLLPRI
事件的 epoll 本身就是一个文件描述符。每当 epoll 监视的 FD 上发生事件时,epoll 文件描述符将生成一个 POLLIN
事件,可以使用带有 add_reader
的 asyncio 监视该事件。因此,我们没有直接注册,而是手动创建一个 epoll 结构并将其注册到 ioloop 中,如下所示:
self.epoll = select.epoll()
self.io_loop.add_reader(self.epoll.fileno(), self._handle_interrupt)
然后将实际的中断事件注册到epoll结构中
self.epoll.register(self.gpio._value_file, select.POLLPRI)
此时,_handle_interrupt
函数中会接收到中断事件。确保在事件处理程序中实际轮询epoll结构,否则它会不断产生读取事件
def _handle_interrupt(self):
self.epoll.poll(0)
...
使用低级 select
功能而不是高级 selectors
很重要,因为它们执行与 asyncio
中类似的事件标志过滤。以下代码摘自 selectors.EpollSelector
:
def register(self, fileobj, events, data=None):
key = super().register(fileobj, events, data)
epoll_events = 0
if events & EVENT_READ:
epoll_events |= select.EPOLLIN
if events & EVENT_WRITE:
epoll_events |= select.EPOLLOUT
try:
self._epoll.register(key.fd, epoll_events)
except BaseException:
super().unregister(fileobj)
raise
return key
可以看出,除了READ和WRITE之外的所有事件都被过滤了。因此,您不能使用高级接口来监视 POLLPRI 事件。因此,使用底层接口。
我希望这可以帮助人们解决这个问题。
我正在尝试将程序从 Tornado 转换为 Asyncio,第一步是使用实际的 asyncio 事件循环作为 described here。
此应用程序在嵌入式 Linux 机器上运行,我通过 sysfs/gpio subsystem 使用 GPIO,并且在其中一些 GPIO 上我正在等待中断。我可以通过以下方式将其直接集成到 Tornado IOLoop 中:
# Register with the queue
self.io_loop.add_handler(
self.gpio._value_file, self._handle_interrupt, self.io_loop._EPOLLPRI | self.io_loop.ERROR
)
在代码段中,_value_file
是可以从中读取 GPIO 的文件的文件句柄。只要该 GPIO 上的中断可用,就会触发事件 EPOLLPRI。在 Tornado 上,这非常有效。它会在中断到来后不久调用 _handle_interrupt
函数。
我的问题是,我无法将其转换为本机异步事件循环。在 the documentation for watching file descriptors 中,我只找到添加读者和作者的函数,但没有发现文件描述符上的通用事件掩码。我无法深入研究代码,因为它进入了 C。但是,查看 Tornado 层以将来自 Tornado IOLoop 的调用转换为 asyncio IOLoop,情况似乎是这样:
def add_handler(self, fd, handler, events):
fd, fileobj = self.split_fd(fd)
if fd in self.handlers:
raise ValueError("fd %s added twice" % fd)
self.handlers[fd] = (fileobj, stack_context.wrap(handler))
if events & IOLoop.READ:
self.asyncio_loop.add_reader(
fd, self._handle_events, fd, IOLoop.READ)
self.readers.add(fd)
if events & IOLoop.WRITE:
self.asyncio_loop.add_writer(
fd, self._handle_events, fd, IOLoop.WRITE)
self.writers.add(fd)
仅转换 READ 和 WRITE 标志,忽略所有其他标志。
有人可以确认目前无法使用 asyncio 来监视文件描述符上除 READ 和 WRITE 事件之外的任何事件吗?还是我做错了什么,其实有办法?
我现在自己找到了解决方案。我的主要信息来源是 this thread in the Python-tulip group and this piece of code,我不得不稍微采用一下。
主要观点是,可用于监视 POLLPRI
事件的 epoll 本身就是一个文件描述符。每当 epoll 监视的 FD 上发生事件时,epoll 文件描述符将生成一个 POLLIN
事件,可以使用带有 add_reader
的 asyncio 监视该事件。因此,我们没有直接注册,而是手动创建一个 epoll 结构并将其注册到 ioloop 中,如下所示:
self.epoll = select.epoll()
self.io_loop.add_reader(self.epoll.fileno(), self._handle_interrupt)
然后将实际的中断事件注册到epoll结构中
self.epoll.register(self.gpio._value_file, select.POLLPRI)
此时,_handle_interrupt
函数中会接收到中断事件。确保在事件处理程序中实际轮询epoll结构,否则它会不断产生读取事件
def _handle_interrupt(self):
self.epoll.poll(0)
...
使用低级 select
功能而不是高级 selectors
很重要,因为它们执行与 asyncio
中类似的事件标志过滤。以下代码摘自 selectors.EpollSelector
:
def register(self, fileobj, events, data=None):
key = super().register(fileobj, events, data)
epoll_events = 0
if events & EVENT_READ:
epoll_events |= select.EPOLLIN
if events & EVENT_WRITE:
epoll_events |= select.EPOLLOUT
try:
self._epoll.register(key.fd, epoll_events)
except BaseException:
super().unregister(fileobj)
raise
return key
可以看出,除了READ和WRITE之外的所有事件都被过滤了。因此,您不能使用高级接口来监视 POLLPRI 事件。因此,使用底层接口。
我希望这可以帮助人们解决这个问题。