From ab5e09db939864ddfe19cf3d33fbd8b6254770cc Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Thu, 29 Nov 2012 21:26:03 +0200 Subject: [PATCH] pairing fixes --- app/listener.py | 9 +- app/ui/main_window.py | 18 +-- app/ui/pair_window.py | 58 ++++---- lib/logitech/unifying_receiver/status.py | 167 +++++++++++++---------- 4 files changed, 138 insertions(+), 114 deletions(-) diff --git a/app/listener.py b/app/listener.py index 5b2e6cea..6e80a166 100644 --- a/app/listener.py +++ b/app/listener.py @@ -28,7 +28,7 @@ DUMMY = _DUMMY_RECEIVER() _DEVICE_TIMEOUT = 3 * 60 # seconds _DEVICE_STATUS_POLL = 60 # seconds -# def fake_device(listener): +# def _fake_device(listener): # dev = _lur.PairedDevice(listener.receiver, 6) # dev._wpid = '1234' # dev._kind = 'touchpad' @@ -64,7 +64,7 @@ class ReceiverListener(_lur.listener.EventsListener): dev.codename, dev.kind, dev.name # dev.status._changed(dev.protocol > 0) - # fake = fake_device(self) + # fake = _fake_device(self) # self.receiver._devices[fake.number] = fake # self._status_changed(fake, _lur.status.ALERT.LOW) @@ -81,7 +81,7 @@ class ReceiverListener(_lur.listener.EventsListener): def tick(self, timestamp): if _log.isEnabledFor(_DEBUG): - _log.debug("tick: polling status") + _log.debug("tick: polling status: %s %s", self.receiver, self.receiver._devices) # read these in case they haven't been read already self.receiver.serial, self.receiver.firmware @@ -130,8 +130,7 @@ class ReceiverListener(_lur.listener.EventsListener): if dev.status is not None and dev.status.process_event(event): if self.receiver.status.lock_open and not known_device: assert event.sub_id == 0x41 - self.receiver.pairing_result = dev - return + self.receiver.status.new_device = dev else: _log.warn("received event %s for invalid device %d", event, event.devnumber) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9be49bde..8d12a828 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -24,6 +24,7 @@ def _make_receiver_box(name): icon_name = ui.get_icon(name, 'preferences-desktop-peripherals') icon = Gtk.Image.new_from_icon_name(icon_name, _RECEIVER_ICON_SIZE) + icon.set_name('icon') icon.set_padding(2, 2) label = Gtk.Label('Scanning...') @@ -272,20 +273,21 @@ def _toggle_info_box(action, label_widget, box_widget, frame, update_function): def _update_receiver_box(frame, receiver): - label, pairing_icon, toolbar, info_label = ui.find_children(frame, 'label', 'pairing-icon', 'toolbar', 'info-label') + icon, label, pairing_icon, toolbar, info_label = ui.find_children(frame, 'icon', 'label', 'pairing-icon', 'toolbar', 'info-label') - if receiver.status is None: + label.set_text(str(receiver.status)) + if receiver: + frame._device = receiver + icon.set_sensitive(True) + pairing_icon.set_visible(receiver.status.lock_open) + toolbar.set_visible(True) + else: frame._device = None - label.set_text('No receiver found.') + icon.set_sensitive(False) pairing_icon.set_visible(False) toolbar.set_visible(False) toolbar.get_children()[0].set_active(False) info_label.set_text('') - else: - frame._device = receiver - label.set_text(str(receiver.status)) - pairing_icon.set_visible(receiver.status.lock_open) - toolbar.set_visible(True) def _update_device_box(frame, dev): diff --git a/app/ui/pair_window.py b/app/ui/pair_window.py index af62003c..ac225e2e 100644 --- a/app/ui/pair_window.py +++ b/app/ui/pair_window.py @@ -6,9 +6,9 @@ import logging from gi.repository import (Gtk, GObject) import ui +from logitech.unifying_receiver import status as _status - -_PAIRING_TIMEOUT = 30 +_PAIRING_TIMEOUT = 15 def _create_page(assistant, kind, header=None, icon_name=None, text=None): @@ -40,29 +40,31 @@ def _create_page(assistant, kind, header=None, icon_name=None, text=None): return p -def _fake_device(receiver): - from logitech.unifying_receiver import PairedDevice - dev = PairedDevice(receiver, 6) - dev._kind = 'touchpad' - dev._codename = 'T650' - dev._name = 'Wireless Rechargeable Touchpad T650' - dev._serial = '0123456789' - dev._protocol = 2.0 - dev.status = {'encrypted': False} - return dev - +# def _fake_device(receiver): +# from logitech.unifying_receiver import PairedDevice +# dev = PairedDevice(receiver, 6) +# dev._kind = 'touchpad' +# dev._codename = 'T650' +# dev._name = 'Wireless Rechargeable Touchpad T650' +# dev._serial = '0123456789' +# dev._protocol = 2.0 +# dev.status = _status.DeviceStatus(dev, lambda *foo: None) +# return dev def _check_lock_state(assistant, receiver): if not assistant.is_drawable(): return False - if receiver.pairing_result: - receiver.pairing_result = _fake_device(receiver) - if type(receiver.pairing_result) == str: - _pairing_failed(assistant, receiver, receiver.pairing_result) - else: - assert hasattr(receiver.pairing_result, 'number') - _pairing_succeeded(assistant, receiver, receiver.pairing_result) + if receiver.status.get(_status.ERROR): + # fake = _fake_device(receiver) + # receiver._devices[fake.number] = fake + # receiver.status.new_device = fake + # fake.status._changed() + _pairing_failed(assistant, receiver, receiver.status.pop(_status.ERROR)) + return False + + if receiver.status.new_device: + _pairing_succeeded(assistant, receiver) return False return receiver.status.lock_open @@ -73,11 +75,12 @@ def _prepare(assistant, page, receiver): # logging.debug("prepare %s %d %s", assistant, index, page) if index == 0: - receiver.pairing_result = None if receiver.set_lock(False, timeout=_PAIRING_TIMEOUT): + assert receiver.status.new_device is None + assert receiver.status.get(_status.ERROR) is None spinner = page.get_children()[-1] spinner.start() - GObject.timeout_add(200, _check_lock_state, assistant, receiver) + GObject.timeout_add(300, _check_lock_state, assistant, receiver) assistant.set_page_complete(page, True) else: GObject.idle_add(_pairing_failed, assistant, receiver, 'the pairing lock did not open') @@ -88,6 +91,7 @@ def _prepare(assistant, page, receiver): def _finish(assistant, receiver): logging.debug("finish %s", assistant) assistant.destroy() + receiver.status.new_device = None if receiver.status.lock_open: receiver.set_lock() @@ -95,9 +99,8 @@ def _finish(assistant, receiver): def _cancel(assistant, receiver): logging.debug("cancel %s", assistant) assistant.destroy() - device, receiver.pairing_result = receiver.pairing_result, None + device, receiver.status.new_device = receiver.status.new_device, None if device: - assert type(device) != str try: del receiver[device.number] except: @@ -107,11 +110,10 @@ def _cancel(assistant, receiver): def _pairing_failed(assistant, receiver, error): - receiver.pairing_result = None assistant.commit() header = 'Pairing failed: %s.' % error - if 'timeout' in error: + if 'timeout' in str(error): text = 'Make sure your device is within range,\nand it has a decent battery charge.' else: text = None @@ -121,7 +123,9 @@ def _pairing_failed(assistant, receiver, error): assistant.commit() -def _pairing_succeeded(assistant, receiver, device): +def _pairing_succeeded(assistant, receiver): + device = receiver.status.new_device + assert device page = _create_page(assistant, Gtk.AssistantPageType.CONFIRM) device_icon = Gtk.Image() diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index 26979a9d..f09d25bd 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -41,6 +41,7 @@ class ReceiverStatus(dict): # self.updated = 0 self.lock_open = False + self.new_device = None self[ERROR] = None def __str__(self): @@ -58,13 +59,20 @@ class ReceiverStatus(dict): self.lock_open = bool(event.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]) if pair_error: self[ERROR] = _hidpp10.PAIRING_ERRORS[pair_error] + self.new_device = None _log.warn("pairing error %d: %s", pair_error, self[ERROR]) else: self[ERROR] = None + self._changed(reason=reason) + return True # # @@ -128,8 +136,9 @@ class DeviceStatus(dict): self._device = None else: _log.warn("device %d disconnection notification %s with unknown type %02X", self._device.number, event, event.address) + return True - elif event.sub_id == 0x41: + if event.sub_id == 0x41: if event.address == 0x04: # unifying protocol # wpid = _strhex(event.data[4:5] + event.data[3:4]) # assert wpid == device.wpid @@ -151,81 +160,91 @@ class DeviceStatus(dict): else: _log.warn("device %d connection notification %s with unknown protocol %02X", self._device.number, event, event.address) - elif event.sub_id < 0x40: - # 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) - return None + return True - feature = self._device.features[event.sub_id] + if event.sub_id >= 0x40: + # this can't possibly be an event, can it? + if _log.isEnabledFor(_DEBUG): + _log.debug("ignoring non-event %s", event) + return False - if feature == _hidpp20.FEATURE.BATTERY: - if event.address == 0x00: - discharge = ord(event.data[:1]) - battery_status = ord(event.data[1:2]) - self[BATTERY_LEVEL] = discharge - self[BATTERY_STATUS] = BATTERY_STATUS[battery_status] - if _hidpp20.BATTERY_OK(battery_status): - alert = ALERT.NONE - reason = self[ERROR] = None - else: - alert = ALERT.MED - reason = self[ERROR] = self[BATTERY_STATUS] - self._changed(alert=alert, reason=reason) + # 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) + return False + + feature = self._device.features[event.sub_id] + + if feature == _hidpp20.FEATURE.BATTERY: + if event.address == 0x00: + discharge = ord(event.data[:1]) + battery_status = ord(event.data[1:2]) + self[BATTERY_LEVEL] = discharge + self[BATTERY_STATUS] = BATTERY_STATUS[battery_status] + if _hidpp20.BATTERY_OK(battery_status): + alert = ALERT.NONE + reason = self[ERROR] = None else: - _log.warn("don't know how to handle BATTERY event %s", event) - - elif feature == _hidpp20.FEATURE.REPROGRAMMABLE_KEYS: - if event.address == 0x00: - _log.debug('reprogrammable key: %s', event) - else: - _log.warn("don't know how to handle REPROGRAMMABLE KEYS event %s", event) - - elif feature == _hidpp20.FEATURE.WIRELESS: - if event.address == 0x00: - _log.debug("wireless status: %s", event) - if event.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) - - elif feature == _hidpp20.FEATURE.SOLAR_CHARGE: - if event.data[5:9] == b'GOOD': - charge, lux, adc = _unpack('!BHH', event.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: - self[LIGHT_LEVEL] = None - self._changed() - elif event.address == 0x10: - self[LIGHT_LEVEL] = lux - if lux > 200: # guesstimate - self[BATTERY_STATUS] += ', charging' - self._changed() - elif event.address == 0x20: - _log.debug("Solar key pressed") - # first cancel any reporting - self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE) - reports_count = 10 - reports_period = 3 # seconds - self._changed(alert=ALERT.MED) - # trigger a new report chain - self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE, 0x00, reports_count, reports_period) - else: - self._changed() - else: - _log.warn("SOLAR_CHARGE event not GOOD? %s", event) - - elif 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]) - 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) - + alert = ALERT.MED + reason = self[ERROR] = self[BATTERY_STATUS] + self._changed(alert=alert, reason=reason) else: - _log.warn("don't know how to handle event %s", event) + _log.warn("don't know how to handle BATTERY event %s", event) + return True + if feature == _hidpp20.FEATURE.REPROGRAMMABLE_KEYS: + if event.address == 0x00: + _log.debug('reprogrammable key: %s', event) + else: + _log.warn("don't know how to handle REPROGRAMMABLE KEYS event %s", event) + 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': + self._changed(alert=ALERT.LOW, reason='powered on') + else: + _log.warn("don't know how to handle WIRELESS event %s", event) + return True + + if feature == _hidpp20.FEATURE.SOLAR_CHARGE: + if event.data[5:9] == b'GOOD': + charge, lux, adc = _unpack('!BHH', event.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: + self[LIGHT_LEVEL] = None + self._changed() + elif event.address == 0x10: + self[LIGHT_LEVEL] = lux + if lux > 200: # guesstimate + self[BATTERY_STATUS] += ', charging' + self._changed() + elif event.address == 0x20: + _log.debug("Solar key pressed") + # first cancel any reporting + self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE) + reports_count = 10 + reports_period = 3 # seconds + self._changed(alert=ALERT.MED) + # trigger a new report chain + self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE, 0x00, reports_count, reports_period) + else: + self._changed() + else: + _log.warn("SOLAR_CHARGE event not GOOD? %s", event) + 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]) + 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", event)