From ebe8320f2e51018dc8ebf4a365e4a8e193d55d1b Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Tue, 25 Sep 2012 17:41:40 +0300 Subject: [PATCH] brought solar app up-to-date with the UR api --- .gitignore | 2 + logitech/unifying_receiver/api.py | 5 +- logitech/unifying_receiver/base.py | 5 +- solar | 9 +++ solar.py | 107 +++++++++++++++++++++-------- unittest.sh | 2 +- 6 files changed, 96 insertions(+), 34 deletions(-) create mode 100755 solar diff --git a/.gitignore b/.gitignore index 4cd99212..045b5abd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.so *.pyc +*.pyo +*.log diff --git a/logitech/unifying_receiver/api.py b/logitech/unifying_receiver/api.py index d1a0a8c7..864161e9 100644 --- a/logitech/unifying_receiver/api.py +++ b/logitech/unifying_receiver/api.py @@ -274,7 +274,7 @@ def get_device_firmware(handle, device, features_array=None): str((ord(fw_info[5]) & 0xF0) >> 4) + str(ord(fw_info[5]) & 0x0F)) name = prefix + ' ' + version - build = 256 * ord(fw_info[6]) + ord(fw_info[7]) + build = (ord(fw_info[6]) << 8) + ord(fw_info[7]) if build: name += ' b' + str(build) extras = fw_info[9:].rstrip('\x00') @@ -324,8 +324,11 @@ def get_device_name(handle, device, features_array=None): _l.log(_LOG_LEVEL, "(%d,%d) device name %s", handle, device, d_name) return d_name + def get_device_battery_level(handle, device, features_array=None): """Reads a device's battery level. + + :raises FeatureNotSupported: if the device does not support this feature. """ battery = request(handle, device, FEATURE.BATTERY, features_array=features_array) if battery: diff --git a/logitech/unifying_receiver/base.py b/logitech/unifying_receiver/base.py index ccec6ace..5a58a9e5 100644 --- a/logitech/unifying_receiver/base.py +++ b/logitech/unifying_receiver/base.py @@ -150,6 +150,7 @@ def write(handle, device, data): _l.warn("(%d:%d) <= w[%s] call packet too long: %d bytes", handle, device, wdata.encode('hex'), len(wdata)) if not _hid.write(handle, wdata): _l.warn("(%d,%d) write failed, assuming receiver no longer available", handle, device) + close(handle) raise NoReceiver() @@ -210,7 +211,7 @@ def request(handle, device, feature_index_function, params=b'', features_array=N if reply_device != device: # this message not for the device we're interested in - _l.log(_LOG_LEVEL, "(%d,%d) request got reply for unexpected device %d: [%s]", handle, device, reply_device, reply.encode('hex')) + _l.log(_LOG_LEVEL, "(%d,%d) request got reply for unexpected device %d: [%s]", handle, device, reply_device, reply_data.encode('hex')) # worst case scenario, this is a reply for a concurrent request # on this receiver _unhandled._publish(reply_code, reply_device, reply_data) @@ -229,7 +230,7 @@ def request(handle, device, feature_index_function, params=b'', features_array=N if reply_code == 0x11 and reply_data[0] == b'\xFF' and reply_data[1:3] == feature_index_function: # an error returned from the device error_code = ord(reply_data[3]) - _l.warn("(%d,%d) request feature call error %d = %s: %s", handle, device, error, _ERROR_NAME(error_code), reply_data.encode('hex')) + _l.warn("(%d,%d) request feature call error %d = %s: %s", handle, device, error_code, ERROR_NAME(error_code), reply_data.encode('hex')) feature_index = ord(feature_index_function[0]) feature_function = feature_index_function[1].encode('hex') feature = None if features_array is None else features_array[feature_index] diff --git a/solar b/solar new file mode 100755 index 00000000..231e2324 --- /dev/null +++ b/solar @@ -0,0 +1,9 @@ +#!/bin/sh + +cd `dirname "$0"` + +export LD_LIBRARY_PATH=$PWD/lib +export PYTHONPATH=$PWD/lib +export PYTHONWARNINGS=all + +exec python -OO -tt -u -3 solar.py "$@" diff --git a/solar.py b/solar.py index e935428d..da6ccdac 100644 --- a/solar.py +++ b/solar.py @@ -1,28 +1,35 @@ +#!/usr/bin/env python + +import logging +logging.basicConfig(level=1) +logging.captureWarnings(True) + import time import threading import subprocess from collections import namedtuple -import gobject -import pygtk -pygtk.require('2.0') -import gtk +from gi.repository import GObject +from gi.repository import Gtk -from logitech import unifying_receiver as ur +from logitech.unifying_receiver import api as ur +# +# A few constants +# + KEYBOARD_NAME = 'Wireless Solar Keyboard K750' TITLE = 'Solar [K750]' NOTIFY_DESKTOP = True -BLINK_ICON = 3 OK = 0 NO_RECEIVER = 1 NO_K750 = 2 NO_STATUS = 3 -SLEEP = (10, 30, 25, 15) +SLEEP = (10, 5, 5, 15) TEXT = ('K750 keyboard connected', 'Logitech Unifying Receiver not detected', 'K750 keyboard not detected', @@ -38,17 +45,39 @@ K750_Status = namedtuple('K750_Status', ['receiver', 'device', 'status', 'charge', 'lux']) +# +# +# + + +# _unhandled_queue = [] + +# def unhandled_messages_hook(code, device, data): +# if len(_unhandled_queue) > 32: +# del _unhandled_queue[:] +# _unhandled_queue.append((code, device, data)) + +# from logitech.unifying_receiver import unhandled +# unhandled.set_unhandled_hook(unhandled_messages_hook) + + +# +# +# + + def notify_desktop(status_code, text): global NOTIFY_DESKTOP if NOTIFY_DESKTOP: try: - program = ('/usr/bin/notify-send', '-u', 'low', TITLE, text) - subprocess.Popen(program, close_fds=True) + subprocess.call(('notify-send', '-u', 'low', TITLE, text)) except OSError: NOTIFY_DESKTOP = False def update_status_icon(status_icon, status_changed, k750): + print "update status", status_changed, k750 + text = TEXT[k750.status] if k750.status == OK: text += '\n' + (CHARGE_LUX_TEXT % (k750.charge, k750.lux)) @@ -57,10 +86,6 @@ def update_status_icon(status_icon, status_changed, k750): if status_changed: notify_desktop(k750.status, text) - if BLINK_ICON: - status_icon.set_blinking(True) - time.sleep(BLINK_ICON) - status_icon.set_blinking(False) def read_charge(receiver, device): @@ -72,26 +97,47 @@ def read_charge(receiver, device): if receiver and not device: try: - device = ur.find_device(receiver, "keyboard", KEYBOARD_NAME) + device = ur.find_device_by_name(receiver, KEYBOARD_NAME) except ur.NoReceiver: - ur.close(receiver) receiver = None - if receiver: - if device: + if receiver and device: + feature_solar_index = device.features_array.index(ur.FEATURE.SOLAR_CHARGE) + + event = None + for i in range(0, 20): + next_event = ur.base.read(receiver, ur.base.DEFAULT_TIMEOUT * 2 // i if i > 0 else ur.base.DEFAULT_TIMEOUT * 3) + if not next_event: + break + if next_event[1] == device.number: + if next_event[0] == 0x10 and next_event[2][0] == b'\x8F': + event = next_event + elif next_event[0] == 0x11 and next_event[2][0] == chr(feature_solar_index) and next_event[2][7:11] == b'GOOD': + if next_event[2][1] == b'\x10': + event = next_event + elif next_event[2][1] == b'\x00' or next_event[2][1] == b'\x20': + event = next_event + + if event is None: try: - charge_lux = ur.get_solar_charge(receiver, device) - if charge_lux is None: + reply = ur.request(receiver, device.number, ur.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01', features_array=device.features_array) + if reply is None: + status = NO_K750 device = None - status = NO_STATUS else: - charge, lux = charge_lux - status = OK + return read_charge(receiver, device) except ur.NoReceiver: - ur.close(receiver) receiver = None + device = None else: - status = NO_K750 + if event[1] == 0x10: + status = NO_K750 + device = None + else: + status = OK + charge = ord(event[2][2]) + if event[2][1] == b'\x10': + lux = (ord(event[2][3]) << 8) + ord(event[2][4]) return K750_Status(receiver, device, status, charge, lux) @@ -109,17 +155,18 @@ class StatusThread(threading.Thread): while True: k750 = read_charge(k750.receiver, k750.device) status_changed = k750.status != last_status - gobject.idle_add(update_status_icon, self.status_icon, status_changed, k750) + GObject.idle_add(update_status_icon, self.status_icon, status_changed, k750) last_status = k750.status time.sleep(SLEEP[k750.status]) if __name__ == "__main__": - status_icon = gtk.status_icon_new_from_file('images/icon.png') - status_icon.set_title('Solar') + status_icon = Gtk.StatusIcon.new_from_file('images/icon.png') + status_icon.set_title(TITLE) + status_icon.set_name(TITLE) status_icon.set_tooltip_text('Initializing...') - status_icon.connect("popup_menu", gtk.main_quit) + status_icon.connect("popup_menu", Gtk.main_quit) - gobject.threads_init() + GObject.threads_init() StatusThread(status_icon).start() - gtk.main() + Gtk.main() diff --git a/unittest.sh b/unittest.sh index 26fc6624..84e82a5b 100755 --- a/unittest.sh +++ b/unittest.sh @@ -7,4 +7,4 @@ export PYTHONPATH=$PWD/lib export PYTHONDONTWRITEBYTECODE=true export PYTHONWARNINGS=all -python -m unittest discover -v "$@" +exec python -m unittest discover -v "$@"