--- /dev/null
+
+Alerts
+------
+
+'lock' is the intermediary for alerts.
+
+Alerts are registered by creating a file in /var/run/alert/NAME
+
+Providing 'lock' is running, it reads /etc/alert/NAME and performs the
+actions listed.
+These can be:
+
+ sound:/file/name
+ vibrate:duration,on,off
+ led:name,on,off
+ flash:
+
+Alert sources are:
+
+ SMS - this requires a single alert, no repeats.
+ file should be removed when alert ends.
+ RING - This alert can be actively repeated by changing the file, and
+ can be aborted by removing file.
+
+ ALARM - once only, but can go for longer and get more annoying.
+
+ LOW-BATTERY - single quiet short
+
+If the file still exists when the alert finishes we either
+ - replay the alert if the mtime has changed
+ - delete the file
+
+Other than 'sound' everything has duration, on, off. They are in millisecs
+Duration defaults to the length of the sound, or 5 seconds if there is no sound
+'on' defaults to 500
+'off' defaults to the same as 'on'
+
+names are:
+ everything in '/sys/class/leds' (unique substring works)
+ display
+
+
+When the screen is woken up, everything stops.
+If an alert is issued while the screen is awake, it continues until
+some input.
+
+Different modes/personalities are selected by modes files created in
+/var/lib/personality/XXX
+
+When reading the actions for an alert we read
+ alert
+ alert.XXX for each XXX
+ XXX for each XXX
+
+settings override, so if 'SILENT' contains 'sound:' then when SILENT mode
+is in effect, no sound is made.
+
+So 'alert' serves as a default if no personality setting has been made.
+
+How does this allow for different tones depending on who the caller is?
+Maybe the alert can be NAME.GROUP so SMS.friends
--- /dev/null
+#!/usr/bin/env python
+
+# This software is copyright Neil Brown 2009.
+# It is licensed to you under the terms of the
+# GNU General Public License version 2.
+#
+#
+# blank and lock the screen when there is no activity.
+#
+# We detect activity by watching touch screen, power button,
+# aux button
+# After a period with no activity, we drop brightness to 20%
+# and Grab all devices.
+# Any activity at this stage returns us to 100% and no grab
+# Continued inactivity turns display off. After that, require
+# a button (AUX or Power) to reactivate.
+#
+# If the device seems really idle, we suspend with "apm -s"
+# Other applications can disable this by getting a LOCK_SH
+# lock on /var/run/suspend_disabled
+#
+
+import gobject
+import gtk
+import fcntl
+import os
+import sys
+import struct
+import time
+import suspend
+import dnotify
+import play
+from subprocess import Popen, PIPE
+
+class EvDev:
+ def __init__(self, path, on_event):
+ self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
+ self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
+ self.on_event = on_event
+ self.grabbed = False
+ self.downlist = []
+ def read(self, x, y):
+ try:
+ str = os.read(self.f, 16)
+ except:
+ return True
+
+ if len(str) != 16:
+ return True
+ (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
+ if typ == 0x01:
+ # KEY event
+ if value == 0:
+ # 'up' - remove from downlist
+ if code in self.downlist:
+ self.downlist.remove(code)
+ else:
+ # 'down' - add to downlist
+ if code not in self.downlist:
+ self.downlist.append(code)
+ self.on_event(self.down_count(), typ, code, value)
+ return True
+ def down_count(self):
+ if len(self.downlist) == 0:
+ return 0
+ # double check, we might have missed an 'up' event somehow.
+ try:
+ rv = fcntl.ioctl(self.f, EVIOCGKEY(768/8), (768/8) * " " )
+ l = len(rv)/4
+ ra = struct.unpack('%dI' % l, rv)
+ isup = []
+ for k in self.downlist:
+ by = int(k/8)
+ bt = k % 8
+ if by < l and ((ra[by] >> bt) & 1) == 0:
+ isup.append[k]
+ for k in isup:
+ self.downlist.remove(k)
+ except:
+ pass
+ return len(self.downlist)
+
+ def grab(self):
+ if self.grabbed:
+ return
+ #print "grab"
+ fcntl.ioctl(self.f, EVIOCGRAB, 1)
+ self.grabbed = True
+ def ungrab(self):
+ if not self.grabbed:
+ return
+ #print "release"
+ fcntl.ioctl(self.f, EVIOCGRAB, 0)
+ self.grabbed = False
+
+FBIOBLANK = 0x4611
+FB_BLANK_UNBLANK = 0
+FB_BLANK_POWERDOWN = 4
+
+EVIOCGRAB = 0x40044590
+def EVIOCGKEY(len):
+ return 0x80004518 + len * 65536
+
+
+class SetAlerts:
+ def __init__(self, alertdir, actiondir):
+ # arrange to respond to alerts.
+ # If a name appears in 'alertdir', respond based
+ # on the content of the name name in 'actiondir'.
+ # Currently that must be a WAV file to be played
+ # If the file disappears, the action must stop instantly
+ # If the file is newer when the action completes, it is
+ # restarted
+ self.alertdir = alertdir
+ self.actiondir = actiondir
+ self.watch = dnotify.dir(alertdir)
+ self.active = {}
+ self.watch.watchall(self.runalert)
+
+ def runalert(self):
+ gobject.idle_add(self.alert)
+ return True
+
+ def alert(self):
+ print 'X'
+ # Only look for new entries here.
+ for n in os.listdir(self.alertdir):
+ if n in self.active:
+ continue
+ if n[0] == '.':
+ continue
+ self.add_alert(n)
+ print 'Y'
+ return False
+
+ def add_alert(self, name):
+ print "adding", name
+ self.active[name] = (None, None, None)
+ w = self.watch.watch(name, lambda x : self.runcheck(x, name))
+ if w.ino == 0:
+ del self.active[name]
+ # already disappeared
+ return
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+
+ def runcheck(self, w, name):
+ gobject.idle_add(lambda : self.check(w, name))
+ return True
+ def check(self, w, name):
+ if name not in self.active:
+ print "check", name, "not found"
+ return False
+ print "check", name
+ (w2, a, mtime) = self.active[name]
+ if w.ino == 0:
+ if a:
+ a.finished = True
+ del self.active[name]
+ return False
+ if a == None and w.mtime > mtime + 1:
+ # play back had stopped - start it again
+ print "restart play"
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+ return False
+
+ def action(self, name, a = None):
+ try:
+ fname = os.path.join(self.actiondir, name)
+ if a:
+ a.setfile(fname)
+ else:
+ a = play.Play(fname,
+ done = lambda : self.action_done(name))
+ except:
+ a = None
+ print "action", name, "retuned", a
+ return a
+
+ def action_done(self, name):
+ print name, "done"
+ if name not in self.active:
+ return
+ (w, a, mtime) = self.active[name]
+ if w.ino == 0:
+ del self.active[name]
+ return
+ if w.mtime <= mtime + 3:
+ a = None
+ else:
+ print "play again", w.mtime, mtime
+ # play again
+ a = self.action(name, a)
+ self.active[name] = (w, a, w.mtime)
+
+
+
+class Screen:
+ def __init__(self):
+ self.state = "unknown"
+ f = open("/sys/class/backlight/gta02-bl/max_brightness")
+ self.max = int(f.read())
+ f.close()
+ def bright(self, pcent):
+ b = int(pcent * self.max / 100)
+ f = open("/sys/class/backlight/gta02-bl/brightness","w")
+ f.write("%d\n" % b)
+ f.close()
+ def power(self, ioc):
+ f = open("/dev/fb0", "r+")
+ fcntl.ioctl(f, FBIOBLANK, ioc)
+ f.close()
+ def on(self):
+ if self.state != "on":
+ self.power(FB_BLANK_UNBLANK)
+ self.state = "on"
+ self.bright(100)
+ def dim(self):
+ if self.state != "on":
+ self.power(FB_BLANK_UNBLANK)
+ self.state = "on"
+ self.bright(20)
+ def off(self):
+ self.bright(0)
+ if self.state != "off":
+ self.power(FB_BLANK_POWERDOWN)
+ self.state = "off"
+
+def grab_all():
+ global screen, power, aux
+ screen.grab()
+ power.grab()
+ aux.grab()
+
+def release_all():
+ global screen, power, aux
+ screen.ungrab()
+ power.ungrab()
+ aux.ungrab()
+
+timeout = None
+state = "full"
+dimtime = 15
+enable_suspend = True
+def set_timeout():
+ global timeout
+ global state, enable_suspend
+ global dimtime
+ #print "set timeout for", state
+ if timeout != None:
+ gobject.source_remove(timeout)
+ if state == "full":
+ timeout = gobject.timeout_add(dimtime*1000, set_dim)
+ elif state == "dim":
+ timeout = gobject.timeout_add(10*1000, set_off)
+ elif state == "insta-lock":
+ timeout = gobject.timeout_add(1000, set_off)
+ elif state == "off" and enable_suspend:
+ timeout = gobject.timeout_add(30*1000, set_suspend)
+
+ set_ico_file()
+
+def set_dim():
+ global state
+ global aux, power, screen
+ if aux.down_count() + power.down_count() + screen.down_count() > 0:
+ # button still down
+ set_timeout()
+ return
+ display.dim()
+ grab_all()
+ state = "dim"
+ set_timeout()
+
+def set_off():
+ global state, dimtime
+ display.off()
+ state = "off"
+ set_timeout()
+ dimtime = 15
+
+def read_num(filename, default = 0, sep = None):
+ try:
+ f = open(filename)
+ except:
+ f = [ "%d" % default ]
+ num = default
+ for l in f:
+ l = l.split(sep)[0].strip()
+ try:
+ num = float(l)
+ break
+ except:
+ num = default
+ return num
+
+#Active Internet connections (w/o servers)
+#Proto Recv-Q Send-Q Local Address Foreign Address State
+#tcp 0 48 192.168.2.202:22 192.168.2.200:34244 ESTABLISHED
+#tcp 0 0 192.168.2.202:22 192.168.2.200:41473 ESTABLISHED
+def external_tcp():
+ # check for TCP connections to external computers.
+ netstat = Popen(['netstat','-nt'], stdout = PIPE, close_fds = True)
+ for l in netstat.stdout:
+ l = l.strip()
+ f = l.split()
+ if f[0] != "tcp" or f[5] != 'ESTABLISHED':
+ continue
+ a = f[4].split(':')
+ if a[0] != "127.0.0.1":
+ return True
+ return False
+
+def set_suspend():
+ global state, display
+ # Check for lock file
+ try:
+ f = open("/var/run/suspend_disabled", "r+")
+ except:
+ f = None
+ if f:
+ try:
+ fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except:
+ set_timeout()
+ return
+
+ # Check for loadavg > 0.5
+ load = read_num("/proc/loadavg")
+ if load > 0.5:
+ set_timeout()
+ return
+
+ # Check for USB power, at least 500mA
+ current = read_num("/sys/class/i2c-adapter/i2c-0/0-0073/pcf50633-mbc/usb_curlim")
+ if current >= 500:
+ set_timeout()
+ return
+
+ #if external_tcp():
+ # set_timeout()
+ # return
+
+ os.system("apm -s")
+ if f:
+ f.close()
+ rr = suspend.resume_reason()
+ if rr == 'PMU power-button-down':
+ wake_all(0)
+ return
+
+ state = "off"
+ set_timeout()
+
+
+def wake_all(down_cnt, *rest):
+ global state, dimtime
+ #print "wake all"
+ display.on()
+ if down_cnt == 0:
+ release_all()
+ if state == "dim" and dimtime < 120:
+ dimtime += 15
+ if state != "disable":
+ state = "full"
+ set_timeout()
+
+def wake_dim(down_cnt, *rest):
+ global state
+ #print "wake_dim"
+ if state == "dim" or state == "full":
+ wake_all(down_cnt)
+
+
+ico_file = None
+def set_ico_file():
+ global state, ico_file, ico
+ if state == "disable":
+ file = "lock-no.png"
+ elif state == "full":
+ if enable_suspend:
+ file = "lock-un.png"
+ else:
+ file = "lock-un-nosusp.png"
+ else:
+ if enable_suspend:
+ file = "lock.png"
+ else:
+ file = "lock-nosusp.png"
+ if file != ico_file:
+ ico.set_from_file("/usr/local/pixmaps/" + file)
+ ico_file = file
+
+last_ping = 0
+prev_ping = 0
+def ping(icon):
+ global state, enable_suspend, last_ping, prev_ping
+ if time.time() - prev_ping < 0.8:
+ grab_all()
+ state = "insta-lock"
+ elif state == "disable":
+ enable_suspend = False
+ state = "full"
+ elif not enable_suspend:
+ enable_suspend = True
+ state = "full"
+ else:
+ state = "disable"
+ prev_ping = last_ping
+ last_ping = time.time()
+ set_timeout()
+
+def main():
+ global display, ico
+ global screen, power, aux
+ aux = EvDev("/dev/input/event4", wake_all)
+ power = EvDev("/dev/input/event0", wake_all)
+ screen = EvDev("/dev/input/event1", wake_dim)
+ alert = SetAlerts("/var/run/alert", "/etc/alert")
+ state = "full"
+ display = Screen()
+ display.on()
+ ico = gtk.StatusIcon()
+ set_timeout()
+ ico.connect("activate", ping)
+
+ gtk.main()
+
+main()
import sys
import struct
import time
+import suspend
+import dnotify
+
from subprocess import Popen, PIPE
class EvDev:
if len(str) != 16:
return True
- (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
+ (sec,usec,typ,code,value) = struct.unpack_from("IIHHi", str)
if typ == 0x01:
# KEY event
if value == 0:
else:
# 'down' - add to downlist
if code not in self.downlist:
- self.downlist.append(code)
- self.on_event(self.down_count(), typ, code, value)
+ # ignore the 'play' butten (headphone insert)
+ if code != 207:
+ self.downlist.append(code)
+ moment = sec + float(usec)/1000000
+ self.on_event(self.down_count(), moment, typ, code, value)
return True
def down_count(self):
if len(self.downlist) == 0:
def EVIOCGKEY(len):
return 0x80004518 + len * 65536
+def sysfs_write(file, val):
+ try:
+ f = open(file, "w")
+ f.write(val)
+ f.close()
+ except:
+ pass
+
+vib_timer = None
+def set_vibrate(on, off, total):
+ global vib_timer
+ if vib_timer:
+ return
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+ sysfs_write(vib+"/trigger", "timer")
+ sysfs_write(vib+"/delay_on", "%d"%on)
+ sysfs_write(vib+"/delay_off", "%d"%off)
+ vib_timer = gobject.timeout_add(total, clear_vibrator)
+def clear_vibrator():
+ global vib_timer
+ if vib_timer:
+ gobject.source_remove(vib_timer)
+ vib_timer = None
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+
+
+class SetAlerts:
+ def __init__(self, alertdir, actiondir):
+ # arrange to respond to alerts.
+ # If a name appears in 'alertdir', respond based
+ # on the content of the same name in 'actiondir'.
+ # Currently that must be a WAV file to be played
+ # If the file disappears, the action must stop instantly
+ # If the file is newer when the action completes, it is
+ # restarted
+ self.alertdir = alertdir
+ self.actiondir = actiondir
+ self.watch = dnotify.dir(alertdir)
+ self.active = {}
+ self.watch.watchall(self.runalert)
+ self.pref = "normal"
+
+ def setpref(self, str):
+ self.pref = str
+
+ def runalert(self):
+ gobject.idle_add(self.alert)
+ return True
+
+ def alert(self):
+ # Only look for new entries here.
+ for n in os.listdir(self.alertdir):
+ if n in self.active:
+ continue
+ if n[0] == '.':
+ continue
+ self.add_alert(n)
+ return False
+
+ def add_alert(self, name):
+ print "adding", name
+ self.active[name] = (None, None, None)
+ w = self.watch.watch(name, lambda x : self.runcheck(x, name))
+ if w.ino == 0:
+ del self.active[name]
+ # already disappeared
+ return
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+
+ def runcheck(self, w, name):
+ gobject.idle_add(lambda : self.check(w, name))
+ return True
+ def check(self, w, name):
+ if name not in self.active:
+ print "check", name, "not found"
+ return False
+ print "check", name
+ (w2, a, mtime) = self.active[name]
+ if w.ino == 0:
+ if a:
+ self.stop(a)
+ del self.active[name]
+ return False
+ #if a and not os.path.exists(a):
+ # a = None
+ #if a == None and w.mtime > mtime + 1:
+ # # play back had stopped - start it again
+ # print "restart play"
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+ return False
+
+ def action(self, name):
+ n = '/var/run/sound/14R-ring'+name
+ try:
+ os.symlink(os.path.join(self.actiondir, self.pref, name), n)
+ except:
+ pass
+ set_vibrate(200,400,1800)
+ if display.state != 'on':
+ set_dim()
+ return n
+ def stop(self, name):
+ try:
+ os.unlink(name)
+ except:
+ pass
+
+ def stopall(self):
+ for n in self.active:
+ w, a, w.time = self.active[n]
+ self.stop(a)
+ self.active[n] = (w, None, 0)
+
class Screen:
def __init__(self):
self.state = "unknown"
self.state = "off"
def grab_all():
- global screen, power, aux
+ global screen, power, aux, accel
screen.grab()
power.grab()
aux.grab()
+ accel.grab()
def release_all():
- global screen, power, aux
+ global screen, power, aux, accel
screen.ungrab()
power.ungrab()
aux.ungrab()
+ accel.ungrab()
+
+# wall-clock might get reset when we wake from suspend, so
+# use a monotonic clock to guard against early blanking.
+import ctypes
+
+CLOCK_MONOTONIC = 1 # not sure about this one, check the headers
+
+class timespec(ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ ]
+
+librt = ctypes.CDLL('librt.so.1')
+clock_gettime = librt.clock_gettime
+clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
+
+def get_ticks():
+ t = timespec()
+ clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t))
+ return t.tv_sec + t.tv_nsec / 1e9
+
+
timeout = None
state = "full"
dimtime = 15
enable_suspend = True
+last_set = 0
def set_timeout():
global timeout
global state, enable_suspend
- global dimtime
+ global dimtime, last_set
+
+ last_set = get_ticks()
#print "set timeout for", state
if timeout != None:
gobject.source_remove(timeout)
timeout = gobject.timeout_add(dimtime*1000, set_dim)
elif state == "dim":
timeout = gobject.timeout_add(10*1000, set_off)
- elif state == "insta-lock":
- timeout = gobject.timeout_add(1000, set_off)
elif state == "off" and enable_suspend:
timeout = gobject.timeout_add(30*1000, set_suspend)
set_ico_file()
def set_dim():
- global state
+ global state, last_set
global aux, power, screen
if aux.down_count() + power.down_count() + screen.down_count() > 0:
# button still down
set_timeout()
return
+ if get_ticks() - last_set < dimtime - 1:
+ # if delay was too quick, try again
+ set_timeout()
+ return
display.dim()
grab_all()
state = "dim"
def set_off():
global state, dimtime
+ global last_set
+ if get_ticks() - last_set < 9.5:
+ set_timeout()
+ return
display.off()
state = "off"
set_timeout()
global state, display
# Check for lock file
try:
- f = open("/var/run/suspend_disabled", "r+")
+ f = open("/var/run/suspend_disabled", "w+")
except:
f = None
if f:
fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except:
set_timeout()
+ f.close()
return
# Check for loadavg > 0.5
- load = read_num("/proc/loadavg")
- if load > 0.5:
- set_timeout()
- return
+ #load = read_num("/proc/loadavg")
+ #if load > 0.5:
+ # set_timeout()
+ # return
# Check for USB power, at least 500mA
+ # If powered and network connection then leave awake.
current = read_num("/sys/class/i2c-adapter/i2c-0/0-0073/pcf50633-mbc/usb_curlim")
- if current >= 500:
+ if current >= 500 and external_tcp():
set_timeout()
return
- #if external_tcp():
- # set_timeout()
- # return
-
os.system("apm -s")
if f:
f.close()
- state = "full"
+ rr = suspend.resume_reason()
+ if rr == 'PMU power-button-down':
+ wake_all(0)
+ return
+
+ state = "off"
set_timeout()
- display.on()
+
def wake_all(down_cnt, *rest):
- global state, dimtime
+ global state, dimtime, alert
#print "wake all"
+ alert.stopall()
display.on()
if down_cnt == 0:
release_all()
if state == "dim" or state == "full":
wake_all(down_cnt)
+EV_SYN = 0
+EV_KEY = 1
+EV_ABS = 3
+ABS_X = 0
+ABS_Y = 1
+ABS_Z = 2
+BTN_X = 307
+BTN_Y = 308
+BTN_Z = 309
+
+shake_cnt = 0
+shake_time = 0
+shake_seen = 0
+invert_timer = 0
+def check_attitude(down_cnt, moment, typ, code, value):
+ global state
+ global shake_cnt, shake_time, shake_seen
+ if moment - shake_time > 0.4:
+ shake_cnt = 0
+ if typ == EV_ABS and abs(value) > 1500:
+ shake_time = moment
+ shake_seen = 1
+ if typ == EV_SYN and shake_seen:
+ shake_cnt += 1
+ shake_seen = 0
+ if typ == EV_ABS and code == ABS_Y and value > 100:
+ shake_cnt = 0
+ if shake_cnt >= 3:
+ shake_cnt = 0
+ #no wake_all(0)
+ #no return
+
+ if typ == EV_KEY and code <= BTN_Z and code >= BTN_X and value == 1:
+ wake_dim(0)
+
+ global invert_timer
+ if typ == EV_ABS and code == ABS_Y and value > 500:
+ # upside down - need this for 0.6 seconds
+ #print "down", moment
+ if invert_timer == 0:
+ invert_timer = gobject.timeout_add(400, attitude_confirm)
+ elif typ == EV_ABS and code == ABS_Y:
+ #print "up", moment
+ if invert_timer:
+ gobject.source_remove(invert_timer)
+ invert_timer = 0
+
+def attitude_confirm():
+ global invert_timer, state
+ # seem to have been inverted for a while
+ invert_timer = 0
+ if state != "off":
+ display.off()
+ grab_all()
+ state = "off"
+ set_timeout()
+ return False
ico_file = None
def set_ico_file():
prev_ping = 0
def ping(icon):
global state, enable_suspend, last_ping, prev_ping
- if time.time() - prev_ping < 0.8:
- grab_all()
- state = "insta-lock"
- elif state == "disable":
+ if state == "disable":
enable_suspend = False
state = "full"
elif not enable_suspend:
last_ping = time.time()
set_timeout()
+def setfile(name, val):
+ f = open(name, 'w')
+ f.write(val)
+ f.close()
+
def main():
global display, ico
- global screen, power, aux
+ global screen, power, aux, accel
+ global alert
aux = EvDev("/dev/input/event4", wake_all)
power = EvDev("/dev/input/event0", wake_all)
screen = EvDev("/dev/input/event1", wake_dim)
+
+ alert = SetAlerts("/var/run/alert", "/etc/alert")
+ setfile('/sys/bus/spi/devices/spi3.1/threshold', '500')
+ setfile('/sys/bus/spi/devices/spi3.1/sample_rate','0')
+ setfile('/sys/bus/spi/devices/spi3.1/taps', '7000 5')
+ accel = EvDev("/dev/input/event3", check_attitude)
state = "full"
display = Screen()
display.on()