blob: 0e4a9139b9d390678792a9935edfd1366dc19552 [file] [log] [blame]
#!/usr/bin/env python -u
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''
Routines to modify keyboard LED state.
'''
import fcntl
import logging
import os
import sys
import threading
import factory_common
from autotest_lib.client.bin import utils
# Constants from /usr/include/linux/kd.h.
KDSETLED = 0x4B32
LED_SCR = 1
LED_NUM = 2
LED_CAP = 4
# Resets LEDs to the default values (console_ioctl.h says that any higher-
# order bit than LED_CAP will do).
LED_RESET = 8
# Number of VTs on which to set the keyboard LEDs.
MAX_VTS = 8
# Set of FDs of all TTYs on which to set LEDs. Lazily initialized by SetLeds.
_tty_fds = None
_tty_fds_lock = threading.Lock()
def SetLeds(state):
'''
Sets the current LEDs on VTs [0,MAX_VTS) to the given state. Errors
are ignored.
(We set the LED on all VTs because /dev/console may not work reliably under
the combination of X and autotest.)
Args:
pattern: A bitwise OR of zero or more of LED_SCR, LED_NUM, and LED_CAP.
'''
global _tty_fds
with _tty_fds_lock:
if _tty_fds is None:
_tty_fds = []
for tty in xrange(MAX_VTS):
dev = '/dev/tty%d' % tty
try:
_tty_fds.append(os.open(dev, os.O_RDWR))
except:
logging.exception('Unable to open %s' % dev)
for fd in _tty_fds:
try:
fcntl.ioctl(fd, KDSETLED, state)
except:
pass
class Blinker(object):
'''
Blinks LEDs asynchronously according to a particular pattern.
Start() and Stop() are not thread-safe and must be invoked from the same
thread.
This can also be used as a context manager:
with leds.Blinker(...):
...do something that will take a while...
'''
thread = None
def __init__(self, pattern):
'''
Constructs the blinker (but does not start it).
Args:
pattern: A list of tuples. Each element is (state, duration),
where state contains the LEDs that should be lit (a bitwise
OR of LED_SCR, LED_NUM, and/or LED_CAP). For example,
((LED_SCR|LED_NUM|LED_CAP, .2),
(0, .05))
would turn all LEDs on for .2 s, then all off for 0.05 s,
ad infinitum.
'''
self.pattern = pattern
self.done = threading.Event()
def Start(self):
'''
Starts blinking in a separate thread until Stop is called.
May only be invoked once.
'''
assert not self.thread
self.thread = threading.Thread(target=self._Run)
self.thread.start()
def Stop(self):
'''
Stops blinking.
'''
self.done.set()
if self.thread:
self.thread.join()
self.thread = None
def __enter__(self):
self.Start()
def __exit__(self, type, value, traceback):
self.Stop()
def _Run(self):
while True: # Repeat pattern forever
for state, duration in self.pattern:
SetLeds(state)
self.done.wait(duration)
if self.done.is_set():
SetLeds(LED_RESET)
return
if __name__ == '__main__':
'''
Blinks the pattern in sys.argv[1] if peresent, or the famous theme from
William Tell otherwise.
'''
if len(sys.argv) > 1:
blinker = Blinker(eval(sys.argv[1]))
else:
DURATION_SCALE = .125
def Blip(state, duration=1):
return [(state, duration * .6 * DURATION_SCALE),
(0, duration * .4 * DURATION_SCALE)]
blinker = Blinker(
2*(2*Blip(LED_NUM) + Blip(LED_NUM, 2)) + 2*Blip(LED_NUM) +
Blip(LED_CAP, 2) + Blip(LED_SCR, 2) + Blip(LED_CAP|LED_SCR, 2) +
2*Blip(LED_NUM) + Blip(LED_NUM, 2) + 2*Blip(LED_NUM) +
Blip(LED_CAP, 2) + 2*Blip(LED_SCR) + Blip(LED_CAP, 2) +
Blip(LED_NUM, 2) + Blip(LED_CAP|LED_NUM, 2)
)
with blinker:
# Wait for newline, and then quit gracefully
sys.stdin.readline()