Oops, some previously added files not added to svn (I hate it when that
[rox-lithium.git] / acpi.py
blob383caf5986326d44190c79d231d05ae091fb0484
1 ##############################################################################
2 ##
3 ## $Id: acpi.py,v 1.23 2003/12/15 20:04:09 riemer Exp $
4 ##
5 ## Copyright (C) 2002-2003 Tilo Riemer <riemer@lincvs.org>,
6 ## Luc Sorgue <luc.sorgue@laposte.net> and
7 ## Rds <rds@rdsarts.com>
8 ## All rights reserved.
9 ##
10 ## Redistribution and use in source and binary forms, with or without
11 ## modification, are permitted provided that the following conditions
12 ## are met:
14 ## 1. Redistributions of source code must retain the above copyright
15 ## notice, this list of conditions and the following disclaimer.
16 ## 2. Redistributions in binary form must reproduce the above copyright
17 ## notice, this list of conditions and the following disclaimer in the
18 ## documentation and/or other materials provided with the distribution.
19 ## 3. The name of the author may not be used to endorse or promote products
20 ## derived from this software without specific prior written permission.
22 ## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ###############################################################################
35 import os,stat,sys
38 # genenal constants ###########################################################
40 #enums
42 VERSION = "0.3.0"
44 OFFLINE = 0
45 ONLINE = 1
46 CHARGING = 2 #implies ONLINE
47 FAN_OFF = 0
48 FAN_ON = 1
52 # exceptions ##################################################################
54 #exception enums
55 ERR_GENERIC = -1
56 ERR_NO_DEVICE = -2
57 ERR_NOT_IMPLEMENTED = -3
58 ERR_NO_LOW_LEVEL = -4
59 ERR_CONFIGURATION_CHANGED = -5 #reload module? or function reconfigure?
60 ERR_NOT_ALLOWED = -6 #access for some resource not allowed --> better idea?
62 class AcpiError(Exception):
63 """ACPI exceptions"""
65 def __init__(self, errno):
66 self.errno = errno
68 def __str__(self):
69 if self.errno == ERR_GENERIC:
70 return "Any ACPI error occured."
71 elif self.errno == ERR_NO_DEVICE:
72 return "ACPI is not configured on this host."
73 elif self.errno == ERR_NOT_IMPLEMENTED:
74 return "No implementation for this operating system."
75 elif self.errno == ERR_NO_LOW_LEVEL:
76 return "Acpi_lowlevel module not found."
77 elif self.errno == ERR_CONFIGURATION_CHANGED:
78 return "ACPI configuartion has been changed."
79 else:
80 return "Unknown error occured."
84 # interface ###################################################################
86 class Acpi:
87 """Interface class for ACPI"""
89 def __init__(self):
90 res = sys.platform
91 if res.find("freebsd4") > -1:
92 self.acpi = None #throw exception
93 raise NotImplemented
95 elif res.find("netbsd1") > -1:
96 self.acpi = None #throw exception
97 raise NotImplemented
99 elif res.find("linux2") > -1:
100 self.acpi = AcpiLinux()
102 elif res.find("linux") > -1:
103 #some systems return linux instead of linux2. We should
104 #show a warning or check by ourselves for Linux2
105 self.acpi = AcpiLinux()
107 else:
108 self.acpi = None #throw exception (os unknown)
109 raise NotImplemented
112 def identity(self):
113 """Returns the identity of this module"""
114 return "acpi.py"
116 def version(self):
117 """Returns the version of this module"""
118 return VERSION
120 def update(self):
121 """Updates the ACPI state"""
122 self.acpi.update()
124 def percent(self):
125 """Returns percentage capacity of all batteries"""
126 return self.acpi.percent()
128 def capacity(self):
129 """Returns capacity of all batteries (in mWh)"""
130 return self.acpi.capacity()
132 def nb_of_batteries(self):
133 """Returns the number of batteries"""
134 return self.acpi.nb_of_batteries()
136 def charging_state(self):
137 """Returns ac state (off-/online/charging)"""
138 return self.acpi.charging_state()
140 def estimated_lifetime(self):
141 """Returns Estimated Lifetime as real number"""
142 return self.acpi.estimated_lifetime()
144 def temperature(self, idx):
145 """Returns Processor Temperature"""
146 return self.acpi.temperature(idx)
148 def fan_state(self, idx):
149 """Returns fan states"""
150 return self.acpi.fan_state(idx)
153 #unfinished: don't use it or better send us improvements ;-)
154 def frequency(self, idx):
155 """ Return the frequency of the processor"""
156 return self.acpi.frequency(idx)
158 def performance_states(self, idx):
159 """ Return a list of available frequencies for the proc """
160 return self.acpi.performance_states(idx)
162 def set_frequency(self, f):
163 """ Set the processor frequency - Warning ! Needs root privileges to work """
164 return self.acpi.set_frequency(f)
168 # implementation for Linux ####################################################
170 class AcpiLinux:
171 def __init__(self):
172 """init ACPI class and check for any ACPI features in /proc/acpi/"""
174 #we read all acpi stuff from here
175 self.proc_acpi_dir = "/proc/acpi"
177 if not os.access(self.proc_acpi_dir, os.F_OK):
178 raise EnvironmentError
180 self.init_batteries()
181 self.init_fans()
182 #self.init_processors()
183 self.init_temperatures()
185 self.update()
188 def update(self):
189 """Read current states of supported acpi components"""
191 self.update_batteries()
192 self.update_fans()
193 #self.update_processors()
194 self.update_temperatures()
196 # battery related functions
197 def init_batteries(self):
198 """Checks for and initializes the batteries"""
200 self.proc_battery_dir = self.proc_acpi_dir + "/battery"
202 # empty lists implies no battery, no capacity etc.
203 self.design_capacity = {}
204 self.life_capacity = {}
205 self.present_rate = {}
207 # empty list of battery sub directories; implies no batteries available
208 self.battery_dir_entries = []
210 try:
211 battery_dir_entries = os.listdir(self.proc_battery_dir)
212 except OSError:
213 self.ac_line_state = ONLINE # no batteries: we assume that a cable is plugged in ;-)
214 return #nothing more to do
217 try:
218 for i in battery_dir_entries:
219 mode = os.stat(self.proc_battery_dir + "/" + i)[stat.ST_MODE]
220 if stat.S_ISDIR(mode):
221 self.battery_dir_entries.append(i)
222 except OSError:
223 # the battery module is not correctly loaded, or is broken.
224 # currently self.battery_dir_entries has no batteries or only
225 # the batteries which we could stat
226 # because the appended dirs should be okay we do not return here
227 pass
229 self.ac_line_state = OFFLINE
231 #later: the newer acpi versions seems to generate always two BAT dirs...
232 #check info for present: no
233 try:
234 for i in self.battery_dir_entries:
235 #print self.proc_battery_dir + "/" + i + "/info"
236 info_file = open(self.proc_battery_dir + "/" + i + "/info")
237 line = info_file.readline()
239 while len(line) != 0:
240 if line.find("design capacity:") == 0:
241 cap = line.split(":")[1].strip()
242 try:
243 self.design_capacity[i] = int(cap.split("m")[0].strip())
244 except ValueError:
245 #no value --> conversion to int failed
246 self.design_capacity[i] = 0
248 line = info_file.readline()
249 info_file.close()
250 except IOError:
251 #print "No batt info found."
252 # the battery module is not correctly loaded... the file info should exist.
253 # wipe out all lists --> no battery infos
254 self.battery_dir_entries = []
255 self.design_capacity = {}
256 self.life_capacity = {}
257 self.present_rate = {}
260 def update_batteries(self):
261 """Read current state of batteries"""
263 try:
264 for i in self.battery_dir_entries:
265 state_file = open(self.proc_battery_dir + "/" + i + "/state")
266 line = state_file.readline()
268 while len(line) != 0:
269 if line.find("remaining capacity") == 0:
270 cap = line.split(":")[1].strip()
271 try:
272 self.life_capacity[i] = int(cap.split("m")[0].strip())
273 except ValueError:
274 self.life_capacity[i] = 0
276 # it's possible that in battery/*/info the charging state is unknown
277 # --> then we must check ac_state...
278 # iterating over all batteries this way is not smart. better implementation needed
279 # better are funcs for capacity, acstate and prrate
281 # a little bit tricky... if loading of ac driver fails, we cant use info
282 # from /proc/ac_*/...
283 # if information in /proc/acpi/battery/*/state is wrong we had to
284 # track the capacity history.
285 # I assume that all battery state files get the same state.
286 if line.find("charging state") == 0:
287 state = line.split(":")[1].strip()
288 if state == "discharging":
289 self.ac_line_state = OFFLINE
290 elif state == "charging":
291 self.ac_line_state = CHARGING
292 else:
293 self.ac_line_state = ONLINE
295 # Read the present energy consumption to
296 # estimate life time
298 if line.find("present rate:") == 0:
299 try:
300 pr_rate = float(line.split(":")[1].strip().split("m")[0].strip())
301 except ValueError:
302 pr_rate = 0
304 self.present_rate[i] = pr_rate
306 line = state_file.readline()
307 state_file.close()
308 except IOError:
309 raise AcpiError, ERR_CONFIGURATION_CHANGED
311 # maybe we should restart init_batteries instead of generating an error ?
312 # the user may have unplugged the battery.
313 #init_batteries()
314 # I prefer raising an exception because we would run into a recursion of
315 # member funcs what is not a good idea.
316 # the case that this error occurs should be very rare
319 def init_temperatures(self):
320 """Initializes temperature stuff"""
322 self.proc_thermal_dir = self.proc_acpi_dir + "/thermal_zone"
324 # empty list implies no thermal feature supported
325 self.temperatures = {}
327 # empty list of thermal sub directories; implies no thermal infos available
328 self.thermal_dir_entries = []
330 try:
331 thermal_dir_entries = os.listdir(self.proc_thermal_dir)
332 except OSError:
333 return #nothing more to do
335 try:
336 for i in thermal_dir_entries:
337 mode = os.stat(self.proc_thermal_dir + "/" + i)[stat.ST_MODE]
338 if stat.S_ISDIR(mode):
339 self.thermal_dir_entries.append(i)
340 except OSError:
341 # the thermal module is not correctly loaded, or is broken.
342 # because the appended dirs should be okay we do not return here
343 pass
346 def update_temperatures(self):
347 """Read current temperatures"""
349 try:
350 for i in self.thermal_dir_entries:
351 file = open(self.proc_thermal_dir + "/" + i + "/temperature")
352 line = file.readline()
353 while len(line) != 0:
354 if line.find("temperature") == 0:
355 self.temperatures[i] = line.split(":")[1].strip()
356 line = file.readline()
357 file.close()
358 except IOError:
359 raise AcpiError,ERR_CONFIGURATION_CHANGED
362 def init_fans(self):
363 """Initialize fans"""
365 self.proc_fan_dir = self.proc_acpi_dir + "/fan"
367 self.fans = {}
369 # empty list of fan sub directories; implies no fan infos available
370 self.fan_dir_entries = []
372 try:
373 fan_dir_entries = os.listdir(self.proc_fan_dir)
374 except OSError:
375 return #nothing more to do
377 try:
378 for i in fan_dir_entries:
379 mode = os.stat(self.proc_fan_dir + "/" + i)[stat.ST_MODE]
380 if stat.S_ISDIR(mode):
381 self.fan_dir_entries.append(i)
382 except OSError:
383 # the fan module is not correctly loaded, or is broken.
384 # because the appended dirs should be okay we do not return here
385 pass
388 def update_fans(self):
389 """Read current state of fans"""
391 try:
392 for i in self.fan_dir_entries:
393 file = open(self.proc_fan_dir + "/" + i + "/state")
394 line = file.readline()
395 while len(line) != 0:
396 if line.find("status") == 0:
397 if line.split(":")[1].strip() == 'on':
398 self.fans[i] = FAN_ON
399 else:
400 self.fans[i] = FAN_OFF
401 line = file.readline()
402 file.close()
403 except IOError:
404 raise AcpiError,ERR_CONFIGURATION_CHANGED
407 def init_processors(self):
408 """Initialize processors"""
410 self.proc_processor_dir = self.proc_acpi_dir + "/processor"
412 # TODO: adapt it for multiple CPUs --> we need a matrix instead of a vector!!!
413 self.perf_states = {} #empty list implies no processor support
415 # empty list of processor sub directories; implies no processor infos available
416 self.processor_dir_entries = []
418 try:
419 processor_dir_entries = os.listdir(self.proc_processor_dir)
420 except OSError:
421 return #nothing more to do
423 try:
424 for i in processor_dir_entries:
425 mode = os.stat(self.proc_processor_dir + "/" + i)[stat.ST_MODE]
426 if stat.S_ISDIR(mode):
427 self.processor_dir_entries.append(i)
428 except OSError:
429 # the processor module is not correctly loaded, or is broken.
430 # because the appended dirs should be okay we do not return here
431 pass
433 try:
434 for i in self.processor_dir_entries:
435 file = open(self.proc_processor_dir + "/" + i + "/performance")
436 line = file.readline()
437 while(len(line)!=0):
438 if line.find("MHz") > -1:
439 state = line.split(":")[0].strip().split("P")[-1]
440 freq = line.split(":")[1].split(",")[0].strip()
441 self.perf_states[freq] = state
442 line = file.readline()
443 file.close()
444 except IOError:
445 self.processor_dir_entries = []
446 self.perf_states = {} #reset list --> should we throw an exception? No!
447 return
450 def update_processors(self):
451 """Read current state of processors"""
453 try:
454 for i in self.processor_dir_entries:
455 file = open(self.proc_processor_dir + "/" + i + "/performance")
456 line = file.readline()
458 while(len(line)!=0):
459 if line.find("*") > -1:
460 self.freq = line.split(":")[1].strip().split(",")[0]
461 line = f.readline()
462 file.close()
463 except IOError:
464 raise AcpiError,ERR_CONFIGURATION_CHANGED
467 def percent(self):
468 """Returns percentage capacity of all batteries"""
470 life_capacity = 0
471 design_capacity = 0
472 for i,c in self.life_capacity.items():
473 life_capacity = life_capacity + c
474 design_capacity = design_capacity + self.design_capacity[i]
476 if design_capacity == 0:
477 return 0
479 # should we use try catch instead of the check above?
480 return (life_capacity * 100) / design_capacity
483 def capacity(self):
484 """Returns capacity of all batteries"""
485 capacity = 0
486 for i,c in self.life_capacity.items():
487 capacity = capacity + c
488 return capacity
491 def nb_of_batteries(self):
492 #returns the number of batteries
493 #if it returns 0, maybe ACPI is not available or
494 #battery driver is not loaded
495 return len(self.battery_dir_entries)
498 def charging_state(self):
499 return self.ac_line_state
502 def estimated_lifetime(self):
503 # what should we return if state==charging?
504 # it's not clean to return a time in one case and any
505 # English string in another case.
506 # The user can check for ac-state before call this func
508 # fixed bug - if a battery is full and not discharging, it
509 # would not be counted in the estimated lifetime
510 # so, this new code totals the remaining capacity and rate
511 # from all the batteries, and divides
512 # however, if two or more batteries are discharging at
513 # different non-zero rates, I don't know how accurate this
514 # will be
515 # - QS Computing (postmaster@qscomputing.net)
517 time = 0
518 totalCapacity = 0
519 totalRate = 0
521 for batt, capacity in self.life_capacity.items():
522 totalCapacity += capacity
523 totalRate += self.present_rate[batt]
524 time = totalCapacity / totalRate
526 return time
529 # we need funcs like max_temperature and average_temperature
530 def temperature(self, idx):
531 #print self.temperatures
532 #print self.thermal_dir_entries[idx]
533 return self.temperatures(self.thermal_dir_entries(idx))
536 def fan_state(self, idx):
537 #print self.fans
538 return self.fans[self.fan_dir_entries[idx]]
541 def performance_states(self, idx):
542 return self.perf_states[idx].keys()
545 def frequency(self, idx):
546 #print self.freq
547 return self.freq[idx]
550 # TODO: adapt it for multiple CPUs
551 def set_frequency(self, f):
552 #I think we should throw exceptions if someone goes wrong here
554 if self.perf_states.has_key(f):
555 state = self.perf_states[f]
556 try:
557 pr = os.listdir("/proc/acpi/processor")[0]
558 except OSError:
559 raise AcpiError, ERR_NOT_ALLOWED
561 try:
562 f = open("/proc/acpi/processor/"+pr+"/performance","w")
563 except IOError:
564 raise AcpiError, ERR_NOT_ALLOWED
566 f.write(state)
567 f.close()
568 else:
569 raise AcpiError, ERR_NOT_ALLOWED