renamed 'events' to 'notifications'
in order to match the name in Logitech's documentation
This commit is contained in:
parent
7617a1ef8e
commit
19cd40cfdd
|
@ -43,7 +43,7 @@ class ReceiverListener(_listener.EventsListener):
|
|||
"""Keeps the status of a Unifying Receiver.
|
||||
"""
|
||||
def __init__(self, receiver, status_changed_callback=None):
|
||||
super(ReceiverListener, self).__init__(receiver, self._events_handler)
|
||||
super(ReceiverListener, self).__init__(receiver, self._notifications_handler)
|
||||
self.tick_period = _POLL_TICK
|
||||
self._last_tick = 0
|
||||
|
||||
|
@ -65,13 +65,13 @@ class ReceiverListener(_listener.EventsListener):
|
|||
receiver.register_new_device = _MethodType(_register_with_status, receiver)
|
||||
|
||||
def has_started(self):
|
||||
_log.info("events listener has started")
|
||||
_log.info("notifications listener has started")
|
||||
self.receiver.enable_notifications()
|
||||
self.receiver.notify_devices()
|
||||
self._status_changed(self.receiver, _status.ALERT.LOW)
|
||||
|
||||
def has_stopped(self):
|
||||
_log.info("events listener has stopped")
|
||||
_log.info("notifications listener has stopped")
|
||||
if self.receiver:
|
||||
self.receiver.enable_notifications(False)
|
||||
self.receiver.close()
|
||||
|
@ -119,26 +119,26 @@ class ReceiverListener(_listener.EventsListener):
|
|||
if device.status is None:
|
||||
self.status_changed_callback(r)
|
||||
|
||||
def _events_handler(self, event):
|
||||
def _notifications_handler(self, n):
|
||||
assert self.receiver
|
||||
if event.devnumber == 0xFF:
|
||||
# a receiver event
|
||||
if n.devnumber == 0xFF:
|
||||
# a receiver notification
|
||||
if self.receiver.status is not None:
|
||||
self.receiver.status.process_event(event)
|
||||
self.receiver.status.process_notification(n)
|
||||
else:
|
||||
# a device event
|
||||
assert event.devnumber > 0 and event.devnumber <= self.receiver.max_devices
|
||||
already_known = event.devnumber in self.receiver
|
||||
dev = self.receiver[event.devnumber]
|
||||
# a device notification
|
||||
assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices
|
||||
already_known = n.devnumber in self.receiver
|
||||
dev = self.receiver[n.devnumber]
|
||||
if dev and dev.status is not None:
|
||||
dev.status.process_event(event)
|
||||
dev.status.process_notification(n)
|
||||
if self.receiver.status.lock_open and not already_known:
|
||||
# this should be the first event after a device was paired
|
||||
assert event.sub_id == 0x41 and event.address == 0x04
|
||||
# this should be the first notification after a device was paired
|
||||
assert n.sub_id == 0x41 and n.address == 0x04
|
||||
_log.info("pairing detected new device")
|
||||
self.receiver.status.new_device = dev
|
||||
else:
|
||||
_log.warn("received event %s for invalid device %d: %s", event, event.devnumber, dev)
|
||||
_log.warn("received notification %s for invalid device %d: %s", n, n.devnumber, dev)
|
||||
|
||||
def __str__(self):
|
||||
return '<ReceiverListener(%s,%s)>' % (self.receiver.path, self.receiver.handle)
|
||||
|
|
|
@ -91,7 +91,7 @@ def _run(args):
|
|||
|
||||
from logitech.unifying_receiver import status
|
||||
|
||||
# callback delivering status events from the receiver/devices to the UI
|
||||
# callback delivering status notifications from the receiver/devices to the UI
|
||||
def status_changed(receiver, device=None, alert=status.ALERT.NONE, reason=None):
|
||||
if alert & status.ALERT.MED:
|
||||
GObject.idle_add(window.present)
|
||||
|
|
|
@ -87,12 +87,13 @@ def _print_receiver(receiver, verbose=False):
|
|||
|
||||
print (" Has %d paired device(s)." % paired_count)
|
||||
|
||||
notifications = receiver.request(0x8100)
|
||||
if notifications:
|
||||
notifications = ord(notifications[0:1]) << 16 | ord(notifications[1:2]) << 8
|
||||
if notifications:
|
||||
notification_flags = receiver.request(0x8100)
|
||||
if notification_flags:
|
||||
notification_flags = ord(notification_flags[0:1]) << 16 | ord(notification_flags[1:2]) << 8
|
||||
if notification_flags:
|
||||
from logitech.unifying_receiver import hidpp10
|
||||
print (" Enabled notifications: %06X = %s." % (notifications, ', '.join(hidpp10.NOTIFICATION_FLAG.flag_names(notifications))))
|
||||
notification_names = hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
|
||||
print (" Enabled notifications: 0x%06X = %s." % (notification_flags, ', '.join(notification_names)))
|
||||
else:
|
||||
print (" All notifications disabled.")
|
||||
|
||||
|
@ -184,37 +185,41 @@ def pair_device(receiver, args):
|
|||
|
||||
done = [False]
|
||||
|
||||
def _events_handler(event):
|
||||
if event.devnumber == 0xFF:
|
||||
r_status.process_event(event)
|
||||
def _notification_handler(n):
|
||||
if n.devnumber == 0xFF:
|
||||
r_status.process_notification(n)
|
||||
if not r_status.lock_open:
|
||||
done[0] = True
|
||||
elif event.sub_id == 0x41 and event.address == 0x04:
|
||||
if event.devnumber not in known_devices:
|
||||
r_status.new_device = receiver[event.devnumber]
|
||||
elif n.sub_id == 0x41 and n.address == 0x04:
|
||||
if n.devnumber not in known_devices:
|
||||
r_status.new_device = receiver[n.devnumber]
|
||||
|
||||
from logitech.unifying_receiver import base
|
||||
base.events_hook = _events_handler
|
||||
base.notifications_hook = _notification_handler
|
||||
|
||||
# check if it's necessary to set the notification flags
|
||||
notifications = receiver.request(0x8100)
|
||||
if notifications:
|
||||
notifications = ord(notifications[:1]) + ord(notifications[1:2]) + ord(notifications[2:3])
|
||||
if not notifications:
|
||||
notification_flags = receiver.request(0x8100)
|
||||
if notification_flags:
|
||||
# just to see if any bits are set
|
||||
notification_flags = ord(notification_flags[:1]) + ord(notification_flags[1:2]) + ord(notification_flags[2:3])
|
||||
if not notification_flags:
|
||||
# if there are any notifications set, just assume the one we need is already set
|
||||
receiver.enable_notifications()
|
||||
receiver.set_lock(False, timeout=20)
|
||||
print ("Pairing: turn your new device on (timing out in 20 seconds).")
|
||||
|
||||
while not done[0]:
|
||||
event = base.read(receiver.handle, 2000)
|
||||
if event:
|
||||
event = base.make_event(*event)
|
||||
if event:
|
||||
_events_handler(event)
|
||||
n = base.read(receiver.handle, 2000)
|
||||
if n:
|
||||
n = base.make_notification(*n)
|
||||
if n:
|
||||
_notification_handler(n)
|
||||
|
||||
if not notifications:
|
||||
if not notification_flags:
|
||||
# only clear the flags if they weren't set before, otherwise a
|
||||
# concurrently running Solaar app will stop working properly
|
||||
receiver.enable_notifications(False)
|
||||
base.events_hook = None
|
||||
base.notifications_hook = None
|
||||
|
||||
if r_status.new_device:
|
||||
dev = r_status.new_device
|
||||
|
|
|
@ -218,42 +218,42 @@ def _skip_incoming(handle):
|
|||
#
|
||||
#
|
||||
|
||||
"""The function that may be called on incoming events.
|
||||
"""The function that may be called on incoming notifications.
|
||||
|
||||
The hook must be a callable accepting one tuple parameter, with the format
|
||||
``(<int> devnumber, <bytes[2]> request_id, <bytes> data)``.
|
||||
|
||||
This hook will only be called by the request()/ping() functions, when received
|
||||
replies do not match the expected request_id. As such, it is not suitable for
|
||||
intercepting broadcast events from the device (e.g. special keys being pressed,
|
||||
battery charge events, etc), at least not in a timely manner.
|
||||
intercepting broadcast notifications from the device (e.g. special keys being
|
||||
pressed, battery charge notifications, etc), at least not in a timely manner.
|
||||
"""
|
||||
events_hook = None
|
||||
notifications_hook = None
|
||||
|
||||
def _unhandled(report_id, devnumber, data):
|
||||
"""Deliver a possible event to the unhandled_hook (if any)."""
|
||||
if events_hook:
|
||||
event = make_event(devnumber, data)
|
||||
if event:
|
||||
events_hook(event)
|
||||
"""Deliver a possible notification to the notifications_hook (if any)."""
|
||||
if notifications_hook:
|
||||
n = make_notification(devnumber, data)
|
||||
if n:
|
||||
notifications_hook(n)
|
||||
|
||||
def make_notification(devnumber, data):
|
||||
"""Guess if this is a notification (and not just a request reply), and
|
||||
return a Notification tuple if it is."""
|
||||
sub_id = ord(data[:1])
|
||||
if sub_id & 0x80 != 0x80:
|
||||
# HID++ 1.0 standard notifications are 0x40 - 0x7F
|
||||
# HID++ 2.0 feature notifications have the SoftwareID 0
|
||||
address = ord(data[1:2])
|
||||
if sub_id >= 0x40 or address & 0x0F == 0x00:
|
||||
return _HIDPP_Notification(devnumber, sub_id, address, data[2:])
|
||||
|
||||
from collections import namedtuple
|
||||
_Event = namedtuple('_Event', ['devnumber', 'sub_id', 'address', 'data'])
|
||||
_Event.__str__ = lambda self: 'Event(%d,%02X,%02X,%s)' % (self.devnumber, self.sub_id, self.address, _strhex(self.data))
|
||||
_Event.__unicode__ = _Event.__str__
|
||||
_HIDPP_Notification = namedtuple('_HIDPP_Notification', ['devnumber', 'sub_id', 'address', 'data'])
|
||||
_HIDPP_Notification.__str__ = lambda self: 'Notification(%d,%02X,%02X,%s)' % (self.devnumber, self.sub_id, self.address, _strhex(self.data))
|
||||
_HIDPP_Notification.__unicode__ = _HIDPP_Notification.__str__
|
||||
del namedtuple
|
||||
|
||||
def make_event(devnumber, data):
|
||||
sub_id = ord(data[:1])
|
||||
if devnumber == 0xFF:
|
||||
if sub_id == 0x4A: # receiver lock event
|
||||
return _Event(devnumber, sub_id, ord(data[1:2]), data[2:])
|
||||
elif sub_id & 0x80 != 0x80:
|
||||
address = ord(data[1:2])
|
||||
if sub_id >= 0x40 or address & 0x01 == 0:
|
||||
return _Event(devnumber, sub_id, address, data[2:])
|
||||
|
||||
|
||||
def request(handle, devnumber, request_id, *params):
|
||||
"""Makes a feature call to a device and waits for a matching reply.
|
||||
|
@ -269,21 +269,22 @@ def request(handle, devnumber, request_id, *params):
|
|||
assert isinstance(request_id, int)
|
||||
if devnumber != 0xFF and request_id < 0x8000:
|
||||
timeout = _DEVICE_REQUEST_TIMEOUT
|
||||
# for HID++ 2.0 feature request, randomize the swid to make it easier to
|
||||
# recognize the reply for this request. also, always set the last bit
|
||||
# (0) in swid, to make events easier to identify
|
||||
request_id = (request_id & 0xFFF0) | _random_bits(4) | 0x01
|
||||
# for HID++ 2.0 feature requests, randomize the SoftwareId to make it
|
||||
# easier to recognize the reply for this request. also, always set the
|
||||
# most significant bit (8) in SoftwareId, to make notifications easier
|
||||
# to distinguish from request replies
|
||||
request_id = (request_id & 0xFFF0) | 0x08 | _random_bits(3)
|
||||
else:
|
||||
timeout = _RECEIVER_REQUEST_TIMEOUT
|
||||
request_str = _pack(b'!H', request_id)
|
||||
|
||||
params = b''.join(_pack(b'B', p) if type(p) == int else p for p in params)
|
||||
params = b''.join(_pack(b'B', p) if isinstance(p, int) else p for p in params)
|
||||
# if _log.isEnabledFor(_DEBUG):
|
||||
# _log.debug("(%s) device %d request_id {%04X} params [%s]", handle, devnumber, request_id, _strhex(params))
|
||||
request_data = _pack(b'!H', request_id) + params
|
||||
|
||||
_skip_incoming(handle)
|
||||
ihandle = int(handle)
|
||||
write(ihandle, devnumber, request_str + params)
|
||||
write(ihandle, devnumber, request_data)
|
||||
|
||||
while True:
|
||||
now = _timestamp()
|
||||
|
@ -293,7 +294,7 @@ def request(handle, devnumber, request_id, *params):
|
|||
if reply:
|
||||
report_id, reply_devnumber, reply_data = reply
|
||||
if reply_devnumber == devnumber:
|
||||
if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_str:
|
||||
if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]:
|
||||
error = ord(reply_data[3:4])
|
||||
|
||||
# if error == _hidpp10.ERROR.resource_error: # device unreachable
|
||||
|
@ -304,18 +305,18 @@ def request(handle, devnumber, request_id, *params):
|
|||
# _log.error("(%s) device %d error on request {%04X}: unknown device", handle, devnumber, request_id)
|
||||
# raise NoSuchDevice(number=devnumber, request=request_id)
|
||||
|
||||
_log.debug("(%s) device %d error on request {%04X}: %d = %s",
|
||||
_log.debug("(%s) device 0x%02X error on request {%04X}: %d = %s",
|
||||
handle, devnumber, request_id, error, _hidpp10.ERROR[error])
|
||||
break
|
||||
|
||||
if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_str:
|
||||
if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]:
|
||||
# a HID++ 2.0 feature call returned with an error
|
||||
error = ord(reply_data[3:4])
|
||||
_log.error("(%s) device %d error on feature request {%04X}: %d = %s",
|
||||
handle, devnumber, request_id, error, _hidpp20.ERROR[error])
|
||||
raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params)
|
||||
|
||||
if reply_data[:2] == request_str:
|
||||
if reply_data[:2] == request_data[:2]:
|
||||
if request_id & 0xFF00 == 0x8300:
|
||||
# long registry r/w should return a long reply
|
||||
assert report_id == 0x11
|
||||
|
@ -329,7 +330,7 @@ def request(handle, devnumber, request_id, *params):
|
|||
if reply_data[2:3] == params[:1]:
|
||||
return reply_data[2:]
|
||||
else:
|
||||
# hm, not mathing my request, and certainly not an event
|
||||
# hm, not mathing my request, and certainly not a notification
|
||||
continue
|
||||
else:
|
||||
return reply_data[2:]
|
||||
|
@ -352,16 +353,15 @@ def ping(handle, devnumber):
|
|||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("(%s) pinging device %d", handle, devnumber)
|
||||
|
||||
# randomize the SoftwareId and mark byte to be able to identify the ping
|
||||
# reply, and set most significant (0x8) bit in SoftwareId so that the reply
|
||||
# is always distinguishable from notifications
|
||||
request_id = 0x0018 | _random_bits(3)
|
||||
request_data = _pack(b'!HBBB', request_id, 0, 0, _random_bits(8))
|
||||
|
||||
_skip_incoming(handle)
|
||||
ihandle = int(handle)
|
||||
|
||||
# randomize the swid and mark byte to positively identify the ping reply,
|
||||
# and set the last (0) bit in swid to make it easier to distinguish requests
|
||||
# from events
|
||||
request_id = 0x0010 | _random_bits(4) | 0x01
|
||||
request_str = _pack(b'!H', request_id)
|
||||
ping_mark = _pack(b'B', _random_bits(8))
|
||||
write(ihandle, devnumber, request_str + b'\x00\x00' + ping_mark)
|
||||
write(ihandle, devnumber, request_data)
|
||||
|
||||
while True:
|
||||
now = _timestamp()
|
||||
|
@ -371,11 +371,11 @@ def ping(handle, devnumber):
|
|||
if reply:
|
||||
report_id, number, data = reply
|
||||
if number == devnumber:
|
||||
if data[:2] == request_str and data[4:5] == ping_mark:
|
||||
if data[:2] == request_data[:2] and data[4:5] == request_data[-1:]:
|
||||
# HID++ 2.0+ device, currently connected
|
||||
return ord(data[2:3]) + ord(data[3:4]) / 10.0
|
||||
|
||||
if report_id == 0x10 and data[:1] == b'\x8F' and data[1:3] == request_str:
|
||||
if report_id == 0x10 and data[:1] == b'\x8F' and data[1:3] == request_data[:2]:
|
||||
assert data[-1:] == b'\x00'
|
||||
error = ord(data[3:4])
|
||||
|
||||
|
|
|
@ -24,7 +24,10 @@ from . import base as _base
|
|||
#
|
||||
|
||||
class ThreadedHandle(object):
|
||||
"""A thread-local wrapper with different open handles for each thread."""
|
||||
"""A thread-local wrapper with different open handles for each thread.
|
||||
|
||||
Closing a ThreadedHandle will close all handles.
|
||||
"""
|
||||
|
||||
__slots__ = ['path', '_local', '_handles']
|
||||
|
||||
|
@ -85,30 +88,40 @@ class ThreadedHandle(object):
|
|||
#
|
||||
#
|
||||
|
||||
# How long to wait during a read for the next packet.
|
||||
# Ideally this should be rather long (10s ?), but the read is blocking
|
||||
# and this means that when the thread is signalled to stop, it would take
|
||||
# a while for it to acknowledge it.
|
||||
_EVENT_READ_TIMEOUT = 500
|
||||
|
||||
# After this many read that did not produce a packet, call the tick() method.
|
||||
_IDLE_READS = 4
|
||||
|
||||
|
||||
class EventsListener(_threading.Thread):
|
||||
"""Listener thread for events from the Unifying Receiver.
|
||||
"""Listener thread for notifications from the Unifying Receiver.
|
||||
|
||||
Incoming packets will be passed to the callback function in sequence.
|
||||
"""
|
||||
def __init__(self, receiver, events_callback):
|
||||
def __init__(self, receiver, notifications_callback):
|
||||
super(EventsListener, self).__init__(name=self.__class__.__name__)
|
||||
|
||||
self.daemon = True
|
||||
self._active = False
|
||||
|
||||
self.receiver = receiver
|
||||
self._queued_events = _Queue(32)
|
||||
self._events_callback = events_callback
|
||||
self._queued_notifications = _Queue(32)
|
||||
self._notifications_callback = notifications_callback
|
||||
|
||||
self.tick_period = 0
|
||||
|
||||
def run(self):
|
||||
self._active = True
|
||||
_base.events_hook = self._events_hook
|
||||
|
||||
# This is necessary because notification packets might be received
|
||||
# during requests made by our callback.
|
||||
_base.notifications_hook = self._notifications_hook
|
||||
|
||||
ihandle = int(self.receiver.handle)
|
||||
_log.info("started with %s (%d)", self.receiver, ihandle)
|
||||
|
||||
|
@ -118,28 +131,28 @@ class EventsListener(_threading.Thread):
|
|||
idle_reads = 0
|
||||
|
||||
while self._active:
|
||||
if self._queued_events.empty():
|
||||
if self._queued_notifications.empty():
|
||||
try:
|
||||
# _log.debug("read next event")
|
||||
event = _base.read(ihandle, _EVENT_READ_TIMEOUT)
|
||||
# _log.debug("read next notification")
|
||||
n = _base.read(ihandle, _EVENT_READ_TIMEOUT)
|
||||
except _base.NoReceiver:
|
||||
_log.warning("receiver disconnected")
|
||||
self.receiver.close()
|
||||
break
|
||||
|
||||
if event:
|
||||
event = _base.make_event(*event)
|
||||
if n:
|
||||
n = _base.make_notification(*n)
|
||||
else:
|
||||
# deliver any queued events
|
||||
event = self._queued_events.get()
|
||||
# deliver any queued notifications
|
||||
n = self._queued_notifications.get()
|
||||
|
||||
if event:
|
||||
if n:
|
||||
# if _log.isEnabledFor(_DEBUG):
|
||||
# _log.debug("processing event %s", event)
|
||||
# _log.debug("processing notification %s", n)
|
||||
try:
|
||||
self._events_callback(event)
|
||||
self._notifications_callback(n)
|
||||
except:
|
||||
_log.exception("processing event %s", event)
|
||||
_log.exception("processing notification %s", n)
|
||||
elif self.tick_period:
|
||||
idle_reads += 1
|
||||
if idle_reads % _IDLE_READS == 0:
|
||||
|
@ -149,8 +162,8 @@ class EventsListener(_threading.Thread):
|
|||
last_tick = now
|
||||
self.tick(now)
|
||||
|
||||
_base.unhandled_hook = None
|
||||
del self._queued_events
|
||||
_base.notifications_hook = None
|
||||
del self._queued_notifications
|
||||
|
||||
self.has_stopped()
|
||||
|
||||
|
@ -159,7 +172,8 @@ class EventsListener(_threading.Thread):
|
|||
self._active = False
|
||||
|
||||
def has_started(self):
|
||||
"""Called right after the thread has started."""
|
||||
"""Called right after the thread has started, and before it starts
|
||||
reading notification packets."""
|
||||
pass
|
||||
|
||||
def has_stopped(self):
|
||||
|
@ -167,16 +181,16 @@ class EventsListener(_threading.Thread):
|
|||
pass
|
||||
|
||||
def tick(self, timestamp):
|
||||
"""Called about every tick_period seconds, if set."""
|
||||
"""Called about every tick_period seconds."""
|
||||
pass
|
||||
|
||||
def _events_hook(self, event):
|
||||
# only consider unhandled events that were sent from this thread,
|
||||
# i.e. triggered during a callback of a previous event
|
||||
def _notifications_hook(self, n):
|
||||
# Only consider unhandled notifications that were sent from this thread,
|
||||
# i.e. triggered by a callback handling a previous notification.
|
||||
if self._active and _threading.current_thread() == self:
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("queueing unhandled event %s", event)
|
||||
self._queued_events.put(event)
|
||||
_log.debug("queueing unhandled notification %s", n)
|
||||
self._queued_notifications.put(n)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._active and self.receiver)
|
||||
|
|
|
@ -247,7 +247,8 @@ class Receiver(object):
|
|||
return self._firmware
|
||||
|
||||
def enable_notifications(self, enable=True):
|
||||
"""Enable or disable device (dis)connection events on this receiver."""
|
||||
"""Enable or disable device (dis)connection notifications on this
|
||||
receiver."""
|
||||
if not self.handle:
|
||||
return False
|
||||
if enable:
|
||||
|
@ -267,7 +268,7 @@ class Receiver(object):
|
|||
"""Scan all devices."""
|
||||
if self.handle:
|
||||
if not self.request(0x8002, 0x02):
|
||||
_log.warn("failed to trigger device events")
|
||||
_log.warn("failed to trigger device link notifications")
|
||||
|
||||
def register_new_device(self, number):
|
||||
if self._devices.get(number) is not None:
|
||||
|
|
|
@ -62,16 +62,16 @@ class ReceiverStatus(dict):
|
|||
# self.updated = _timestamp()
|
||||
self._changed_callback(self._receiver, alert=alert, reason=reason)
|
||||
|
||||
def process_event(self, event):
|
||||
if event.sub_id == 0x4A:
|
||||
self.lock_open = bool(event.address & 0x01)
|
||||
def process_notification(self, n):
|
||||
if n.sub_id == 0x4A:
|
||||
self.lock_open = bool(n.address & 0x01)
|
||||
reason = 'pairing lock is ' + ('open' if self.lock_open else 'closed')
|
||||
_log.info("%s: %s", self._receiver, reason)
|
||||
if self.lock_open:
|
||||
self[ERROR] = None
|
||||
self.new_device = None
|
||||
|
||||
pair_error = ord(event.data[:1])
|
||||
pair_error = ord(n.data[:1])
|
||||
if pair_error:
|
||||
self[ERROR] = _hidpp10.PAIRING_ERRORS[pair_error]
|
||||
self.new_device = None
|
||||
|
@ -98,15 +98,27 @@ class DeviceStatus(dict):
|
|||
self.updated = 0
|
||||
|
||||
def __str__(self):
|
||||
t = []
|
||||
if self.get(BATTERY_LEVEL) is not None:
|
||||
b = 'Battery: %d%%' % self[BATTERY_LEVEL]
|
||||
if self.get(BATTERY_STATUS):
|
||||
b += ' (' + self[BATTERY_STATUS] + ')'
|
||||
t.append(b)
|
||||
if self.get(LIGHT_LEVEL) is not None:
|
||||
t.append('Light: %d lux' % self[LIGHT_LEVEL])
|
||||
return ', '.join(t)
|
||||
def _item(name, format):
|
||||
value = self.get(name)
|
||||
if value is not None:
|
||||
return format % value
|
||||
|
||||
def _items():
|
||||
battery_level = _item(BATTERY_LEVEL, 'Battery: %d%%')
|
||||
if battery_level:
|
||||
yield battery_level
|
||||
battery_status = _item(BATTERY_STATUS, ' <small>(%s)</small>')
|
||||
if battery_status:
|
||||
yield battery_status
|
||||
|
||||
light_level = _item(LIGHT_LEVEL, 'Light: %d lux')
|
||||
if light_level:
|
||||
if battery_level:
|
||||
yield ', '
|
||||
yield light_level
|
||||
|
||||
return ''.join(i for i in _items())
|
||||
|
||||
__unicode__ = __str__
|
||||
|
||||
def __bool__(self):
|
||||
|
@ -156,25 +168,25 @@ class DeviceStatus(dict):
|
|||
self.clear()
|
||||
self._changed(active=False, alert=ALERT.LOW, timestamp=timestamp)
|
||||
|
||||
def process_event(self, event):
|
||||
assert event.sub_id < 0x80
|
||||
def process_notification(self, n):
|
||||
assert n.sub_id < 0x80
|
||||
|
||||
if event.sub_id == 0x40:
|
||||
if event.address == 0x02:
|
||||
if n.sub_id == 0x40:
|
||||
if n.address == 0x02:
|
||||
# device un-paired
|
||||
self.clear()
|
||||
self._device.status = None
|
||||
self._changed(False, ALERT.HIGH, 'unpaired')
|
||||
else:
|
||||
_log.warn("device %d disconnection notification %s with unknown type %02X", self._device.number, event, event.address)
|
||||
_log.warn("device %d disconnection notification %s with unknown type %02X", self._device.number, n, n.address)
|
||||
return True
|
||||
|
||||
if event.sub_id == 0x41:
|
||||
if event.address == 0x04: # unifying protocol
|
||||
# wpid = _strhex(event.data[4:5] + event.data[3:4])
|
||||
if n.sub_id == 0x41:
|
||||
if n.address == 0x04: # unifying protocol
|
||||
# wpid = _strhex(n.data[4:5] + n.data[3:4])
|
||||
# assert wpid == device.wpid
|
||||
|
||||
flags = ord(event.data[:1]) & 0xF0
|
||||
flags = ord(n.data[:1]) & 0xF0
|
||||
link_encrypyed = bool(flags & 0x20)
|
||||
link_established = not (flags & 0x40)
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
|
@ -185,47 +197,43 @@ class DeviceStatus(dict):
|
|||
self[ENCRYPTED] = link_encrypyed
|
||||
self._changed(link_established)
|
||||
|
||||
elif event.address == 0x03:
|
||||
_log.warn("device %d connection notification %s with eQuad protocol, ignored", self._device.number, event)
|
||||
elif n.address == 0x03:
|
||||
_log.warn("device %d connection notification %s with eQuad protocol, ignored", self._device.number, n)
|
||||
|
||||
else:
|
||||
_log.warn("device %d connection notification %s with unknown protocol %02X", self._device.number, event, event.address)
|
||||
_log.warn("device %d connection notification %s with unknown protocol %02X", self._device.number, n, n.address)
|
||||
|
||||
return True
|
||||
|
||||
if event.sub_id == 0x49:
|
||||
if n.sub_id == 0x49:
|
||||
# raw input event? just ignore it
|
||||
# if event.address == 0x01, no idea what it is
|
||||
# if event.address == 0x03, it's an actual input event
|
||||
# if n.address == 0x01, no idea what it is, but they keep on coming
|
||||
# if n.address == 0x03, it's an actual input event
|
||||
return True
|
||||
|
||||
if event.sub_id == 0x4B:
|
||||
if event.address == 0x01:
|
||||
_log.debug("device came online %d", event.devnumber)
|
||||
if n.sub_id == 0x4B:
|
||||
if n.address == 0x01:
|
||||
_log.debug("device came online %d", n.devnumber)
|
||||
self._changed(alert=ALERT.LOW, reason='powered on')
|
||||
else:
|
||||
_log.warn("unknown event %s", event)
|
||||
_log.warn("unknown notification %s", n)
|
||||
return True
|
||||
|
||||
if event.sub_id >= 0x40:
|
||||
_log.warn("don't know how to handle event %s", event)
|
||||
return False
|
||||
|
||||
# this must be a feature event, assuming no device has more than 0x40 features
|
||||
if event.sub_id >= len(self._device.features):
|
||||
_log.warn("device %d got event from unknown feature index %02X", self._device.number, event.sub_id)
|
||||
# this must be a feature notification, assuming no device has more than 0x40 features
|
||||
if n.sub_id >= len(self._device.features):
|
||||
_log.warn("device %s got notification from invalid feature index %02X", self._device, n.sub_id)
|
||||
return False
|
||||
|
||||
try:
|
||||
feature = self._device.features[event.sub_id]
|
||||
feature = self._device.features[n.sub_id]
|
||||
except IndexError:
|
||||
_log.warn("don't know how to handle event %s for feature with invalid index %02X", event, event.sub_id)
|
||||
_log.warn("device %s got notification from invalid feature index %02X", self._device, n.sub_id)
|
||||
return False
|
||||
|
||||
if feature == _hidpp20.FEATURE.BATTERY:
|
||||
if event.address == 0x00:
|
||||
discharge = ord(event.data[:1])
|
||||
battery_status = ord(event.data[1:2])
|
||||
if n.address == 0x00:
|
||||
discharge = ord(n.data[:1])
|
||||
battery_status = ord(n.data[1:2])
|
||||
self[BATTERY_LEVEL] = discharge
|
||||
self[BATTERY_STATUS] = BATTERY_STATUS[battery_status]
|
||||
if _hidpp20.BATTERY_OK(battery_status):
|
||||
|
@ -236,40 +244,40 @@ class DeviceStatus(dict):
|
|||
reason = self[ERROR] = self[BATTERY_STATUS]
|
||||
self._changed(alert=alert, reason=reason)
|
||||
else:
|
||||
_log.warn("don't know how to handle BATTERY event %s", event)
|
||||
_log.warn("don't know how to handle BATTERY notification %s", n)
|
||||
return True
|
||||
|
||||
if feature == _hidpp20.FEATURE.REPROGRAMMABLE_KEYS:
|
||||
if event.address == 0x00:
|
||||
_log.debug('reprogrammable key: %s', event)
|
||||
if n.address == 0x00:
|
||||
_log.warn('unknown reprogrammable key: %s', n)
|
||||
else:
|
||||
_log.warn("don't know how to handle REPROGRAMMABLE KEYS event %s", event)
|
||||
_log.warn("don't know how to handle REPROGRAMMABLE KEYS notification %s", n)
|
||||
return True
|
||||
|
||||
if feature == _hidpp20.FEATURE.WIRELESS:
|
||||
if event.address == 0x00:
|
||||
_log.debug("wireless status: %s", event)
|
||||
if event.data[0:3] == b'\x01\x01\x01':
|
||||
if n.address == 0x00:
|
||||
_log.debug("wireless status: %s", n)
|
||||
if n.data[0:3] == b'\x01\x01\x01':
|
||||
self._changed(alert=ALERT.LOW, reason='powered on')
|
||||
else:
|
||||
_log.warn("don't know how to handle WIRELESS event %s", event)
|
||||
_log.warn("don't know how to handle WIRELESS notification %s", n)
|
||||
return True
|
||||
|
||||
if feature == _hidpp20.FEATURE.SOLAR_CHARGE:
|
||||
if event.data[5:9] == b'GOOD':
|
||||
charge, lux, adc = _unpack(b'!BHH', event.data[:5])
|
||||
if n.data[5:9] == b'GOOD':
|
||||
charge, lux, adc = _unpack(b'!BHH', n.data[:5])
|
||||
self[BATTERY_LEVEL] = charge
|
||||
# guesstimate the battery voltage, emphasis on 'guess'
|
||||
self[BATTERY_STATUS] = '%1.2fV' % (adc * 2.67793237653 / 0x0672)
|
||||
if event.address == 0x00:
|
||||
if n.address == 0x00:
|
||||
self[LIGHT_LEVEL] = None
|
||||
self._changed()
|
||||
elif event.address == 0x10:
|
||||
elif n.address == 0x10:
|
||||
self[LIGHT_LEVEL] = lux
|
||||
if lux > 200: # guesstimate
|
||||
self[BATTERY_STATUS] += ', charging'
|
||||
self._changed()
|
||||
elif event.address == 0x20:
|
||||
elif n.address == 0x20:
|
||||
_log.debug("Solar key pressed")
|
||||
# first cancel any reporting
|
||||
self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE)
|
||||
|
@ -281,17 +289,17 @@ class DeviceStatus(dict):
|
|||
else:
|
||||
self._changed()
|
||||
else:
|
||||
_log.warn("SOLAR CHARGE event not GOOD? %s", event)
|
||||
_log.warn("SOLAR CHARGE notification not GOOD? %s", n)
|
||||
return True
|
||||
|
||||
if feature == _hidpp20.FEATURE.TOUCH_MOUSE:
|
||||
if event.address == 0x00:
|
||||
_log.debug("TOUCH MOUSE points event: %s", event)
|
||||
elif event.address == 0x10:
|
||||
touch = ord(event.data[:1])
|
||||
if n.address == 0x00:
|
||||
_log.debug("TOUCH MOUSE points notification: %s", n)
|
||||
elif n.address == 0x10:
|
||||
touch = ord(n.data[:1])
|
||||
button_down = bool(touch & 0x02)
|
||||
mouse_lifted = bool(touch & 0x01)
|
||||
_log.debug("TOUCH MOUSE status: button_down=%s mouse_lifted=%s", button_down, mouse_lifted)
|
||||
return True
|
||||
|
||||
_log.warn("don't know how to handle event %s for feature %s (%02X)", event, feature, event.sub_id)
|
||||
_log.warn("don't know how to handle %s for feature %s (%02X)", n, feature, n.sub_id)
|
||||
|
|
Loading…
Reference in New Issue