receiver: handle bluetooth-connected devices
This commit is contained in:
parent
1e7050595e
commit
815c9755b5
|
@ -156,7 +156,7 @@ def close(handle):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def write(handle, devnumber, data):
|
def write(handle, devnumber, data, long_message=False):
|
||||||
"""Writes some data to the receiver, addressed to a certain device.
|
"""Writes some data to the receiver, addressed to a certain device.
|
||||||
|
|
||||||
:param handle: an open UR handle.
|
:param handle: an open UR handle.
|
||||||
|
@ -173,7 +173,7 @@ def write(handle, devnumber, data):
|
||||||
assert data is not None
|
assert data is not None
|
||||||
assert isinstance(data, bytes), (repr(data), type(data))
|
assert isinstance(data, bytes), (repr(data), type(data))
|
||||||
|
|
||||||
if len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b'\x82':
|
if long_message or len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b'\x82':
|
||||||
wdata = _pack('!BB18s', 0x11, devnumber, data)
|
wdata = _pack('!BB18s', 0x11, devnumber, data)
|
||||||
else:
|
else:
|
||||||
wdata = _pack('!BB5s', 0x10, devnumber, data)
|
wdata = _pack('!BB5s', 0x10, devnumber, data)
|
||||||
|
@ -232,7 +232,7 @@ def _read(handle, timeout):
|
||||||
timeout = int(timeout * 1000)
|
timeout = int(timeout * 1000)
|
||||||
data = _hid.read(int(handle), _MAX_READ_SIZE, timeout)
|
data = _hid.read(int(handle), _MAX_READ_SIZE, timeout)
|
||||||
except Exception as reason:
|
except Exception as reason:
|
||||||
_log.error('read failed, assuming handle %r no longer available', handle)
|
_log.warn('read failed, assuming handle %r no longer available', handle)
|
||||||
close(handle)
|
close(handle)
|
||||||
raise NoReceiver(reason=reason)
|
raise NoReceiver(reason=reason)
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ del namedtuple
|
||||||
|
|
||||||
|
|
||||||
# a very few requests (e.g., host switching) do not expect a reply, but use no_reply=True with extreme caution
|
# a very few requests (e.g., host switching) do not expect a reply, but use no_reply=True with extreme caution
|
||||||
def request(handle, devnumber, request_id, *params, no_reply=False, return_error=False):
|
def request(handle, devnumber, request_id, *params, no_reply=False, return_error=False, long_message=False):
|
||||||
"""Makes a feature call to a device and waits for a matching reply.
|
"""Makes a feature call to a device and waits for a matching reply.
|
||||||
:param handle: an open UR handle.
|
:param handle: an open UR handle.
|
||||||
:param devnumber: attached device number.
|
:param devnumber: attached device number.
|
||||||
|
@ -357,7 +357,7 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error
|
||||||
ihandle = int(handle)
|
ihandle = int(handle)
|
||||||
notifications_hook = getattr(handle, 'notifications_hook', None)
|
notifications_hook = getattr(handle, 'notifications_hook', None)
|
||||||
_skip_incoming(handle, ihandle, notifications_hook)
|
_skip_incoming(handle, ihandle, notifications_hook)
|
||||||
write(ihandle, devnumber, request_data)
|
write(ihandle, devnumber, request_data, long_message)
|
||||||
|
|
||||||
if no_reply:
|
if no_reply:
|
||||||
return None
|
return None
|
||||||
|
@ -443,7 +443,7 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error
|
||||||
# raise DeviceUnreachable(number=devnumber, request=request_id)
|
# raise DeviceUnreachable(number=devnumber, request=request_id)
|
||||||
|
|
||||||
|
|
||||||
def ping(handle, devnumber):
|
def ping(handle, devnumber, long_message=False):
|
||||||
"""Check if a device is connected to the receiver.
|
"""Check if a device is connected to the receiver.
|
||||||
|
|
||||||
:returns: The HID protocol supported by the device, as a floating point number, if the device is active.
|
:returns: The HID protocol supported by the device, as a floating point number, if the device is active.
|
||||||
|
@ -467,7 +467,7 @@ def ping(handle, devnumber):
|
||||||
ihandle = int(handle)
|
ihandle = int(handle)
|
||||||
notifications_hook = getattr(handle, 'notifications_hook', None)
|
notifications_hook = getattr(handle, 'notifications_hook', None)
|
||||||
_skip_incoming(handle, ihandle, notifications_hook)
|
_skip_incoming(handle, ihandle, notifications_hook)
|
||||||
write(ihandle, devnumber, request_data)
|
write(ihandle, devnumber, request_data, long_message)
|
||||||
|
|
||||||
# we consider timeout from this point
|
# we consider timeout from this point
|
||||||
request_started = _timestamp()
|
request_started = _timestamp()
|
||||||
|
|
|
@ -11,10 +11,10 @@ import hidapi as _hid
|
||||||
import solaar.configuration as _configuration
|
import solaar.configuration as _configuration
|
||||||
|
|
||||||
from . import base as _base
|
from . import base as _base
|
||||||
|
from . import descriptors as _descriptors
|
||||||
from . import hidpp10 as _hidpp10
|
from . import hidpp10 as _hidpp10
|
||||||
from . import hidpp20 as _hidpp20
|
from . import hidpp20 as _hidpp20
|
||||||
from .common import strhex as _strhex
|
from .common import strhex as _strhex
|
||||||
from .descriptors import DEVICES as _DESCRIPTORS
|
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .settings_templates import check_feature_settings as _check_feature_settings
|
from .settings_templates import check_feature_settings as _check_feature_settings
|
||||||
|
|
||||||
|
@ -51,7 +51,8 @@ class Device(object):
|
||||||
# the Wireless PID is unique per device model
|
# the Wireless PID is unique per device model
|
||||||
self.wpid = None
|
self.wpid = None
|
||||||
self.descriptor = None
|
self.descriptor = None
|
||||||
|
# Bluetooth connections need long messages
|
||||||
|
self.bluetooth = False
|
||||||
# mouse, keyboard, etc (see _hidpp10.DEVICE_KIND)
|
# mouse, keyboard, etc (see _hidpp10.DEVICE_KIND)
|
||||||
self._kind = None
|
self._kind = None
|
||||||
# Unifying peripherals report a codename.
|
# Unifying peripherals report a codename.
|
||||||
|
@ -127,7 +128,6 @@ class Device(object):
|
||||||
if device_info is None:
|
if device_info is None:
|
||||||
_log.error('failed to read Nano wpid for device %d of %s', number, receiver)
|
_log.error('failed to read Nano wpid for device %d of %s', number, receiver)
|
||||||
raise _base.NoSuchDevice(number=number, receiver=receiver, error='read Nano wpid')
|
raise _base.NoSuchDevice(number=number, receiver=receiver, error='read Nano wpid')
|
||||||
|
|
||||||
self.wpid = _strhex(device_info[3:5])
|
self.wpid = _strhex(device_info[3:5])
|
||||||
self._power_switch = '(' + _('unknown') + ')'
|
self._power_switch = '(' + _('unknown') + ')'
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ class Device(object):
|
||||||
except Exception: # give up
|
except Exception: # give up
|
||||||
self.handle = None
|
self.handle = None
|
||||||
|
|
||||||
self.descriptor = _DESCRIPTORS.get(self.wpid)
|
self.descriptor = _descriptors.get_wpid(self.wpid)
|
||||||
if self.descriptor is None:
|
if self.descriptor is None:
|
||||||
# Last chance to correctly identify the device; many Nano
|
# Last chance to correctly identify the device; many Nano
|
||||||
# receivers do not support this call.
|
# receivers do not support this call.
|
||||||
|
@ -156,21 +156,24 @@ class Device(object):
|
||||||
codename_length = ord(codename[1:2])
|
codename_length = ord(codename[1:2])
|
||||||
codename = codename[2:2 + codename_length]
|
codename = codename[2:2 + codename_length]
|
||||||
self._codename = codename.decode('ascii')
|
self._codename = codename.decode('ascii')
|
||||||
self.descriptor = _DESCRIPTORS.get(self._codename)
|
self.descriptor = _descriptors.get_codename(self._codename)
|
||||||
|
else:
|
||||||
|
self.path = info.path
|
||||||
|
self.handle = _hid.open_path(self.path)
|
||||||
|
self.online = True
|
||||||
|
self.product_id = info.product_id
|
||||||
|
self.bluetooth = info.bus_id == 0x0005
|
||||||
|
self.descriptor = _descriptors.get_btid(self.product_id
|
||||||
|
) if self.bluetooth else _descriptors.get_usbid(self.product_id)
|
||||||
|
|
||||||
if self.descriptor:
|
if self.descriptor:
|
||||||
self._name = self.descriptor.name
|
self._name = self.descriptor.name
|
||||||
|
if self.descriptor.protocol:
|
||||||
self._protocol = self.descriptor.protocol
|
self._protocol = self.descriptor.protocol
|
||||||
if self._codename is None:
|
if self._codename is None:
|
||||||
self._codename = self.descriptor.codename
|
self._codename = self.descriptor.codename
|
||||||
if self._kind is None:
|
if self._kind is None:
|
||||||
self._kind = self.descriptor.kind
|
self._kind = self.descriptor.kind
|
||||||
else:
|
|
||||||
self.path = info.path
|
|
||||||
self.handle = _hid.open_path(self.path)
|
|
||||||
self.product_id = info.product_id
|
|
||||||
self._serial = ''.join(info.serial.split('-')).upper()
|
|
||||||
self.online = True
|
|
||||||
|
|
||||||
if self._protocol is not None:
|
if self._protocol is not None:
|
||||||
self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray(self)
|
self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray(self)
|
||||||
|
@ -181,7 +184,7 @@ class Device(object):
|
||||||
@property
|
@property
|
||||||
def protocol(self):
|
def protocol(self):
|
||||||
if not self._protocol and self.online:
|
if not self._protocol and self.online:
|
||||||
self._protocol = _base.ping(self.handle or self.receiver.handle, self.number)
|
self._protocol = _base.ping(self.handle or self.receiver.handle, self.number, long_message=self.bluetooth)
|
||||||
# if the ping failed, the peripheral is (almost) certainly offline
|
# if the ping failed, the peripheral is (almost) certainly offline
|
||||||
self.online = self._protocol is not None
|
self.online = self._protocol is not None
|
||||||
|
|
||||||
|
@ -417,7 +420,14 @@ class Device(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def request(self, request_id, *params, no_reply=False):
|
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)
|
return _base.request(
|
||||||
|
self.handle or self.receiver.handle,
|
||||||
|
self.number,
|
||||||
|
request_id,
|
||||||
|
*params,
|
||||||
|
no_reply=no_reply,
|
||||||
|
long_message=self.bluetooth
|
||||||
|
)
|
||||||
|
|
||||||
def feature_request(self, feature, function=0x00, *params, no_reply=False):
|
def feature_request(self, feature, function=0x00, *params, no_reply=False):
|
||||||
if self.protocol >= 2.0:
|
if self.protocol >= 2.0:
|
||||||
|
@ -425,7 +435,7 @@ class Device(object):
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
"""Checks if the device is online, returns True of False"""
|
"""Checks if the device is online, returns True of False"""
|
||||||
protocol = _base.ping(self.handle or self.receiver.handle, self.number)
|
protocol = _base.ping(self.handle or self.receiver.handle, self.number, long_message=self.bluetooth)
|
||||||
self.online = protocol is not None
|
self.online = protocol is not None
|
||||||
if protocol:
|
if protocol:
|
||||||
self._protocol = protocol
|
self._protocol = protocol
|
||||||
|
|
|
@ -117,7 +117,7 @@ def _wired_devices(dev_path=None):
|
||||||
if dev_path is not None and dev_path != dev_info.path:
|
if dev_path is not None and dev_path != dev_info.path:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
d = Device(None, 0, info=dev_info)
|
d = Device.open(dev_info)
|
||||||
if _log.isEnabledFor(_DEBUG):
|
if _log.isEnabledFor(_DEBUG):
|
||||||
_log.debug('[%s] => %s', dev_info.path, d)
|
_log.debug('[%s] => %s', dev_info.path, d)
|
||||||
if d is not None:
|
if d is not None:
|
||||||
|
|
|
@ -137,7 +137,7 @@ def persister(device):
|
||||||
_configuration[key] = entry
|
_configuration[key] = entry
|
||||||
elif device.wpid and not entry: # create now with wpid:serial
|
elif device.wpid and not entry: # create now with wpid:serial
|
||||||
key = '%s:%s' % (device.wpid, device.serial)
|
key = '%s:%s' % (device.wpid, device.serial)
|
||||||
else: # create now with modelId:unitId
|
elif not entry: # create now with modelId:unitId
|
||||||
key = '%s:%s' % (device.modelId, device.unitId)
|
key = '%s:%s' % (device.modelId, device.unitId)
|
||||||
else: # defer until more is known (i.e., device comes on line)
|
else: # defer until more is known (i.e., device comes on line)
|
||||||
return
|
return
|
||||||
|
|
|
@ -324,6 +324,12 @@ def stop_all():
|
||||||
# so mark its saved status to ensure that the status is pushed to the device when it comes back
|
# so mark its saved status to ensure that the status is pushed to the device when it comes back
|
||||||
def ping_all(resuming=False):
|
def ping_all(resuming=False):
|
||||||
for l in _all_listeners.values():
|
for l in _all_listeners.values():
|
||||||
|
if l.receiver.isDevice:
|
||||||
|
if resuming:
|
||||||
|
l.receiver.status._active = False
|
||||||
|
l.receiver.ping()
|
||||||
|
l._status_changed(l.receiver)
|
||||||
|
else:
|
||||||
count = l.receiver.count()
|
count = l.receiver.count()
|
||||||
if count:
|
if count:
|
||||||
for dev in l.receiver:
|
for dev in l.receiver:
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
ACTION != "add", GOTO="solaar_end"
|
ACTION != "add", GOTO="solaar_end"
|
||||||
SUBSYSTEM != "hidraw", GOTO="solaar_end"
|
SUBSYSTEM != "hidraw", GOTO="solaar_end"
|
||||||
|
|
||||||
# Logitech receivers and direct-connected devices
|
# USB-connected Logitech receivers and devices
|
||||||
ATTRS{idVendor}=="046d", GOTO="solaar_apply"
|
ATTRS{idVendor}=="046d", GOTO="solaar_apply"
|
||||||
|
|
||||||
# Lenovo nano receiver
|
# Lenovo nano receiver
|
||||||
ATTRS{idVendor}=="17ef", ATTRS{idProduct}=="6042", GOTO="solaar_apply"
|
ATTRS{idVendor}=="17ef", ATTRS{idProduct}=="6042", GOTO="solaar_apply"
|
||||||
|
|
||||||
|
# Bluetooth-connected Logitech devices
|
||||||
|
KERNELS == "0005:046D:*", GOTO="solaar_apply"
|
||||||
|
|
||||||
GOTO="solaar_end"
|
GOTO="solaar_end"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue