Release prep...
[rox-lithium.git] / acpi.py
blobb7ce1fadfccc5b304f32231eb9230bb1ff58b3e8
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()
123 def percent(self):
124 """Returns percentage capacity of all batteries"""
125 return self.acpi.percent()
127 def capacity(self):
128 """Returns capacity of all batteries (in mWh)"""
129 return self.acpi.capacity()
131 def nb_of_batteries(self):
132 """Returns the number of batteries"""
133 return self.acpi.nb_of_batteries()
135 def charging_state(self):
136 """Returns ac state (off-/online/charging)"""
137 return self.acpi.charging_state()
139 def estimated_lifetime(self):
140 """Returns Estimated Lifetime as real number"""
141 return self.acpi.estimated_lifetime()
143 def temperature(self, idx):
144 """Returns Processor Temperature"""
145 return self.acpi.temperature(idx)
147 def fan_state(self, idx):
148 """Returns fan states"""
149 return self.acpi.fan_state(idx)
152 #unfinished: don't use it or better send us improvements ;-)
153 def frequency(self, idx):
154 """ Return the frequency of the processor"""
155 return self.acpi.frequency(idx)
157 def performance_states(self, idx):
158 """ Return a list of available frequencies for the proc """
159 return self.acpi.performance_states(idx)
161 def set_frequency(self, f):
162 """ Set the processor frequency - Warning ! Needs root privileges to work """
163 return self.acpi.set_frequency(f)
167 # implementation for Linux ####################################################
169 class AcpiLinux:
170 def __init__(self):
171 """init ACPI class and check for any ACPI features in /proc/acpi/"""
173 #we read all acpi stuff from here
174 self.proc_acpi_dir = "/proc/acpi"
176 if not os.access(self.proc_acpi_dir, os.F_OK):
177 raise EnvironmentError
179 self.init_batteries()
180 self.init_fans()
181 #self.init_processors()
182 self.init_temperatures()
184 self.update()
187 def update(self):
188 """Read current states of supported acpi components"""
190 self.update_batteries()
191 self.update_fans()
192 #self.update_processors()
193 self.update_temperatures()
195 # battery related functions
196 def init_batteries(self):
197 """Checks for and initializes the batteries"""
199 self.proc_battery_dir = self.proc_acpi_dir + "/battery"
201 # empty lists implies no battery, no capacity etc.
202 self.design_capacity = {}
203 self.last_full_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 if line.find("last full capacity:") == 0:
249 cap = line.split(":")[1].strip()
250 try:
251 self.last_full_capacity[i] = int(cap.split("m")[0].strip())
252 except ValueError:
253 #no value --> conversion to int failed
254 self.last_full_capacity[i] = 0
255 line = info_file.readline()
256 info_file.close()
257 except IOError:
258 #print "No batt info found."
259 # the battery module is not correctly loaded... the file info should exist.
260 # wipe out all lists --> no battery infos
261 self.battery_dir_entries = []
262 self.design_capacity = {}
263 self.last_full_capacity = {}
264 self.life_capacity = {}
265 self.present_rate = {}
268 def update_batteries(self):
269 """Read current state of batteries"""
271 try:
272 for i in self.battery_dir_entries:
273 state_file = open(self.proc_battery_dir + "/" + i + "/state")
274 line = state_file.readline()
276 while len(line) != 0:
277 if line.find("remaining capacity") == 0:
278 cap = line.split(":")[1].strip()
279 try:
280 self.life_capacity[i] = int(cap.split("m")[0].strip())
281 except ValueError:
282 self.life_capacity[i] = 0
284 # it's possible that in battery/*/info the charging state is unknown
285 # --> then we must check ac_state...
286 # iterating over all batteries this way is not smart. better implementation needed
287 # better are funcs for capacity, acstate and prrate
289 # a little bit tricky... if loading of ac driver fails, we cant use info
290 # from /proc/ac_*/...
291 # if information in /proc/acpi/battery/*/state is wrong we had to
292 # track the capacity history.
293 # I assume that all battery state files get the same state.
294 if line.find("charging state") == 0:
295 state = line.split(":")[1].strip()
296 if state == "discharging":
297 self.ac_line_state = OFFLINE
298 elif state == "charging":
299 self.ac_line_state = CHARGING
300 else:
301 self.ac_line_state = ONLINE
303 # Read the present energy consumption to
304 # estimate life time
306 if line.find("present rate:") == 0:
307 try:
308 pr_rate = float(line.split(":")[1].strip().split("m")[0].strip())
309 except ValueError:
310 pr_rate = 0
312 self.present_rate[i] = pr_rate
314 line = state_file.readline()
315 state_file.close()
316 except IOError:
317 raise AcpiError, ERR_CONFIGURATION_CHANGED
319 # maybe we should restart init_batteries instead of generating an error ?
320 # the user may have unplugged the battery.
321 #init_batteries()
322 # I prefer raising an exception because we would run into a recursion of
323 # member funcs what is not a good idea.
324 # the case that this error occurs should be very rare
327 def init_temperatures(self):
328 """Initializes temperature stuff"""
330 self.proc_thermal_dir = self.proc_acpi_dir + "/thermal_zone"
332 # empty list implies no thermal feature supported
333 self.temperatures = {}
335 # empty list of thermal sub directories; implies no thermal infos available
336 self.thermal_dir_entries = []
338 try:
339 thermal_dir_entries = os.listdir(self.proc_thermal_dir)
340 except OSError:
341 return #nothing more to do
343 try:
344 for i in thermal_dir_entries:
345 mode = os.stat(self.proc_thermal_dir + "/" + i)[stat.ST_MODE]
346 if stat.S_ISDIR(mode):
347 self.thermal_dir_entries.append(i)
348 except OSError:
349 # the thermal module is not correctly loaded, or is broken.
350 # because the appended dirs should be okay we do not return here
351 pass
354 def update_temperatures(self):
355 """Read current temperatures"""
357 try:
358 for i in self.thermal_dir_entries:
359 file = open(self.proc_thermal_dir + "/" + i + "/temperature")
360 line = file.readline()
361 while len(line) != 0:
362 if line.find("temperature") == 0:
363 self.temperatures[i] = line.split(":")[1].strip()
364 line = file.readline()
365 file.close()
366 except IOError:
367 raise AcpiError,ERR_CONFIGURATION_CHANGED
370 def init_fans(self):
371 """Initialize fans"""
373 self.proc_fan_dir = self.proc_acpi_dir + "/fan"
375 self.fans = {}
377 # empty list of fan sub directories; implies no fan infos available
378 self.fan_dir_entries = []
380 try:
381 fan_dir_entries = os.listdir(self.proc_fan_dir)
382 except OSError:
383 return #nothing more to do
385 try:
386 for i in fan_dir_entries:
387 mode = os.stat(self.proc_fan_dir + "/" + i)[stat.ST_MODE]
388 if stat.S_ISDIR(mode):
389 self.fan_dir_entries.append(i)
390 except OSError:
391 # the fan module is not correctly loaded, or is broken.
392 # because the appended dirs should be okay we do not return here
393 pass
396 def update_fans(self):
397 """Read current state of fans"""
399 try:
400 for i in self.fan_dir_entries:
401 file = open(self.proc_fan_dir + "/" + i + "/state")
402 line = file.readline()
403 while len(line) != 0:
404 if line.find("status") == 0:
405 if line.split(":")[1].strip() == 'on':
406 self.fans[i] = FAN_ON
407 else:
408 self.fans[i] = FAN_OFF
409 line = file.readline()
410 file.close()
411 except IOError:
412 raise AcpiError,ERR_CONFIGURATION_CHANGED
415 def init_processors(self):
416 """Initialize processors"""
418 self.proc_processor_dir = self.proc_acpi_dir + "/processor"
420 # TODO: adapt it for multiple CPUs --> we need a matrix instead of a vector!!!
421 self.perf_states = {} #empty list implies no processor support
423 # empty list of processor sub directories; implies no processor infos available
424 self.processor_dir_entries = []
426 try:
427 processor_dir_entries = os.listdir(self.proc_processor_dir)
428 except OSError:
429 return #nothing more to do
431 try:
432 for i in processor_dir_entries:
433 mode = os.stat(self.proc_processor_dir + "/" + i)[stat.ST_MODE]
434 if stat.S_ISDIR(mode):
435 self.processor_dir_entries.append(i)
436 except OSError:
437 # the processor module is not correctly loaded, or is broken.
438 # because the appended dirs should be okay we do not return here
439 pass
441 try:
442 for i in self.processor_dir_entries:
443 file = open(self.proc_processor_dir + "/" + i + "/performance")
444 line = file.readline()
445 while(len(line)!=0):
446 if line.find("MHz") > -1:
447 state = line.split(":")[0].strip().split("P")[-1]
448 freq = line.split(":")[1].split(",")[0].strip()
449 self.perf_states[freq] = state
450 line = file.readline()
451 file.close()
452 except IOError:
453 self.processor_dir_entries = []
454 self.perf_states = {} #reset list --> should we throw an exception? No!
455 return
458 def update_processors(self):
459 """Read current state of processors"""
461 try:
462 for i in self.processor_dir_entries:
463 file = open(self.proc_processor_dir + "/" + i + "/performance")
464 line = file.readline()
466 while(len(line)!=0):
467 if line.find("*") > -1:
468 self.freq = line.split(":")[1].strip().split(",")[0]
469 line = f.readline()
470 file.close()
471 except IOError:
472 raise AcpiError,ERR_CONFIGURATION_CHANGED
475 def percent(self):
476 """Returns percentage capacity of all batteries"""
478 life_capacity = 0
479 design_capacity = 0
480 last_full_capacity = 0
481 current_capacity = 0
482 for i,c in self.life_capacity.items():
483 life_capacity = life_capacity + c
484 design_capacity = design_capacity + self.design_capacity[i]
485 last_full_capacity = last_full_capacity + self.last_full_capacity[i]
487 current_capacity = last_full_capacity #max(design_capacity, last_full_capacity)
488 if current_capacity == 0:
489 return 0
491 # should we use try catch instead of the check above?
492 return (life_capacity * 100) / current_capacity
495 def capacity(self):
496 """Returns capacity of all batteries"""
497 capacity = 0
498 for i,c in self.life_capacity.items():
499 capacity = capacity + c
500 return capacity
503 def nb_of_batteries(self):
504 #returns the number of batteries
505 #if it returns 0, maybe ACPI is not available or
506 #battery driver is not loaded
507 return len(self.battery_dir_entries)
510 def charging_state(self):
511 return self.ac_line_state
514 def estimated_lifetime(self):
515 # what should we return if state==charging?
516 # it's not clean to return a time in one case and any
517 # English string in another case.
518 # The user can check for ac-state before call this func
520 # fixed bug - if a battery is full and not discharging, it
521 # would not be counted in the estimated lifetime
522 # so, this new code totals the remaining capacity and rate
523 # from all the batteries, and divides
524 # however, if two or more batteries are discharging at
525 # different non-zero rates, I don't know how accurate this
526 # will be
527 # - QS Computing (postmaster@qscomputing.net)
529 time = 0
530 totalCapacity = 0
531 totalRate = 0
533 for batt, capacity in self.life_capacity.items():
534 totalCapacity += capacity
535 totalRate += self.present_rate[batt]
536 if totalRate:
537 time = totalCapacity / totalRate
538 else:
539 time = totalCapacity
541 return time
544 # we need funcs like max_temperature and average_temperature
545 def temperature(self, idx):
546 #print self.temperatures
547 #print self.thermal_dir_entries[idx]
548 return self.temperatures(self.thermal_dir_entries(idx))
551 def fan_state(self, idx):
552 #print self.fans
553 return self.fans[self.fan_dir_entries[idx]]
556 def performance_states(self, idx):
557 return self.perf_states[idx].keys()
560 def frequency(self, idx):
561 #print self.freq
562 return self.freq[idx]
565 # TODO: adapt it for multiple CPUs
566 def set_frequency(self, f):
567 #I think we should throw exceptions if someone goes wrong here
569 if self.perf_states.has_key(f):
570 state = self.perf_states[f]
571 try:
572 pr = os.listdir("/proc/acpi/processor")[0]
573 except OSError:
574 raise AcpiError, ERR_NOT_ALLOWED
576 try:
577 f = open("/proc/acpi/processor/"+pr+"/performance","w")
578 except IOError:
579 raise AcpiError, ERR_NOT_ALLOWED
581 f.write(state)
582 f.close()
583 else:
584 raise AcpiError, ERR_NOT_ALLOWED