one a separate flag to check if the device is active
This commit is contained in:
		
							parent
							
								
									c04851f64e
								
							
						
					
					
						commit
						fd35f23af7
					
				|  | @ -105,8 +105,8 @@ def get_register(device, name, default_number=-1): | ||||||
| 		if reply: | 		if reply: | ||||||
| 			return reply | 			return reply | ||||||
| 
 | 
 | ||||||
| 		if not known_register and device.ping(): | 		if not known_register and device.kind is not None and device.online: | ||||||
| 			_log.warn("%s: failed to read '%s' from default register 0x%02X, blacklisting", | 			_log.warn("%s: failed to read register '%s' (0x%02X), blacklisting", | ||||||
| 							device, name, default_number) | 							device, name, default_number) | ||||||
| 			device.registers[name] = -default_number | 			device.registers[name] = -default_number | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -147,7 +147,7 @@ class FeaturesArray(object): | ||||||
| 			if self.features is not None: | 			if self.features is not None: | ||||||
| 				return True | 				return True | ||||||
| 
 | 
 | ||||||
| 			if hasattr(self.device, 'status') and not bool(self.device.status): | 			if not self.device.online: | ||||||
| 				# device is not connected right now, will have to try later | 				# device is not connected right now, will have to try later | ||||||
| 				return False | 				return False | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,11 +26,12 @@ MAX_PAIRED_DEVICES = 6 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PairedDevice(object): | class PairedDevice(object): | ||||||
| 	def __init__(self, receiver, number): | 	def __init__(self, receiver, number, link_notification=None): | ||||||
| 		assert receiver | 		assert receiver | ||||||
| 		self.receiver = _proxy(receiver) | 		self.receiver = _proxy(receiver) | ||||||
| 		assert number > 0 and number <= receiver.max_devices | 		assert number > 0 and number <= receiver.max_devices | ||||||
| 		self.number = number | 		self.number = number | ||||||
|  | 		self.online = None | ||||||
| 
 | 
 | ||||||
| 		self.wpid = None | 		self.wpid = None | ||||||
| 		self.polling_rate = 0 | 		self.polling_rate = 0 | ||||||
|  | @ -38,14 +39,14 @@ class PairedDevice(object): | ||||||
| 		self._kind = None | 		self._kind = None | ||||||
| 		self._codename = None | 		self._codename = None | ||||||
| 		self._name = None | 		self._name = None | ||||||
|  | 		self._protocol = None | ||||||
|  | 		self._serial = None | ||||||
| 
 | 
 | ||||||
| 		unifying = self.receiver.unifying_supported | 		unifying = self.receiver.unifying_supported | ||||||
|  | 		if link_notification is None: | ||||||
| 			if unifying: | 			if unifying: | ||||||
| 				# force a reading of the codename | 				# force a reading of the codename | ||||||
| 			if self.codename is None: | 				pair_info = receiver.read_register(0x2B5, 0x20 + number - 1) | ||||||
| 				raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read codename") |  | ||||||
| 
 |  | ||||||
| 			pair_info = receiver.request(0x83B5, 0x20 + number - 1) |  | ||||||
| 				if pair_info is None: | 				if pair_info is None: | ||||||
| 					raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read pair info") | 					raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read pair info") | ||||||
| 
 | 
 | ||||||
|  | @ -54,7 +55,7 @@ class PairedDevice(object): | ||||||
| 				self._kind = _hidpp10.DEVICE_KIND[kind] | 				self._kind = _hidpp10.DEVICE_KIND[kind] | ||||||
| 				self.polling_rate = ord(pair_info[2:3]) | 				self.polling_rate = ord(pair_info[2:3]) | ||||||
| 			else: | 			else: | ||||||
| 			# guesswork... | 				# guesswork... look for the product id in the descriptors | ||||||
| 				descriptor = _descriptors.DEVICES.get(self.receiver.product_id) | 				descriptor = _descriptors.DEVICES.get(self.receiver.product_id) | ||||||
| 				if descriptor is None: | 				if descriptor is None: | ||||||
| 					self._codename = self.receiver.product_id | 					self._codename = self.receiver.product_id | ||||||
|  | @ -65,29 +66,34 @@ class PairedDevice(object): | ||||||
| 					self._codename = descriptor.codename | 					self._codename = descriptor.codename | ||||||
| 					self._name = descriptor.name | 					self._name = descriptor.name | ||||||
| 
 | 
 | ||||||
| 			device_info = self.receiver.request(0x83B5, 0x04) | 				device_info = self.receiver.read_register(0x2B5, 0x04) | ||||||
| 			assert device_info, "failed to read Nano device info" | 				if device_info is None: | ||||||
|  | 					raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read Nano wpid") | ||||||
| 				self.wpid = _strhex(device_info[3:5]) | 				self.wpid = _strhex(device_info[3:5]) | ||||||
| 				# self._kind = descriptor.kind | 				# self._kind = descriptor.kind | ||||||
| 			self.polling_rate = 0 | 				self.serial = self.receiver.serial | ||||||
|  | 		else: | ||||||
|  | 			self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2]) | ||||||
|  | 			assert link_notification.address == (0x04 if unifying else 0x03) | ||||||
|  | 			kind = ord(link_notification.data[1:2]) & 0x0F | ||||||
|  | 			self._kind = _hidpp10.DEVICE_KIND[kind] | ||||||
|  | 			self.online = bool(ord(link_notification.data[0:1]) & 0x40) | ||||||
| 
 | 
 | ||||||
| 		# the wpid is necessary to properly identify wireless link on/off notifications | 		# the wpid is necessary to properly identify wireless link on/off notifications | ||||||
|  | 		# also it gets set to None when the device is unpaired | ||||||
| 		assert self.wpid is not None, "failed to read wpid: device %d of %s" % (number, receiver) | 		assert self.wpid is not None, "failed to read wpid: device %d of %s" % (number, receiver) | ||||||
| 		# the codename is necessary to guess all other info |  | ||||||
| 		assert self._codename is not None, "failed to read codename: device %d of %s" % (number, receiver) |  | ||||||
| 
 | 
 | ||||||
| 		# knowing the protocol as soon as possible helps reading all other info | 		# knowing the protocol as soon as possible helps reading all other info | ||||||
| 		# and avoids an unecessary ping | 		# and avoids an unecessary ping | ||||||
| 		descriptor = _descriptors.DEVICES.get(self.codename) | 		if self._codename is not None: | ||||||
|  | 			descriptor = _descriptors.DEVICES.get(self._codename) | ||||||
| 			if descriptor is None: | 			if descriptor is None: | ||||||
| 			_log.warn("device without descriptor found: %s (%d of %s)", self.codename, number, receiver) | 				_log.warn("device without descriptor found: %s (%d of %s)", self._codename, number, receiver) | ||||||
| 				self._protocol = None if unifying else 1.0 | 				self._protocol = None if unifying else 1.0 | ||||||
| 			else: | 			else: | ||||||
| 				self._protocol = descriptor.protocol if unifying else 1.0  # may be None | 				self._protocol = descriptor.protocol if unifying else 1.0  # may be None | ||||||
| 
 | 
 | ||||||
| 		self._power_switch = None if unifying else '(unknown)' | 		self._power_switch = None if unifying else '(unknown)' | ||||||
| 		self.serial = _hidpp10.get_serial(self) if unifying else self.receiver.serial |  | ||||||
| 
 |  | ||||||
| 		self._firmware = None | 		self._firmware = None | ||||||
| 		self._keys = None | 		self._keys = None | ||||||
| 
 | 
 | ||||||
|  | @ -106,10 +112,22 @@ class PairedDevice(object): | ||||||
| 	def protocol(self): | 	def protocol(self): | ||||||
| 		if self._protocol is None: | 		if self._protocol is None: | ||||||
| 			self._protocol = _base.ping(self.receiver.handle, self.number) | 			self._protocol = _base.ping(self.receiver.handle, self.number) | ||||||
| 			if self._protocol is None: | 			# if the ping failed, the peripheral is (almost) certainly offline | ||||||
|  | 			self.online = self._protocol is not None | ||||||
|  | 
 | ||||||
|  | 			# use the descriptor only as a fallback, because it may not be 100% correct | ||||||
| 			descriptor = _descriptors.DEVICES.get(self.codename) | 			descriptor = _descriptors.DEVICES.get(self.codename) | ||||||
|  | 			if self._protocol is None: | ||||||
| 				if descriptor and descriptor.protocol is not None: | 				if descriptor and descriptor.protocol is not None: | ||||||
| 					self._protocol = descriptor.protocol | 					self._protocol = descriptor.protocol | ||||||
|  | 			else: | ||||||
|  | 				if descriptor: | ||||||
|  | 					if descriptor.protocol is None: | ||||||
|  | 						_log.info("%s: descriptor has no protocol, should be %0.1f", self, self._protocol) | ||||||
|  | 					elif descriptor.protocol != self._protocol: | ||||||
|  | 						_log.error("%s: descriptor has wrong protocol %0.1f, should be %0.1f", | ||||||
|  | 									self, descriptor.protocol, self._protocol) | ||||||
|  | 
 | ||||||
| 			# _log.debug("device %d protocol %s", self.number, self._protocol) | 			# _log.debug("device %d protocol %s", self.number, self._protocol) | ||||||
| 		return self._protocol or 0 | 		return self._protocol or 0 | ||||||
| 
 | 
 | ||||||
|  | @ -131,12 +149,12 @@ class PairedDevice(object): | ||||||
| 				if codename: | 				if codename: | ||||||
| 					self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') | 					self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') | ||||||
| 					# _log.debug("device %d codename %s", self.number, self._codename) | 					# _log.debug("device %d codename %s", self.number, self._codename) | ||||||
| 		return self._codename | 		return self._codename or '?' | ||||||
| 
 | 
 | ||||||
| 	@property | 	@property | ||||||
| 	def name(self): | 	def name(self): | ||||||
| 		if self._name is None: | 		if self._name is None: | ||||||
| 			if self.protocol >= 2.0: | 			if self.protocol >= 2.0 and self.online: | ||||||
| 				self._name = _hidpp20.get_name(self) | 				self._name = _hidpp20.get_name(self) | ||||||
| 			if self._name is None: | 			if self._name is None: | ||||||
| 				descriptor = _descriptors.DEVICES.get(self.codename) | 				descriptor = _descriptors.DEVICES.get(self.codename) | ||||||
|  | @ -155,7 +173,7 @@ class PairedDevice(object): | ||||||
| 			# 		self._kind = _hidpp10.DEVICE_KIND[kind] | 			# 		self._kind = _hidpp10.DEVICE_KIND[kind] | ||||||
| 			# 		if self.wpid is None: | 			# 		if self.wpid is None: | ||||||
| 			# 			self.wpid = _strhex(pair_info[3:5]) | 			# 			self.wpid = _strhex(pair_info[3:5]) | ||||||
| 			if self.protocol >= 2.0: | 			if self.protocol >= 2.0 and self.online: | ||||||
| 				self._kind = _hidpp20.get_kind(self) | 				self._kind = _hidpp20.get_kind(self) | ||||||
| 			if self._kind is None: | 			if self._kind is None: | ||||||
| 				descriptor = _descriptors.DEVICES.get(self.codename) | 				descriptor = _descriptors.DEVICES.get(self.codename) | ||||||
|  | @ -165,26 +183,25 @@ class PairedDevice(object): | ||||||
| 
 | 
 | ||||||
