backend: allow device objects to handle notifications
This commit is contained in:
parent
0259e44c31
commit
362d43a7a1
|
@ -3,7 +3,9 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
import errno as _errno
|
||||
|
||||
from logging import INFO as _INFO
|
||||
from logging import WARNING as _WARNING
|
||||
from logging import getLogger
|
||||
from typing import Optional
|
||||
|
||||
import hidapi as _hid
|
||||
import solaar.configuration as _configuration
|
||||
|
@ -81,6 +83,9 @@ class Device(object):
|
|||
self._polling_rate = None
|
||||
self._power_switch = None
|
||||
|
||||
# See `add_notification_handler`
|
||||
self._notification_handlers = {}
|
||||
|
||||
self.handle = None
|
||||
self.path = None
|
||||
self.product_id = None
|
||||
|
@ -357,7 +362,7 @@ class Device(object):
|
|||
raise _base.NoSuchDevice(number=index, receiver=receiver, error='Unknown 27Mhz device number')
|
||||
return kind
|
||||
|
||||
def enable_notifications(self, enable=True):
|
||||
def enable_connection_notifications(self, enable=True):
|
||||
"""Enable or disable device (dis)connection notifications on this
|
||||
receiver."""
|
||||
if not bool(self.receiver) or self.protocol >= 2.0:
|
||||
|
@ -382,6 +387,35 @@ class Device(object):
|
|||
_log.info('%s: device notifications %s %s', self, 'enabled' if enable else 'disabled', flag_names)
|
||||
return flag_bits if ok else None
|
||||
|
||||
def add_notification_handler(self, id: str, fn):
|
||||
"""Adds the notification handling callback `fn` to this device under name `id`.
|
||||
If a callback has already been registered under this name, it's replaced with
|
||||
the argument.
|
||||
The callback will be invoked whenever the device emits an event message, and
|
||||
the resulting notification hasn't been handled by another handler on this device
|
||||
(order is not guaranteed, so handlers should not overlap in functionality).
|
||||
The callback should have type `(PairedDevice, Notification) -> Optional[bool]`.
|
||||
It should return `None` if it hasn't handled the notification, return `True`
|
||||
if it did so successfully and return `False` if an error should be reported
|
||||
(malformed notification, etc).
|
||||
"""
|
||||
self._notification_handlers[id] = fn
|
||||
|
||||
def remove_notification_handler(self, id: str):
|
||||
"""Unregisters the notification handler under name `id`."""
|
||||
|
||||
if id not in self._notification_handlers and _log.isEnabledFor(_WARNING):
|
||||
_log.warn(f'Tried to remove nonexistent notification handler {id} from device {self}.')
|
||||
else:
|
||||
del self._notification_handlers[id]
|
||||
|
||||
def handle_notification(self, n) -> Optional[bool]:
|
||||
for h in self._notification_handlers.values():
|
||||
ret = h(self, n)
|
||||
if ret is not None:
|
||||
return ret
|
||||
return None
|
||||
|
||||
def request(self, request_id, *params, no_reply=False):
|
||||
return _base.request(self.handle or self.receiver.handle, self.number, request_id, *params, no_reply=no_reply)
|
||||
|
||||
|
|
|
@ -103,6 +103,12 @@ def _process_device_notification(device, status, n):
|
|||
# HID++ 1.0 requests, should never get here
|
||||
assert n.sub_id & 0x80 == 0
|
||||
|
||||
# Allow the device object to handle the notification using custom
|
||||
# per-device state.
|
||||
handling_ret = device.handle_notification(n)
|
||||
if handling_ret is not None:
|
||||
return handling_ret
|
||||
|
||||
# 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications
|
||||
if n.sub_id >= 0x40:
|
||||
if len(n.data) == _DJ_NOTIFICATION_LENGTH:
|
||||
|
|
|
@ -109,7 +109,7 @@ class Receiver(object):
|
|||
self._remaining_pairings = ps - 5 if ps >= 5 else -1
|
||||
return self._remaining_pairings
|
||||
|
||||
def enable_notifications(self, enable=True):
|
||||
def enable_connection_notifications(self, enable=True):
|
||||
"""Enable or disable device (dis)connection notifications on this
|
||||
receiver."""
|
||||
if not self.handle:
|
||||
|
|
|
@ -290,7 +290,7 @@ class DeviceStatus(dict):
|
|||
# get cleared when the device is turned off (but not when the device
|
||||
# goes idle, and we can't tell the difference right now).
|
||||
if d.protocol < 2.0:
|
||||
self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications()
|
||||
self[KEYS.NOTIFICATION_FLAGS] = d.enable_connection_notifications()
|
||||
|
||||
# If we've been inactive for a long time, forget anything
|
||||
# about the battery. (This is probably unnecessary.)
|
||||
|
|
|
@ -80,7 +80,7 @@ class ReceiverListener(_listener.EventsListener):
|
|||
def has_started(self):
|
||||
if _log.isEnabledFor(_INFO):
|
||||
_log.info('%s: notifications listener has started (%s)', self.receiver, self.receiver.handle)
|
||||
notification_flags = self.receiver.enable_notifications()
|
||||
notification_flags = self.receiver.enable_connection_notifications()
|
||||
self.receiver.status[_status.KEYS.NOTIFICATION_FLAGS] = notification_flags
|
||||
self.receiver.notify_devices()
|
||||
self._status_changed(self.receiver) # , _status.ALERT.NOTIFICATION)
|
||||
|
|
Loading…
Reference in New Issue