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"""
125 """Returns percentage capacity of all batteries"""
126 return self
.acpi
.percent()
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 ####################################################
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()
182 #self.init_processors()
183 self
.init_temperatures()
189 """Read current states of supported acpi components"""
191 self
.update_batteries()
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
= []
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 line
= info_file
.readline()
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"""
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()
272 self
.life_capacity
[i
] = int(cap
.split("m")[0].strip())
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
293 self
.ac_line_state
= ONLINE
295 # Read the present energy consumption to
298 if line
.find("present rate:") == 0:
300 pr_rate
= float(line
.split(":")[1].strip().split("m")[0].strip())
304 self
.present_rate
[i
] = pr_rate
306 line
= state_file
.readline()
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.
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
= []
331 thermal_dir_entries
= os
.listdir(self
.proc_thermal_dir
)
333 return #nothing more to do
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
)
341 # the thermal module is not correctly loaded, or is broken.
342 # because the appended dirs should be okay we do not return here
346 def update_temperatures(self
):
347 """Read current temperatures"""
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()
359 raise AcpiError
,ERR_CONFIGURATION_CHANGED
363 """Initialize fans"""
365 self
.proc_fan_dir
= self
.proc_acpi_dir
+ "/fan"
369 # empty list of fan sub directories; implies no fan infos available
370 self
.fan_dir_entries
= []
373 fan_dir_entries
= os
.listdir(self
.proc_fan_dir
)
375 return #nothing more to do
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
)
383 # the fan module is not correctly loaded, or is broken.
384 # because the appended dirs should be okay we do not return here
388 def update_fans(self
):
389 """Read current state of fans"""
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
400 self
.fans
[i
] = FAN_OFF
401 line
= file.readline()
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
= []
419 processor_dir_entries
= os
.listdir(self
.proc_processor_dir
)
421 return #nothing more to do
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
)
429 # the processor module is not correctly loaded, or is broken.
430 # because the appended dirs should be okay we do not return here
434 for i
in self
.processor_dir_entries
:
435 file = open(self
.proc_processor_dir
+ "/" + i
+ "/performance")
436 line
= file.readline()
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()
445 self
.processor_dir_entries
= []
446 self
.perf_states
= {} #reset list --> should we throw an exception? No!
450 def update_processors(self
):
451 """Read current state of processors"""
454 for i
in self
.processor_dir_entries
:
455 file = open(self
.proc_processor_dir
+ "/" + i
+ "/performance")
456 line
= file.readline()
459 if line
.find("*") > -1:
460 self
.freq
= line
.split(":")[1].strip().split(",")[0]
464 raise AcpiError
,ERR_CONFIGURATION_CHANGED
468 """Returns percentage capacity of all batteries"""
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:
479 # should we use try catch instead of the check above?
480 return (life_capacity
* 100) / design_capacity
484 """Returns capacity of all batteries"""
486 for i
,c
in self
.life_capacity
.items():
487 capacity
= capacity
+ c
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
515 # - QS Computing (postmaster@qscomputing.net)
521 for batt
, capacity
in self
.life_capacity
.items():
522 totalCapacity
+= capacity
523 totalRate
+= self
.present_rate
[batt
]
524 time
= totalCapacity
/ totalRate
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
):
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
):
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
]
557 pr
= os
.listdir("/proc/acpi/processor")[0]
559 raise AcpiError
, ERR_NOT_ALLOWED
562 f
= open("/proc/acpi/processor/"+pr
+"/performance","w")
564 raise AcpiError
, ERR_NOT_ALLOWED
569 raise AcpiError
, ERR_NOT_ALLOWED