| 	@property | 	@property | ||||||
| 	def firmware(self): | 	def firmware(self): | ||||||
| 		if self._firmware is None: | 		if self._firmware is None and self.online: | ||||||
| 			if self.protocol < 2.0: | 			if self.protocol < 2.0: | ||||||
| 				self._firmware = _hidpp10.get_firmware(self) | 				self._firmware = _hidpp10.get_firmware(self) | ||||||
| 			else: | 			else: | ||||||
| 				self._firmware = _hidpp20.get_firmware(self) | 				self._firmware = _hidpp20.get_firmware(self) | ||||||
| 		return self._firmware or () | 		return self._firmware or () | ||||||
| 
 | 
 | ||||||
| 	# @property | 	@property | ||||||
| 	# def serial(self): | 	def serial(self): | ||||||
| 	# 	if self._serial is None: | 		if self._serial is None: | ||||||
| 	# 		if self.receiver.unifying_supported: | 			assert self.receiver.unifying_supported | ||||||
| 	# 			self._serial = _hidpp10.get_serial(self) | 			# otherwise it should have been set in the constructor | ||||||
| 	# 		else: | 			self._serial = _hidpp10.get_serial(self) | ||||||
| 	# 			self._serial = self.receiver.serial | 		return self._serial or '?' | ||||||
| 	# 	return self._serial or '?' |  | ||||||
| 
 | 
 | ||||||
| 	@property | 	@property | ||||||
| 	def keys(self): | 	def keys(self): | ||||||
| 		if self._keys is None: | 		if self._keys is None: | ||||||
| 			if self.protocol >= 2.0: | 			if self.protocol >= 2.0 and self.online: | ||||||
| 				self._keys = _hidpp20.get_keys(self) or () | 				self._keys = _hidpp20.get_keys(self) or () | ||||||
| 		return self._keys | 		return self._keys | ||||||
| 
 | 
 | ||||||
|  | @ -207,14 +224,14 @@ class PairedDevice(object): | ||||||
| 			else: | 			else: | ||||||
| 				self._settings = [s(self) for s in descriptor.settings] | 				self._settings = [s(self) for s in descriptor.settings] | ||||||
| 
 | 
 | ||||||
| 		if self.features: | 		if self.online and self.features: | ||||||
| 			_descriptors.check_features(self, self._settings) | 			_descriptors.check_features(self, self._settings) | ||||||
| 		return self._settings | 		return self._settings | ||||||
| 
 | 
 | ||||||
| 	def enable_notifications(self, enable=True): | 	def enable_notifications(self, enable=True): | ||||||
| 		"""Enable or disable device (dis)connection notifications on this | 		"""Enable or disable device (dis)connection notifications on this | ||||||
| 		receiver.""" | 		receiver.""" | ||||||
| 		if not self.receiver or not self.receiver.handle or self.protocol >= 2.0: | 		if not bool(self.receiver) or self.protocol >= 2.0: | ||||||
| 			return False | 			return False | ||||||
| 
 | 
 | ||||||
| 		if enable: | 		if enable: | ||||||
|  | @ -245,7 +262,10 @@ class PairedDevice(object): | ||||||
| 			return _hidpp20.feature_request(self, feature, function, *params) | 			return _hidpp20.feature_request(self, feature, function, *params) | ||||||
| 
 | 
 | ||||||
| 	def ping(self): | 	def ping(self): | ||||||
| 		return _base.ping(self.receiver.handle, self.number) is not None | 		"""Checks if the device is online, returns True of False""" | ||||||
|  | 		protocol = _base.ping(self.receiver.handle, self.number) | ||||||
|  | 		self.online = protocol is not None | ||||||
|  | 		return self.online | ||||||
| 
 | 
 | ||||||
| 	def __index__(self): | 	def __index__(self): | ||||||
| 		return self.number | 		return self.number | ||||||
|  | @ -260,6 +280,8 @@ class PairedDevice(object): | ||||||
| 	def __hash__(self): | 	def __hash__(self): | ||||||
| 		return self.serial.__hash__() | 		return self.serial.__hash__() | ||||||
| 
 | 
 | ||||||
|  | 	__bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver | ||||||
|  | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return '<PairedDevice(%d,%s,%s)>' % (self.number, self.wpid, self.codename or '?') | 		return '<PairedDevice(%d,%s,%s)>' % (self.number, self.wpid, self.codename or '?') | ||||||
| 	__unicode__ = __repr__ = __str__ | 	__unicode__ = __repr__ = __str__ | ||||||
|  | @ -349,12 +371,15 @@ class Receiver(object): | ||||||
| 			if not self.write_register(0x02, 0x02): | 			if not self.write_register(0x02, 0x02): | ||||||
| 				_log.warn("%s: failed to trigger device link notifications", self) | 				_log.warn("%s: failed to trigger device link notifications", self) | ||||||
| 
 | 
 | ||||||
| 	def register_new_device(self, number): | 	def register_new_device(self, number, notification=None): | ||||||
| 		if self._devices.get(number) is not None: | 		if self._devices.get(number) is not None: | ||||||
| 			raise IndexError("%s: device number %d already registered" % (self, number)) | 			raise IndexError("%s: device number %d already registered" % (self, number)) | ||||||
| 
 | 
 | ||||||
|  | 		assert notification is None or notification.devnumber == number | ||||||
|  | 		assert notification is None or notification.sub_id == 0x41 | ||||||
|  | 
 | ||||||
| 		try: | 		try: | ||||||
| 			dev = PairedDevice(self, number) | 			dev = PairedDevice(self, number, notification) | ||||||
| 			assert dev.wpid | 			assert dev.wpid | ||||||
| 			_log.info("%s: found new device %d (%s)", self, number, dev.wpid) | 			_log.info("%s: found new device %d (%s)", self, number, dev.wpid) | ||||||
| 			self._devices[number] = dev | 			self._devices[number] = dev | ||||||
|  | @ -420,6 +445,7 @@ class Receiver(object): | ||||||
| 		if reply: | 		if reply: | ||||||
| 			# invalidate the device | 			# invalidate the device | ||||||
| 			dev.wpid = None | 			dev.wpid = None | ||||||
|  | 			dev.online = False | ||||||
| 			del self._devices[key] | 			del self._devices[key] | ||||||
| 			_log.warn("%s unpaired device %s", self, dev) | 			_log.warn("%s unpaired device %s", self, dev) | ||||||
| 		else: | 		else: | ||||||
|  |  | ||||||
|  | @ -190,8 +190,10 @@ class DeviceStatus(dict): | ||||||
| 			self._changed(alert=alert, reason=reason, timestamp=timestamp) | 			self._changed(alert=alert, reason=reason, timestamp=timestamp) | ||||||
| 
 | 
 | ||||||
