1 ##############################################################################
3 ## $Id: acpi.py,v 1.23 2003/12/15 20:04:09 riemer Exp $
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.
10 ## Redistribution and use in source and binary forms, with or without
11 ## modification, are permitted provided that the following conditions
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 ###############################################################################
38 # genenal constants ###########################################################
46 CHARGING
= 2 #implies ONLINE
52 # exceptions ##################################################################
57 ERR_NOT_IMPLEMENTED
= -3
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):
65 def __init__(self
, errno
):
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."
80 return "Unknown error occured."
84 # interface ###################################################################
87 """Interface class for ACPI"""
91 if res
.find("freebsd4") > -1:
92 self
.acpi
= None #throw exception
95 elif res
.find("netbsd1") > -1:
96 self
.acpi
= None #throw exception
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()
108 self
.acpi
= None #throw exception (os unknown)
113 """Returns the identity of this module"""
117 """Returns the version of this module"""
121 """Updates the ACPI state"""
124 """Returns percentage capacity of all batteries"""
125 return self
.acpi
.percent()
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 ####################################################
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()
181 #self.init_processors()
182 self
.init_temperatures()
188 """Read current states of supported acpi components"""
190 self
.update_batteries()
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
= []
211 battery_dir_entries
= os
.listdir(self
.proc_battery_dir
)
213 self
.ac_line_state
= ONLINE
# no batteries: we assume that a cable is plugged in ;-)
214 return #nothing more to do
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
)
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
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
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()
243 self
.design_capacity
[i
] = int(cap
.split("m")[0].strip())
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()
251 self
.last_full_capacity
[i
] = int(cap
.split("m")[0].strip())
253 #no value --> conversion to int failed
254 self
.last_full_capacity
[i
] = 0
255 line
= info_file
.readline()
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"""
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()
280 self
.life_capacity
[i
] = int(cap
.split("m")[0].strip())
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
301 self
.ac_line_state
= ONLINE
303 # Read the present energy consumption to
306 if line
.find("present rate:") == 0:
308 pr_rate
= float(line
.split(":")[1].strip().split("m")[0].strip())
312 self
.present_rate
[i
] = pr_rate
314 line
= state_file
.readline()
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.
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
= []
339 thermal_dir_entries
= os
.listdir(self
.proc_thermal_dir
)
341 return #nothing more to do
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
)
349 # the thermal module is not correctly loaded, or is broken.
350 # because the appended dirs should be okay we do not return here
354 def update_temperatures(self
):
355 """Read current temperatures"""
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()
367 raise AcpiError
,ERR_CONFIGURATION_CHANGED
371 """Initialize fans"""
373 self
.proc_fan_dir
= self
.proc_acpi_dir
+ "/fan"
377 # empty list of fan sub directories; implies no fan infos available
378 self
.fan_dir_entries
= []
381 fan_dir_entries
= os
.listdir(self
.proc_fan_dir
)
383 return #nothing more to do
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
)
391 # the fan module is not correctly loaded, or is broken.
392 # because the appended dirs should be okay we do not return here
396 def update_fans(self
):
397 """Read current state of fans"""
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
408 self
.fans
[i
] = FAN_OFF
409 line
= file.readline()
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
= []
427 processor_dir_entries
= os
.listdir(self
.proc_processor_dir
)
429 return #nothing more to do
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
)
437 # the processor module is not correctly loaded, or is broken.
438 # because the appended dirs should be okay we do not return here
442 for i
in self
.processor_dir_entries
:
443 file = open(self
.proc_processor_dir
+ "/" + i
+ "/performance")
444 line
= file.readline()
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()
453 self
.processor_dir_entries
= []
454 self
.perf_states
= {} #reset list --> should we throw an exception? No!
458 def update_processors(self
):
459 """Read current state of processors"""
462 for i
in self
.processor_dir_entries
:
463 file = open(self
.proc_processor_dir
+ "/" + i
+ "/performance")
464 line
= file.readline()
467 if line
.find("*") > -1:
468 self
.freq
= line
.split(":")[1].strip().split(",")[0]
472 raise AcpiError
,ERR_CONFIGURATION_CHANGED
476 """Returns percentage capacity of all batteries"""
480 last_full_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:
491 # should we use try catch instead of the check above?
492 return (life_capacity
* 100) / current_capacity
496 """Returns capacity of all batteries"""
498 for i
,c
in self
.life_capacity
.items():
499 capacity
= capacity
+ c
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
527 # - QS Computing (postmaster@qscomputing.net)
533 for batt
, capacity
in self
.life_capacity
.items():
534 totalCapacity
+= capacity
535 totalRate
+= self
.present_rate
[batt
]
537 time
= totalCapacity
/ totalRate
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
):
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
):
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
]
572 pr
= os
.listdir("/proc/acpi/processor")[0]
574 raise AcpiError
, ERR_NOT_ALLOWED
577 f
= open("/proc/acpi/processor/"+pr
+"/performance","w")
579 raise AcpiError
, ERR_NOT_ALLOWED
584 raise AcpiError
, ERR_NOT_ALLOWED