mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Fix keyboard_remote for python 3.11 (#94570)
* started work to update keyboard_remote to work with python 3.11 * updated function names * all checks pass * fixed asyncio for python 3.11 * cleanup * Update homeassistant/components/keyboard_remote/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update __init__.py added: from __future__ import annotations * Fix typing --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
e539344d22
commit
e998320053
@ -1,11 +1,14 @@
|
|||||||
"""Receive signals from a keyboard and use it as a remote control."""
|
"""Receive signals from a keyboard and use it as a remote control."""
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import aionotify
|
from asyncinotify import Inotify, Mask
|
||||||
from evdev import InputDevice, categorize, ecodes, list_devices
|
from evdev import InputDevice, categorize, ecodes, list_devices
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -64,9 +67,9 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the keyboard_remote."""
|
"""Set up the keyboard_remote."""
|
||||||
config = config[DOMAIN]
|
domain_config: list[dict[str, Any]] = config[DOMAIN]
|
||||||
|
|
||||||
remote = KeyboardRemote(hass, config)
|
remote = KeyboardRemote(hass, domain_config)
|
||||||
remote.setup()
|
remote.setup()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -75,12 +78,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
class KeyboardRemote:
|
class KeyboardRemote:
|
||||||
"""Manage device connection/disconnection using inotify to asynchronously monitor."""
|
"""Manage device connection/disconnection using inotify to asynchronously monitor."""
|
||||||
|
|
||||||
def __init__(self, hass, config):
|
def __init__(self, hass: HomeAssistant, config: list[dict[str, Any]]) -> None:
|
||||||
"""Create handlers and setup dictionaries to keep track of them."""
|
"""Create handlers and setup dictionaries to keep track of them."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.handlers_by_name = {}
|
self.handlers_by_name = {}
|
||||||
self.handlers_by_descriptor = {}
|
self.handlers_by_descriptor = {}
|
||||||
self.active_handlers_by_descriptor = {}
|
self.active_handlers_by_descriptor: dict[str, asyncio.Future] = {}
|
||||||
|
self.inotify = None
|
||||||
self.watcher = None
|
self.watcher = None
|
||||||
self.monitor_task = None
|
self.monitor_task = None
|
||||||
|
|
||||||
@ -110,16 +114,12 @@ class KeyboardRemote:
|
|||||||
connected, and start monitoring for device connection/disconnection.
|
connected, and start monitoring for device connection/disconnection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# start watching
|
_LOGGER.debug("Start monitoring")
|
||||||
self.watcher = aionotify.Watcher()
|
|
||||||
self.watcher.watch(
|
self.inotify = Inotify()
|
||||||
alias="devinput",
|
self.watcher = self.inotify.add_watch(
|
||||||
path=DEVINPUT,
|
DEVINPUT, Mask.CREATE | Mask.ATTRIB | Mask.DELETE
|
||||||
flags=aionotify.Flags.CREATE
|
|
||||||
| aionotify.Flags.ATTRIB
|
|
||||||
| aionotify.Flags.DELETE,
|
|
||||||
)
|
)
|
||||||
await self.watcher.setup(self.hass.loop)
|
|
||||||
|
|
||||||
# add initial devices (do this AFTER starting watcher in order to
|
# add initial devices (do this AFTER starting watcher in order to
|
||||||
# avoid race conditions leading to missing device connections)
|
# avoid race conditions leading to missing device connections)
|
||||||
@ -134,7 +134,9 @@ class KeyboardRemote:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
self.active_handlers_by_descriptor[descriptor] = handler
|
self.active_handlers_by_descriptor[descriptor] = handler
|
||||||
initial_start_monitoring.add(handler.async_start_monitoring(dev))
|
initial_start_monitoring.add(
|
||||||
|
asyncio.create_task(handler.async_device_start_monitoring(dev))
|
||||||
|
)
|
||||||
|
|
||||||
if initial_start_monitoring:
|
if initial_start_monitoring:
|
||||||
await asyncio.wait(initial_start_monitoring)
|
await asyncio.wait(initial_start_monitoring)
|
||||||
@ -146,6 +148,10 @@ class KeyboardRemote:
|
|||||||
|
|
||||||
_LOGGER.debug("Cleanup on shutdown")
|
_LOGGER.debug("Cleanup on shutdown")
|
||||||
|
|
||||||
|
if self.inotify and self.watcher:
|
||||||
|
self.inotify.rm_watch(self.watcher)
|
||||||
|
self.watcher = None
|
||||||
|
|
||||||
if self.monitor_task is not None:
|
if self.monitor_task is not None:
|
||||||
if not self.monitor_task.done():
|
if not self.monitor_task.done():
|
||||||
self.monitor_task.cancel()
|
self.monitor_task.cancel()
|
||||||
@ -153,11 +159,16 @@ class KeyboardRemote:
|
|||||||
|
|
||||||
handler_stop_monitoring = set()
|
handler_stop_monitoring = set()
|
||||||
for handler in self.active_handlers_by_descriptor.values():
|
for handler in self.active_handlers_by_descriptor.values():
|
||||||
handler_stop_monitoring.add(handler.async_stop_monitoring())
|
handler_stop_monitoring.add(
|
||||||
|
asyncio.create_task(handler.async_device_stop_monitoring())
|
||||||
|
)
|
||||||
if handler_stop_monitoring:
|
if handler_stop_monitoring:
|
||||||
await asyncio.wait(handler_stop_monitoring)
|
await asyncio.wait(handler_stop_monitoring)
|
||||||
|
|
||||||
|
if self.inotify:
|
||||||
|
self.inotify.close()
|
||||||
|
self.inotify = None
|
||||||
|
|
||||||
def get_device_handler(self, descriptor):
|
def get_device_handler(self, descriptor):
|
||||||
"""Find the correct device handler given a descriptor (path)."""
|
"""Find the correct device handler given a descriptor (path)."""
|
||||||
|
|
||||||
@ -187,20 +198,21 @@ class KeyboardRemote:
|
|||||||
async def async_monitor_devices(self):
|
async def async_monitor_devices(self):
|
||||||
"""Monitor asynchronously for device connection/disconnection or permissions changes."""
|
"""Monitor asynchronously for device connection/disconnection or permissions changes."""
|
||||||
|
|
||||||
|
_LOGGER.debug("Start monitoring loop")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
async for event in self.inotify:
|
||||||
event = await self.watcher.get_event()
|
|
||||||
descriptor = f"{DEVINPUT}/{event.name}"
|
descriptor = f"{DEVINPUT}/{event.name}"
|
||||||
|
_LOGGER.debug("got events for %s: %s", descriptor, event.mask)
|
||||||
|
|
||||||
descriptor_active = descriptor in self.active_handlers_by_descriptor
|
descriptor_active = descriptor in self.active_handlers_by_descriptor
|
||||||
|
|
||||||
if (event.flags & aionotify.Flags.DELETE) and descriptor_active:
|
if (event.mask & Mask.DELETE) and descriptor_active:
|
||||||
handler = self.active_handlers_by_descriptor[descriptor]
|
handler = self.active_handlers_by_descriptor[descriptor]
|
||||||
del self.active_handlers_by_descriptor[descriptor]
|
del self.active_handlers_by_descriptor[descriptor]
|
||||||
await handler.async_stop_monitoring()
|
await handler.async_device_stop_monitoring()
|
||||||
elif (
|
elif (
|
||||||
(event.flags & aionotify.Flags.CREATE)
|
(event.mask & Mask.CREATE) or (event.mask & Mask.ATTRIB)
|
||||||
or (event.flags & aionotify.Flags.ATTRIB)
|
|
||||||
) and not descriptor_active:
|
) and not descriptor_active:
|
||||||
dev, handler = await self.hass.async_add_executor_job(
|
dev, handler = await self.hass.async_add_executor_job(
|
||||||
self.get_device_handler, descriptor
|
self.get_device_handler, descriptor
|
||||||
@ -208,31 +220,32 @@ class KeyboardRemote:
|
|||||||
if handler is None:
|
if handler is None:
|
||||||
continue
|
continue
|
||||||
self.active_handlers_by_descriptor[descriptor] = handler
|
self.active_handlers_by_descriptor[descriptor] = handler
|
||||||
await handler.async_start_monitoring(dev)
|
await handler.async_device_start_monitoring(dev)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
|
_LOGGER.debug("Monitoring canceled")
|
||||||
return
|
return
|
||||||
|
|
||||||
class DeviceHandler:
|
class DeviceHandler:
|
||||||
"""Manage input events using evdev with asyncio."""
|
"""Manage input events using evdev with asyncio."""
|
||||||
|
|
||||||
def __init__(self, hass, dev_block):
|
def __init__(self, hass: HomeAssistant, dev_block: dict[str, Any]) -> None:
|
||||||
"""Fill configuration data."""
|
"""Fill configuration data."""
|
||||||
|
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
|
||||||
key_types = dev_block.get(TYPE)
|
key_types = dev_block[TYPE]
|
||||||
|
|
||||||
self.key_values = set()
|
self.key_values = set()
|
||||||
for key_type in key_types:
|
for key_type in key_types:
|
||||||
self.key_values.add(KEY_VALUE[key_type])
|
self.key_values.add(KEY_VALUE[key_type])
|
||||||
|
|
||||||
self.emulate_key_hold = dev_block.get(EMULATE_KEY_HOLD)
|
self.emulate_key_hold = dev_block[EMULATE_KEY_HOLD]
|
||||||
self.emulate_key_hold_delay = dev_block.get(EMULATE_KEY_HOLD_DELAY)
|
self.emulate_key_hold_delay = dev_block[EMULATE_KEY_HOLD_DELAY]
|
||||||
self.emulate_key_hold_repeat = dev_block.get(EMULATE_KEY_HOLD_REPEAT)
|
self.emulate_key_hold_repeat = dev_block[EMULATE_KEY_HOLD_REPEAT]
|
||||||
self.monitor_task = None
|
self.monitor_task = None
|
||||||
self.dev = None
|
self.dev = None
|
||||||
|
|
||||||
async def async_keyrepeat(self, path, name, code, delay, repeat):
|
async def async_device_keyrepeat(self, path, name, code, delay, repeat):
|
||||||
"""Emulate keyboard delay/repeat behaviour by sending key events on a timer."""
|
"""Emulate keyboard delay/repeat behaviour by sending key events on a timer."""
|
||||||
|
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
@ -248,8 +261,9 @@ class KeyboardRemote:
|
|||||||
)
|
)
|
||||||
await asyncio.sleep(repeat)
|
await asyncio.sleep(repeat)
|
||||||
|
|
||||||
async def async_start_monitoring(self, dev):
|
async def async_device_start_monitoring(self, dev):
|
||||||
"""Start event monitoring task and issue event."""
|
"""Start event monitoring task and issue event."""
|
||||||
|
_LOGGER.debug("Keyboard async_device_start_monitoring, %s", dev.name)
|
||||||
if self.monitor_task is None:
|
if self.monitor_task is None:
|
||||||
self.dev = dev
|
self.dev = dev
|
||||||
self.monitor_task = self.hass.async_create_task(
|
self.monitor_task = self.hass.async_create_task(
|
||||||
@ -261,7 +275,7 @@ class KeyboardRemote:
|
|||||||
)
|
)
|
||||||
_LOGGER.debug("Keyboard (re-)connected, %s", dev.name)
|
_LOGGER.debug("Keyboard (re-)connected, %s", dev.name)
|
||||||
|
|
||||||
async def async_stop_monitoring(self):
|
async def async_device_stop_monitoring(self):
|
||||||
"""Stop event monitoring task and issue event."""
|
"""Stop event monitoring task and issue event."""
|
||||||
if self.monitor_task is not None:
|
if self.monitor_task is not None:
|
||||||
with suppress(OSError):
|
with suppress(OSError):
|
||||||
@ -295,6 +309,7 @@ class KeyboardRemote:
|
|||||||
_LOGGER.debug("Start device monitoring")
|
_LOGGER.debug("Start device monitoring")
|
||||||
await self.hass.async_add_executor_job(dev.grab)
|
await self.hass.async_add_executor_job(dev.grab)
|
||||||
async for event in dev.async_read_loop():
|
async for event in dev.async_read_loop():
|
||||||
|
# pylint: disable=no-member
|
||||||
if event.type is ecodes.EV_KEY:
|
if event.type is ecodes.EV_KEY:
|
||||||
if event.value in self.key_values:
|
if event.value in self.key_values:
|
||||||
_LOGGER.debug(categorize(event))
|
_LOGGER.debug(categorize(event))
|
||||||
@ -313,7 +328,7 @@ class KeyboardRemote:
|
|||||||
and self.emulate_key_hold
|
and self.emulate_key_hold
|
||||||
):
|
):
|
||||||
repeat_tasks[event.code] = self.hass.async_create_task(
|
repeat_tasks[event.code] = self.hass.async_create_task(
|
||||||
self.async_keyrepeat(
|
self.async_device_keyrepeat(
|
||||||
dev.path,
|
dev.path,
|
||||||
dev.name,
|
dev.name,
|
||||||
event.code,
|
event.code,
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/keyboard_remote",
|
"documentation": "https://www.home-assistant.io/integrations/keyboard_remote",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aionotify", "evdev"],
|
"loggers": ["aionotify", "evdev"],
|
||||||
"requirements": ["evdev==1.4.0", "aionotify==0.2.0"]
|
"requirements": ["evdev==1.6.1", "asyncinotify==4.0.2"]
|
||||||
}
|
}
|
||||||
|
@ -297,9 +297,6 @@ aiomusiccast==0.14.8
|
|||||||
# homeassistant.components.nanoleaf
|
# homeassistant.components.nanoleaf
|
||||||
aionanoleaf==0.2.1
|
aionanoleaf==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
|
||||||
aionotify==0.2.0
|
|
||||||
|
|
||||||
# homeassistant.components.notion
|
# homeassistant.components.notion
|
||||||
aionotion==2023.05.5
|
aionotion==2023.05.5
|
||||||
|
|
||||||
@ -451,6 +448,9 @@ asterisk-mbox==0.5.0
|
|||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
async-upnp-client==0.33.2
|
async-upnp-client==0.33.2
|
||||||
|
|
||||||
|
# homeassistant.components.keyboard_remote
|
||||||
|
asyncinotify==4.0.2
|
||||||
|
|
||||||
# homeassistant.components.supla
|
# homeassistant.components.supla
|
||||||
asyncpysupla==0.0.5
|
asyncpysupla==0.0.5
|
||||||
|
|
||||||
@ -758,7 +758,7 @@ eternalegypt==0.0.16
|
|||||||
eufylife-ble-client==0.1.7
|
eufylife-ble-client==0.1.7
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
# homeassistant.components.keyboard_remote
|
||||||
# evdev==1.4.0
|
# evdev==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
evohome-async==0.3.15
|
evohome-async==0.3.15
|
||||||
|
Loading…
x
Reference in New Issue
Block a user