| 	def read_battery(self, timestamp=None): | 	def read_battery(self, timestamp=None): | ||||||
|  | 		if self._active: | ||||||
| 			d = self._device | 			d = self._device | ||||||
| 		if d and self._active: | 			assert d | ||||||
|  | 
 | ||||||
| 			if d.protocol < 2.0: | 			if d.protocol < 2.0: | ||||||
| 				battery = _hidpp10.get_battery(d) | 				battery = _hidpp10.get_battery(d) | ||||||
| 			else: | 			else: | ||||||
|  | @ -213,10 +215,13 @@ class DeviceStatus(dict): | ||||||
| 				self[KEYS.BATTERY_CHARGING] = None | 				self[KEYS.BATTERY_CHARGING] = None | ||||||
| 				self._changed() | 				self._changed() | ||||||
| 
 | 
 | ||||||
| 	def _changed(self, active=True, alert=ALERT.NONE, reason=None, timestamp=None): | 	def _changed(self, active=None, alert=ALERT.NONE, reason=None, timestamp=None): | ||||||
| 		assert self._changed_callback | 		assert self._changed_callback | ||||||
| 		assert self._device |  | ||||||
| 		d = self._device | 		d = self._device | ||||||
|  | 		assert d | ||||||
|  | 
 | ||||||
|  | 		if active is not None: | ||||||
|  | 			d.online = active | ||||||
| 			was_active, self._active = self._active, active | 			was_active, self._active = self._active, active | ||||||
| 			if active: | 			if active: | ||||||
| 				if not was_active: | 				if not was_active: | ||||||
|  | @ -235,7 +240,7 @@ class DeviceStatus(dict): | ||||||
| 					if battery is not None: | 					if battery is not None: | ||||||
| 						self[KEYS.BATTERY_LEVEL] = battery | 						self[KEYS.BATTERY_LEVEL] = battery | ||||||
| 
 | 
 | ||||||
| 		if self.updated == 0 and active: | 		if self.updated == 0 and active == True: | ||||||
| 			# if the device is active on the very first status notification, | 			# if the device is active on the very first status notification, | ||||||
| 			# (meaning just when the program started or a new receiver was just | 			# (meaning just when the program started or a new receiver was just | ||||||
| 			# detected), pop-up a notification about it | 			# detected), pop-up a notification about it | ||||||
|  | @ -330,8 +335,10 @@ class DeviceStatus(dict): | ||||||
| 			if n.address == 0x02: | 			if n.address == 0x02: | ||||||
| 				# device un-paired | 				# device un-paired | ||||||
| 				self.clear() | 				self.clear() | ||||||
| 				self._device.status = None | 				dev = self._device | ||||||
| 				self._changed(False, ALERT.ALL, 'unpaired') | 				dev.wpid = None | ||||||
|  | 				dev.status = None | ||||||
|  | 				self._changed(active=False, alert=ALERT.ALL, reason='unpaired') | ||||||
| 			else: | 			else: | ||||||
| 				_log.warn("%s: disconnection with unknown type %02X: %s", self._device, n.address, n) | 				_log.warn("%s: disconnection with unknown type %02X: %s", self._device, n.address, n) | ||||||
| 			return True | 			return True | ||||||
|  | @ -342,6 +349,10 @@ class DeviceStatus(dict): | ||||||
| 						else 'eQuad' if n.address == 0x03 | 						else 'eQuad' if n.address == 0x03 | ||||||
| 						else None) | 						else None) | ||||||
| 			if protocol_name: | 			if protocol_name: | ||||||
|  | 				if _log.isEnabledFor(_DEBUG): | ||||||
|  | 					wpid = _strhex(n.data[2:3] + n.data[1:2]) | ||||||
|  | 					assert wpid == self._device.wpid, "%s wpid mismatch, got %s" % (self._device, wpid) | ||||||
|  | 
 | ||||||
| 				flags = ord(n.data[:1]) & 0xF0 | 				flags = ord(n.data[:1]) & 0xF0 | ||||||
| 				link_encrypyed = bool(flags & 0x20) | 				link_encrypyed = bool(flags & 0x20) | ||||||
| 				link_established = not (flags & 0x40) | 				link_established = not (flags & 0x40) | ||||||
|  | @ -351,20 +362,20 @@ class DeviceStatus(dict): | ||||||
| 					_log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", | 					_log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", | ||||||
| 								self._device, protocol_name, sw_present, link_encrypyed, link_established, has_payload) | 								self._device, protocol_name, sw_present, link_encrypyed, link_established, has_payload) | ||||||
| 				self[KEYS.LINK_ENCRYPTED] = link_encrypyed | 				self[KEYS.LINK_ENCRYPTED] = link_encrypyed | ||||||
| 				self._changed(link_established) | 				self._changed(active=link_established) | ||||||
| 
 | 
 | ||||||
| 				if protocol_name == 'eQuad': | 				# if protocol_name == 'eQuad': | ||||||
| 					# some Nano devices might not have been initialized fully | 				# 	# some Nano devices might not have been initialized fully | ||||||
| 					if self._device._kind is None: | 				# 	if self._device._kind is None: | ||||||
| 						kind = ord(n.data[:1]) & 0x0F | 				# 		kind = ord(n.data[:1]) & 0x0F | ||||||
| 						self._device._kind = _hidpp10.DEVICE_KIND[kind] | 				# 		self._device._kind = _hidpp10.DEVICE_KIND[kind] | ||||||
| 					assert self._device.wpid == _strhex(n.data[2:3] + n.data[1:2]) | 				# 	assert self._device.wpid == _strhex(n.data[2:3] + n.data[1:2]) | ||||||
| 			else: |  | ||||||
| 				_log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) |  | ||||||
| 
 | 
 | ||||||
