backend: allow device objects to handle notifications

This commit is contained in:
Wojciech Nawrocki 2020-04-05 23:13:19 +02:00 committed by Peter F. Patel-Schneider
parent 0259e44c31
commit 362d43a7a1
5 changed files with 44 additions and 4 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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.)

View File

@ -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)