brought solar app up-to-date with the UR api

This commit is contained in:
Daniel Pavel 2012-09-25 17:41:40 +03:00
parent fc0a0ca2bb
commit ebe8320f2e
6 changed files with 96 additions and 34 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
*.so *.so
*.pyc *.pyc
*.pyo
*.log

View File

@ -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]) & 0xF0) >> 4) +
str(ord(fw_info[5]) & 0x0F)) str(ord(fw_info[5]) & 0x0F))
name = prefix + ' ' + version 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: if build:
name += ' b' + str(build) name += ' b' + str(build)
extras = fw_info[9:].rstrip('\x00') 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) _l.log(_LOG_LEVEL, "(%d,%d) device name %s", handle, device, d_name)
return d_name return d_name
def get_device_battery_level(handle, device, features_array=None): def get_device_battery_level(handle, device, features_array=None):
"""Reads a device's battery level. """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) battery = request(handle, device, FEATURE.BATTERY, features_array=features_array)
if battery: if battery:

View File

@ -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)) _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): if not _hid.write(handle, wdata):
_l.warn("(%d,%d) write failed, assuming receiver no longer available", handle, device) _l.warn("(%d,%d) write failed, assuming receiver no longer available", handle, device)
close(handle)
raise NoReceiver() raise NoReceiver()
@ -210,7 +211,7 @@ def request(handle, device, feature_index_function, params=b'', features_array=N
if reply_device != device: if reply_device != device:
# this message not for the device we're interested in # 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 # worst case scenario, this is a reply for a concurrent request
# on this receiver # on this receiver
_unhandled._publish(reply_code, reply_device, reply_data) _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: if reply_code == 0x11 and reply_data[0] == b'\xFF' and reply_data[1:3] == feature_index_function:
# an error returned from the device # an error returned from the device
error_code = ord(reply_data[3]) 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_index = ord(feature_index_function[0])
feature_function = feature_index_function[1].encode('hex') feature_function = feature_index_function[1].encode('hex')
feature = None if features_array is None else features_array[feature_index] feature = None if features_array is None else features_array[feature_index]

9
solar Executable file
View File

@ -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 "$@"

107
solar.py
View File

@ -1,28 +1,35 @@
#!/usr/bin/env python
import logging
logging.basicConfig(level=1)
logging.captureWarnings(True)
import time import time
import threading import threading
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
import gobject from gi.repository import GObject
import pygtk from gi.repository import Gtk
pygtk.require('2.0')
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' KEYBOARD_NAME = 'Wireless Solar Keyboard K750'
TITLE = 'Solar [K750]' TITLE = 'Solar [K750]'
NOTIFY_DESKTOP = True NOTIFY_DESKTOP = True
BLINK_ICON = 3
OK = 0 OK = 0
NO_RECEIVER = 1 NO_RECEIVER = 1
NO_K750 = 2 NO_K750 = 2
NO_STATUS = 3 NO_STATUS = 3
SLEEP = (10, 30, 25, 15) SLEEP = (10, 5, 5, 15)
TEXT = ('K750 keyboard connected', TEXT = ('K750 keyboard connected',
'Logitech Unifying Receiver not detected', 'Logitech Unifying Receiver not detected',
'K750 keyboard not detected', 'K750 keyboard not detected',
@ -38,17 +45,39 @@ K750_Status = namedtuple('K750_Status',
['receiver', 'device', 'status', 'charge', 'lux']) ['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): def notify_desktop(status_code, text):
global NOTIFY_DESKTOP global NOTIFY_DESKTOP
if NOTIFY_DESKTOP: if NOTIFY_DESKTOP:
try: try:
program = ('/usr/bin/notify-send', '-u', 'low', TITLE, text) subprocess.call(('notify-send', '-u', 'low', TITLE, text))
subprocess.Popen(program, close_fds=True)
except OSError: except OSError:
NOTIFY_DESKTOP = False NOTIFY_DESKTOP = False
def update_status_icon(status_icon, status_changed, k750): def update_status_icon(status_icon, status_changed, k750):
print "update status", status_changed, k750
text = TEXT[k750.status] text = TEXT[k750.status]
if k750.status == OK: if k750.status == OK:
text += '\n' + (CHARGE_LUX_TEXT % (k750.charge, k750.lux)) 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: if status_changed:
notify_desktop(k750.status, text) 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): def read_charge(receiver, device):
@ -72,26 +97,47 @@ def read_charge(receiver, device):
if receiver and not device: if receiver and not device:
try: try:
device = ur.find_device(receiver, "keyboard", KEYBOARD_NAME) device = ur.find_device_by_name(receiver, KEYBOARD_NAME)
except ur.NoReceiver: except ur.NoReceiver:
ur.close(receiver)
receiver = None receiver = None
if receiver: if receiver and device:
if 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: try:
charge_lux = ur.get_solar_charge(receiver, device) reply = ur.request(receiver, device.number, ur.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01', features_array=device.features_array)
if charge_lux is None: if reply is None:
status = NO_K750
device = None device = None
status = NO_STATUS
else: else:
charge, lux = charge_lux return read_charge(receiver, device)
status = OK
except ur.NoReceiver: except ur.NoReceiver:
ur.close(receiver)
receiver = None receiver = None
device = None
else: 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) return K750_Status(receiver, device, status, charge, lux)
@ -109,17 +155,18 @@ class StatusThread(threading.Thread):
while True: while True:
k750 = read_charge(k750.receiver, k750.device) k750 = read_charge(k750.receiver, k750.device)
status_changed = k750.status != last_status 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 last_status = k750.status
time.sleep(SLEEP[k750.status]) time.sleep(SLEEP[k750.status])
if __name__ == "__main__": if __name__ == "__main__":
status_icon = gtk.status_icon_new_from_file('images/icon.png') status_icon = Gtk.StatusIcon.new_from_file('images/icon.png')
status_icon.set_title('Solar') status_icon.set_title(TITLE)
status_icon.set_name(TITLE)
status_icon.set_tooltip_text('Initializing...') 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() StatusThread(status_icon).start()
gtk.main() Gtk.main()

View File

@ -7,4 +7,4 @@ export PYTHONPATH=$PWD/lib
export PYTHONDONTWRITEBYTECODE=true export PYTHONDONTWRITEBYTECODE=true
export PYTHONWARNINGS=all export PYTHONWARNINGS=all
python -m unittest discover -v "$@" exec python -m unittest discover -v "$@"