| 				# if the device just came online, read the battery charge | 				# if the device just came online, read the battery charge | ||||||
| 				if self._active and KEYS.BATTERY_LEVEL not in self: | 				if self._active and KEYS.BATTERY_LEVEL not in self: | ||||||
| 					self.read_battery() | 					self.read_battery() | ||||||
|  | 			else: | ||||||
|  | 				_log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) | ||||||
| 
 | 
 | ||||||
| 			return True | 			return True | ||||||
| 
 | 
 | ||||||
|  | @ -380,7 +391,7 @@ class DeviceStatus(dict): | ||||||
| 				if _log.isEnabledFor(_DEBUG): | 				if _log.isEnabledFor(_DEBUG): | ||||||
| 					_log.debug("%s: device powered on", self._device) | 					_log.debug("%s: device powered on", self._device) | ||||||
| 				reason = str(self) or 'powered on' | 				reason = str(self) or 'powered on' | ||||||
| 				self._changed(alert=ALERT.NOTIFICATION, reason=reason) | 				self._changed(active=True, alert=ALERT.NOTIFICATION, reason=reason) | ||||||
| 			else: | 			else: | ||||||
| 				_log.info("%s: unknown %s", self._device, n) | 				_log.info("%s: unknown %s", self._device, n) | ||||||
| 			return True | 			return True | ||||||
|  | @ -410,7 +421,7 @@ class DeviceStatus(dict): | ||||||
| 				if _log.isEnabledFor(_DEBUG): | 				if _log.isEnabledFor(_DEBUG): | ||||||
| 					_log.debug("wireless status: %s", n) | 					_log.debug("wireless status: %s", n) | ||||||
| 				if n.data[0:3] == b'\x01\x01\x01': | 				if n.data[0:3] == b'\x01\x01\x01': | ||||||
| 					self._changed(alert=ALERT.NOTIFICATION, reason='powered on') | 					self._changed(active=True, alert=ALERT.NOTIFICATION, reason='powered on') | ||||||
| 				else: | 				else: | ||||||
| 					_log.info("%s: unknown WIRELESS %s", self._device, n) | 					_log.info("%s: unknown WIRELESS %s", self._device, n) | ||||||
| 			else: | 			else: | ||||||
|  | @ -426,11 +437,11 @@ class DeviceStatus(dict): | ||||||
| 				if n.address == 0x00: | 				if n.address == 0x00: | ||||||
| 					self[KEYS.LIGHT_LEVEL] = None | 					self[KEYS.LIGHT_LEVEL] = None | ||||||
| 					self[KEYS.BATTERY_CHARGING] = None | 					self[KEYS.BATTERY_CHARGING] = None | ||||||
| 					self._changed() | 					self._changed(active=True) | ||||||
| 				elif n.address == 0x10: | 				elif n.address == 0x10: | ||||||
| 					self[KEYS.LIGHT_LEVEL] = lux | 					self[KEYS.LIGHT_LEVEL] = lux | ||||||
| 					self[KEYS.BATTERY_CHARGING] = lux > 200 | 					self[KEYS.BATTERY_CHARGING] = lux > 200 | ||||||
| 					self._changed() | 					self._changed(active=True) | ||||||
| 				elif n.address == 0x20: | 				elif n.address == 0x20: | ||||||
| 					_log.debug("%s: Light Check button pressed", self._device) | 					_log.debug("%s: Light Check button pressed", self._device) | ||||||
| 					self._changed(alert=ALERT.SHOW_WINDOW) | 					self._changed(alert=ALERT.SHOW_WINDOW) | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ from logitech.unifying_receiver import (Receiver, | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
| _GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'serial', 'status']) | _GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'serial', 'status', 'online']) | ||||||
| _GHOST_DEVICE.__bool__ = lambda self: False | _GHOST_DEVICE.__bool__ = lambda self: False | ||||||
| _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ | _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ | ||||||
| del namedtuple | del namedtuple | ||||||
|  | @ -30,7 +30,8 @@ def _ghost(device): | ||||||
| 					name=device.name, | 					name=device.name, | ||||||
| 					kind=device.kind, | 					kind=device.kind, | ||||||
| 					serial=device.serial, | 					serial=device.serial, | ||||||
| 					status=None) | 					status=None, | ||||||
|  | 					online=False) | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
| # | # | ||||||
|  | @ -109,8 +110,7 @@ class ReceiverListener(_listener.EventsListener): | ||||||
| 			for number in range(1, 6): | 			for number in range(1, 6): | ||||||
| 				if number in self.receiver: | 				if number in self.receiver: | ||||||
| 					dev = self.receiver[number] | 					dev = self.receiver[number] | ||||||
| 					assert dev | 					if dev and dev.status is not None: | ||||||
| 					if dev.status is not None: |  | ||||||
| 						dev.status.poll(timestamp) | 						dev.status.poll(timestamp) | ||||||
| 		except Exception as e: | 		except Exception as e: | ||||||
| 			_log.exception("polling", e) | 			_log.exception("polling", e) | ||||||
|  | @ -118,8 +118,14 @@ class ReceiverListener(_listener.EventsListener): | ||||||
| 	def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): | 	def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): | ||||||
| 		assert device is not None | 		assert device is not None | ||||||
| 		if _log.isEnabledFor(_DEBUG): | 		if _log.isEnabledFor(_DEBUG): | ||||||
| 			_log.debug("%s: status_changed %s: %s, %s (%X) %s", self.receiver, device, | 			if device.kind is None: | ||||||
| 						'active' if device.status else 'inactive', | 				_log.debug("status_changed %s: %s, %s (%X) %s", device, | ||||||
|  | 							'present' if bool(device) else 'removed', | ||||||
|  | 							device.status, alert, reason or '') | ||||||
|  | 			else: | ||||||
|  | 				_log.debug("status_changed %s: %s %s, %s (%X) %s", device, | ||||||
|  | 							'paired' if bool(device) else 'unpaired', | ||||||
|  | 							'online' if device.online else 'offline', | ||||||
| 							device.status, alert, reason or '') | 							device.status, alert, reason or '') | ||||||
| 
 | 
 | ||||||
