Solaar/lib/logitech/ur_queue2.py

217 lines
5.7 KiB
Python

"""A few functions to deal with the Logitech Universal Receiver.
It is assumed a single UR device is attached to the machine.
Uses hidapi.
"""
import logging
import threading
from . import ur_lowlevel as urll
from urll import FEATURE
_log = logging.getLogger('logitech.ur')
_log.setLevel(logging.DEBUG)
# class NoDevice(Exception):
# """May be thrown when trying to talk through a previously present device
# that is no longer available."""
# pass
class _EventQueue(threading.Thread):
def __init__(self, receiver, timeout=urll.DEFAULT_TIMEOUT):
super(_EventQueue, self).__init__()
self.daemon = True
self.receiver = receiver
self.timeout = timeout
self.active = True
def stop(self):
self.active = False
self.join()
def run(self):
while self.active:
data = urll.read(self.receiver.handle, self.timeout)
if not self.active:
# in case the queue has been stopped while reading
break
if data:
self.receiver._dispatch(*data)
class Receiver:
def __init__(self, path, handle=None, timeout=urll.DEFAULT_TIMEOUT):
self.path = path
self.handle = handle
self.DEVICE_FEATURES = {}
self.hooks = {}
self.event_queue = _EventQueue(self.handle, timeout)
self.event_queue.start()
def close(self):
self.event_queue.stop()
self.event_queue = None
urll.close(self.handle)
self.handle = None
self.hooks = {}
self.DEVICE_FEATURES = {}
def ping(self, device):
reply = self.event_queue.req()
if not urll.write(self.handle, device, '\x00\x10\x00\x00\xAA'):
# print "write failed",
return False
reply = urll.read(self.handle, device)
if not reply:
# print "no data",
return False
# 10018f00100900
if ord(reply[0]) == 0x10:
if ord(reply[2]) == 0x8F:
# print "invalid",
return False
# 110100100200aa00000000000000000000000000
if ord(reply[0]) == 0x11:
if reply[2:4] == "\x00\x10" and reply[6] == "\xAA":
# success
return True
# print "unknown"
return False
def hook(self, device, feature, function=None, callback=None):
features = self.DEVICE_FEATURES[device]
if feature not in features:
raise Exception("feature " + feature + " not supported by device")
feature_index = features.index(feature)
key = (device, feature_index, function, callback)
if key not in self.hooks:
self.hooks[key] = []
if callback is None:
if callback in self.hooks[key]:
self.hooks[key].remove(callback)
else:
self.hooks[key].append(callback)
def _dispatch(self, status, device, data):
_log.debug("incoming event %2x:%2x:%s", status, device, data.encode('hex'))
dispatched = False
for (key, callback) in self.hooks.items():
if key[0] == device and key[1] == ord(data[0]):
if key[2] is not None and key[2] == data[1] & 0xFF:
callback.__call__(data)
if not dispatched:
_log.debug("ignored incoming event %2x:%2x:%s",
status, device, data.encode('hex'))
def _request(self, device, data=''):
if urll.write(self.handler, device, data):
pass
def find_device(self, device_type=None, name=None):
"""Gets the device number for the first device matching.
The device type and name are case-insensitive.
"""
# Apparently a receiver supports up to 6 devices.
for device in range(1, 7):
if self.ping(device):
if device not in self.DEVICE_FEATURES:
self.DEVICE_FEATURES[device] = \
urll.get_device_features(self.handle, device)
# print get_reprogrammable_keys(receiver, device)
# d_firmware = get_firmware_version(receiver, device)
# print "device", device, "[", d_name, "/", d_type, "]"
# print "firmware", d_firmware, "features", _DEVICE_FEATURES[device]
if device_type:
d_type = self.get_type(device)
if d_type is None or device_type.lower() != d_type.lower():
continue
if name:
d_name = self.get_name(device)
if d_name is None or name.lower() != d_name.lower():
continue
return device
def get_type(self, device):
reply = self._request(device, FEATURE.GET_NAME, '\x20')
if reply:
return DEVICE_TYPES[ord(reply[2][2])]
def get_name(self, device):
reply = self._request(device, FEATURE.GET_NAME)
if reply:
charcount = ord(reply[4])
name = ''
index = 0
while len(name) < charcount:
reply = self._request(device, FEATURE.NAME, '\x10', chr(index))
if reply:
name += reply[4:4 + charcount - index]
index = len(name)
else:
break
return name
def get_firmware_version(self, device, firmware_type=0):
reply = self._request(device,
FEATURE.FIRMWARE, '\x10', chr(firmware_type))
if reply:
return '%s %s.%s' % (reply[5:8],
reply[8:10].encode('hex'), reply[10:12].encode('hex'))
def get_battery_level(self, device):
reply = self._request(device, FEATURE.BATTERY)
if reply:
return (ord(reply[4]), ord(reply[5]), ord(reply[6]))
def get_reprogrammable_keys(self, device):
count = self._request(device, FEATURE.REPROGRAMMABLE_KEYS)
if count:
keys = []
for index in range(ord(count[4])):
key = self._request(device,
FEATURE.REPROGRAMMABLE_KEYS, '\x10', chr(index))
keys.append(key[4:6], keys[6:8], ord(key[8]))
return keys
def get_solar_charge(self, device):
reply = self._request(device, FEATURE.SOLAR_CHARGE,
'\x03', '\x78', '\x01', reply_function='\x10')
if reply:
charge = ord(reply[4])
lux = ord(reply[5]) << 8 | ord(reply[6])
# lux = int(round(((255 * ord(reply[5])) + ord(reply[6])) / 538.0, 2) * 100)
return (charge, lux)
#
#
#
def get():
"""Gets a Receiver object for the Unifying Receiver connected to the machine.
It is assumed a single receiver is connected to the machine. If more than
one are present, the first one found will be returned.
:returns: a Receiver object, or None.
"""
receiver = urll.open()
if receiver:
return Receiver(*receiver)