| 		if device.kind is None: | 		if device.kind is None: | ||||||
|  | @ -129,16 +135,15 @@ class ReceiverListener(_listener.EventsListener): | ||||||
| 			return | 			return | ||||||
| 
 | 
 | ||||||
| 		assert device.receiver == self.receiver | 		assert device.receiver == self.receiver | ||||||
| 
 | 		if not device: | ||||||
| 		if device.status is None: |  | ||||||
| 			# device was unpaired, and since the object is weakref'ed | 			# device was unpaired, and since the object is weakref'ed | ||||||
| 			# it won't be valid for much longer | 			# it won't be valid for much longer | ||||||
| 			_log.info("device %s was unpaired, ghosting", device) | 			_log.warn("device %s was unpaired, ghosting", device) | ||||||
| 			device = _ghost(device) | 			device = _ghost(device) | ||||||
| 
 | 
 | ||||||
| 		self.status_changed_callback(device, alert, reason) | 		self.status_changed_callback(device, alert, reason) | ||||||
| 
 | 
 | ||||||
| 		if device.status is None: | 		if not device: | ||||||
| 			# the device was just unpaired, need to update the | 			# the device was just unpaired, need to update the | ||||||
| 			# status of the receiver as well | 			# status of the receiver as well | ||||||
| 			self.status_changed_callback(self.receiver) | 			self.status_changed_callback(self.receiver) | ||||||
|  | @ -155,6 +160,9 @@ class ReceiverListener(_listener.EventsListener): | ||||||
| 		# a device notification | 		# a device notification | ||||||
| 		assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices | 		assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices | ||||||
| 		already_known = n.devnumber in self.receiver | 		already_known = n.devnumber in self.receiver | ||||||
|  | 		if not already_known and n.sub_id == 0x41: | ||||||
|  | 			dev = self.receiver.register_new_device(n.devnumber, n) | ||||||
|  | 		else: | ||||||
| 			dev = self.receiver[n.devnumber] | 			dev = self.receiver[n.devnumber] | ||||||
| 
 | 
 | ||||||
| 		if not dev: | 		if not dev: | ||||||
|  | @ -162,21 +170,23 @@ class ReceiverListener(_listener.EventsListener): | ||||||
| 			return | 			return | ||||||
| 
 | 
 | ||||||
| 		if not already_known: | 		if not already_known: | ||||||
| 			# read these as soon as possible, they will be used everywhere | 			# _log.info("%s triggered new device %s", n, dev) | ||||||
| 			dev.protocol, dev.codename |  | ||||||
| 			dev.status = _status.DeviceStatus(dev, self._status_changed) | 			dev.status = _status.DeviceStatus(dev, self._status_changed) | ||||||
| 			dev.status.configuration = configuration | 			dev.status.configuration = configuration | ||||||
| 			# the receiver changed status as well | 			# the receiver changed status as well | ||||||
| 			self._status_changed(self.receiver) | 			self._status_changed(self.receiver) | ||||||
| 
 | 
 | ||||||
| 		# status may be None if the device has just been unpaired | 		assert dev | ||||||
| 		if dev.status is not None: | 		assert dev.status is not None | ||||||
| 		dev.status.process_notification(n) | 		dev.status.process_notification(n) | ||||||
| 		if self.receiver.status.lock_open and not already_known: | 		if self.receiver.status.lock_open and not already_known: | ||||||
| 			# this should be the first notification after a device was paired | 			# this should be the first notification after a device was paired | ||||||
| 			assert n.sub_id == 0x41 and n.address == 0x04 | 			assert n.sub_id == 0x41 and n.address == 0x04 | ||||||
| 			_log.info("%s: pairing detected new device", self.receiver) | 			_log.info("%s: pairing detected new device", self.receiver) | ||||||
| 			self.receiver.status.new_device = dev | 			self.receiver.status.new_device = dev | ||||||
|  | 		else: | ||||||
|  | 			if dev.online is None: | ||||||
|  | 				dev.ping() | ||||||
| 
 | 
 | ||||||
| 	def __str__(self): | 	def __str__(self): | ||||||
| 		return '<ReceiverListener(%s,%s)>' % (self.receiver.path, self.receiver.handle) | 		return '<ReceiverListener(%s,%s)>' % (self.receiver.path, self.receiver.handle) | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ def destroy(): | ||||||
| from logitech.unifying_receiver.status import ALERT | from logitech.unifying_receiver.status import ALERT | ||||||
| def _status_changed(device, alert, reason): | def _status_changed(device, alert, reason): | ||||||
| 	assert device is not None | 	assert device is not None | ||||||
| 	_log.info("status changed: %s, %s, %s", device, alert, reason) | 	_log.info("status changed: %s (%s) %s", device, alert, reason) | ||||||
| 
 | 
 | ||||||
| 	tray.update(device) | 	tray.update(device) | ||||||
| 	if alert & ALERT.ATTENTION: | 	if alert & ALERT.ATTENTION: | ||||||
|  |  | ||||||
|  | @ -127,7 +127,7 @@ def _create_sbox(s): | ||||||
| 	return sbox | 	return sbox | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _update_setting_item(sbox, value, is_active=True): | def _update_setting_item(sbox, value, is_online=True): | ||||||
| 	_, failed, spinner, control = sbox.get_children() | 	_, failed, spinner, control = sbox.get_children() | ||||||
| 	spinner.set_visible(False) | 	spinner.set_visible(False) | ||||||
| 	spinner.stop() | 	spinner.stop() | ||||||
|  | @ -135,7 +135,7 @@ def _update_setting_item(sbox, value, is_active=True): | ||||||
| 	# print ("update", control, "with new value", value) | 	# print ("update", control, "with new value", value) | ||||||
| 	if value is None: | 	if value is None: | ||||||
| 		control.set_sensitive(False) | 		control.set_sensitive(False) | ||||||
| 		failed.set_visible(is_active) | 		failed.set_visible(is_online) | ||||||
| 		return | 		return | ||||||
| 
 | 
 | ||||||
| 	failed.set_visible(False) | 	failed.set_visible(False) | ||||||
|  |  | ||||||
|  | @ -391,16 +391,19 @@ def _remove_receiver(receiver): | ||||||
| 			index += 1 | 			index += 1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _update_menu_item(index, device_status): | def _update_menu_item(index, device): | ||||||
|  | 	assert device | ||||||
|  | 	assert device.status is not None | ||||||
|  | 
 | ||||||
| 	menu_items = _menu.get_children() | 	menu_items = _menu.get_children() | ||||||
| 	menu_item = menu_items[index] | 	menu_item = menu_items[index] | ||||||
| 
 | 
 | ||||||
| 	level = device_status.get(_K.BATTERY_LEVEL) | 	level = device.status.get(_K.BATTERY_LEVEL) | ||||||
| 	charging = device_status.get(_K.BATTERY_CHARGING) | 	charging = device.status.get(_K.BATTERY_CHARGING) | ||||||
| 	icon_name = _icons.battery(level, charging) | 	icon_name = _icons.battery(level, charging) | ||||||
| 
 | 
 | ||||||
| 	image_widget = menu_item.get_image() | 	image_widget = menu_item.get_image() | ||||||
| 	image_widget.set_sensitive(bool(device_status)) | 	image_widget.set_sensitive(bool(device.online)) | ||||||
| 	_update_menu_icon(image_widget, icon_name) | 	_update_menu_icon(image_widget, icon_name) | ||||||
| 
 | 
 | ||||||
| # | # | ||||||
|  | @ -455,17 +458,17 @@ def update(device=None): | ||||||
| 
 | 
 | ||||||
| 		else: | 		else: | ||||||
| 			# peripheral | 			# peripheral | ||||||
| 			is_alive = device.status is not None | 			is_paired = bool(device) | ||||||
| 			receiver_path = device.receiver.path | 			receiver_path = device.receiver.path | ||||||
| 			index = None | 			index = None | ||||||
| 			for idx, (path, serial, name, _, _) in enumerate(_devices_info): | 			for idx, (path, serial, name, _, _) in enumerate(_devices_info): | ||||||
| 				if path == receiver_path and serial == device.serial: | 				if path == receiver_path and serial == device.serial: | ||||||
| 					index = idx | 					index = idx | ||||||
| 
 | 
 | ||||||
| 			if is_alive: | 			if is_paired: | ||||||
| 				if index is None: | 				if index is None: | ||||||
| 					index = _add_device(device) | 					index = _add_device(device) | ||||||
| 				_update_menu_item(index, device.status) | 				_update_menu_item(index, device) | ||||||
| 			else: | 			else: | ||||||
| 				# was just unpaired | 				# was just unpaired | ||||||
| 				if index: | 				if index: | ||||||
|  |  | ||||||
|  | @ -431,6 +431,7 @@ def _update_details(button): | ||||||
| 	assert button | 	assert button | ||||||
| 	visible = button.get_active() | 	visible = button.get_active() | ||||||
| 	device = _find_selected_device() | 	device = _find_selected_device() | ||||||
|  | 	assert device | ||||||
| 
 | 
 | ||||||
| 	if visible: | 	if visible: | ||||||
| 		_details._text.set_markup('<small>reading...</small>') | 		_details._text.set_markup('<small>reading...</small>') | ||||||
|  | @ -465,6 +466,8 @@ def _update_details(button): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _update_receiver_panel(receiver, panel, buttons, full=False): | def _update_receiver_panel(receiver, panel, buttons, full=False): | ||||||
|  | 	assert receiver | ||||||
|  | 
 | ||||||
| 	devices_count = len(receiver) | 	devices_count = len(receiver) | ||||||
| 	if receiver.max_devices > 1: | 	if receiver.max_devices > 1: | ||||||
| 		if devices_count == 0: | 		if devices_count == 0: | ||||||
|  | @ -498,8 +501,9 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _update_device_panel(device, panel, buttons, full=False): | def _update_device_panel(device, panel, buttons, full=False): | ||||||
| 	is_active = bool(device.status) | 	assert device | ||||||
| 	panel.set_sensitive(is_active) | 	is_online = bool(device.online) | ||||||
|  | 	panel.set_sensitive(is_online) | ||||||
| 
 | 
 | ||||||
| 	battery_level = device.status.get(_K.BATTERY_LEVEL) | 	battery_level = device.status.get(_K.BATTERY_LEVEL) | ||||||
| 	if battery_level is None: | 	if battery_level is None: | ||||||
|  | @ -515,15 +519,15 @@ def _update_device_panel(device, panel, buttons, full=False): | ||||||
| 		panel._battery._icon.set_sensitive(True) | 		panel._battery._icon.set_sensitive(True) | ||||||
| 
 | 
 | ||||||
| 		text = '%d%%' % battery_level | 		text = '%d%%' % battery_level | ||||||
| 		if is_active: | 		if is_online: | ||||||
| 			if charging: | 			if charging: | ||||||
| 				text += ' <small>(charging)</small>' | 				text += ' <small>(charging)</small>' | ||||||
| 		else: | 		else: | ||||||
| 			text += ' <small>(last known)</small>' | 			text += ' <small>(last known)</small>' | ||||||
| 		panel._battery._text.set_sensitive(is_active) | 		panel._battery._text.set_sensitive(is_online) | ||||||
| 		panel._battery._text.set_markup(text) | 		panel._battery._text.set_markup(text) | ||||||
| 
 | 
 | ||||||
| 	if is_active: | 	if is_online: | ||||||
| 		not_secure = device.status.get(_K.LINK_ENCRYPTED) == False | 		not_secure = device.status.get(_K.LINK_ENCRYPTED) == False | ||||||
| 		if not_secure: | 		if not_secure: | ||||||
| 			panel._secure._text.set_text('not encrypted') | 			panel._secure._text.set_text('not encrypted') | ||||||
|  | @ -539,7 +543,7 @@ def _update_device_panel(device, panel, buttons, full=False): | ||||||
| 		panel._secure._icon.set_visible(False) | 		panel._secure._icon.set_visible(False) | ||||||
| 		panel._secure.set_tooltip_text('') | 		panel._secure.set_tooltip_text('') | ||||||
| 
 | 
 | ||||||
| 	if is_active: | 	if is_online: | ||||||
| 		light_level = device.status.get(_K.LIGHT_LEVEL) | 		light_level = device.status.get(_K.LIGHT_LEVEL) | ||||||
| 		if light_level is None: | 		if light_level is None: | ||||||
| 			panel._lux.set_visible(False) | 			panel._lux.set_visible(False) | ||||||
|  | @ -557,7 +561,7 @@ def _update_device_panel(device, panel, buttons, full=False): | ||||||
| 	panel.set_visible(True) | 	panel.set_visible(True) | ||||||
| 
 | 
 | ||||||
| 	if full: | 	if full: | ||||||
| 		_config_panel.update(panel._config, device, is_active) | 		_config_panel.update(device, is_online) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _update_info_panel(device, full=False): | def _update_info_panel(device, full=False): | ||||||
|  | @ -567,19 +571,24 @@ def _update_info_panel(device, full=False): | ||||||
| 		_empty.set_visible(True) | 		_empty.set_visible(True) | ||||||
| 		return | 		return | ||||||
| 
 | 
 | ||||||
| 	is_active = bool(device.status) | 	# a receiver must be valid | ||||||
|  | 	# a device must be paired | ||||||
|  | 	assert device | ||||||
| 
 | 
 | ||||||
| 	_info._title.set_markup('<b>%s</b>' % device.name) | 	_info._title.set_markup('<b>%s</b>' % device.name) | ||||||
| 	_info._title.set_sensitive(is_active) |  | ||||||
| 	icon_name = _icons.device_icon_name(device.name, device.kind) | 	icon_name = _icons.device_icon_name(device.name, device.kind) | ||||||
| 	_info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) | 	_info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) | ||||||
| 	_info._icon.set_sensitive(is_active) |  | ||||||
| 
 | 
 | ||||||
| 	if device.kind is None: | 	if device.kind is None: | ||||||
| 		_info._device.set_visible(False) | 		_info._device.set_visible(False) | ||||||
|  | 		_info._icon.set_sensitive(True) | ||||||
|  | 		_info._title.set_sensitive(True) | ||||||
| 		_update_receiver_panel(device, _info._receiver, _info._buttons, full) | 		_update_receiver_panel(device, _info._receiver, _info._buttons, full) | ||||||
| 	else: | 	else: | ||||||
| 		_info._receiver.set_visible(False) | 		_info._receiver.set_visible(False) | ||||||
|  | 		is_online = bool(device.online) | ||||||
|  | 		_info._icon.set_sensitive(is_online) | ||||||
|  | 		_info._title.set_sensitive(is_online) | ||||||
| 		_update_device_panel(device, _info._device, _info._buttons, full) | 		_update_device_panel(device, _info._device, _info._buttons, full) | ||||||
| 
 | 
 | ||||||
| 	_empty.set_visible(False) | 	_empty.set_visible(False) | ||||||
|  | @ -664,10 +673,16 @@ def update(device, need_popup=False): | ||||||
| 
 | 
 | ||||||
| 	else: | 	else: | ||||||
| 		# peripheral | 		# peripheral | ||||||
| 		is_alive = device.status is not None | 		is_paired = bool(device) | ||||||
| 		item = _device_row(device.receiver.path, device.serial, device if is_alive else None) | 		assert device.receiver | ||||||
| 		if is_alive and item: | 		assert device.serial | ||||||
| 			_model.set_value(item, _COLUMN.ACTIVE, bool(device.status)) | 		item = _device_row(device.receiver.path, device.serial, device if is_paired else None) | ||||||
|  | 
 | ||||||
|  | 		if is_paired and item: | ||||||
|  | 			was_online = _model.get_value(item, _COLUMN.ACTIVE) | ||||||
|  | 			is_online = bool(device.online) | ||||||
|  | 			_model.set_value(item, _COLUMN.ACTIVE, is_online) | ||||||
|  | 
 | ||||||
| 			battery_level = device.status.get(_K.BATTERY_LEVEL) | 			battery_level = device.status.get(_K.BATTERY_LEVEL) | ||||||
| 			if battery_level is None: | 			if battery_level is None: | ||||||
| 				_model.set_value(item, _COLUMN.STATUS_ICON, '') | 				_model.set_value(item, _COLUMN.STATUS_ICON, '') | ||||||
|  | @ -679,7 +694,8 @@ def update(device, need_popup=False): | ||||||
| 			if selected_device_id is None: | 			if selected_device_id is None: | ||||||
| 				select(device.receiver.path, device.serial) | 				select(device.receiver.path, device.serial) | ||||||
| 			elif selected_device_id == device.serial: | 			elif selected_device_id == device.serial: | ||||||
| 				_update_info_panel(device, need_popup) | 				full_update = need_popup or was_online != is_online | ||||||
|  | 				_update_info_panel(device, full=full_update) | ||||||
| 
 | 
 | ||||||
| 		elif item: | 		elif item: | ||||||
| 			_model.remove(item) | 			_model.remove(item) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue