1 -- Authors: Jari Eskelinen <jari.eskelinen@iki.fi>, René van Bevern <rvb@pro-linux.de>, Juri Hamburg <juri@fail2fail.com>, Philipp Hartwig <ph@phhart.de>
2 -- License: GPL, version 2
3 -- Last Changed: 2013-04-09
5 -- statusd_laptopstatus.lua v0.0.3 (last modified 2011-10-30)
7 -- Copyright (C) 2005 Jari Eskelinen <jari.eskelinen@iki.fi>
8 -- modified by René van Bevern <rvb@pro-linux.de> for error handling
9 -- modified by Juri Hamburg <juri@fail2fail.com> for sysfs support (2011)
10 -- modified by Philipp Hartwig <ph@phhart.de> for improved sysfs support (2013)
12 -- Permission to copy, redistirbute, modify etc. is granted under the terms
13 -- of GNU GENERAL PUBLIC LICENSE Version 2.
15 -- This is script for displaying some interesting information about your
16 -- laptops power saving in Notion's status monitor. Script is very Linux
17 -- specific (uses procfs or sysfs interface) and needs ACPI-support (don't
18 -- know exactly but 2.6.x kernels should be fine). Also if you have some
19 -- kind of exotic hardware (multiple processors, multiple batteries etc.)
20 -- this script probably will fail or show incorrect information.
22 -- The script will try sysfs interface first. If that fails, it will try to
23 -- use the procfs interface.
25 -- Just throw this script under ~/.notion and add following keywords to your
26 -- cfg_statusbar.lua's template-line with your own taste:
28 -- %laptopstatus_cpuspeed
29 -- %laptopstatus_temperature
30 -- %laptopstatus_batterypercent
31 -- %laptopstatus_batterytimeleft
32 -- %laptopstatus_batterydrain
34 -- Template example: template="[ %date || load:% %>load || CPU: %laptopstatus_cpuspeed %laptopstatus_temperature || BATT: %laptopstatus_batterypercent %laptopstatus_batterytimeleft %laptopstatus_batterydrain ]"
36 -- You can also run this script with lua interpreter and see if you get
39 -- TODO: * Is it stupid to use file:read("*all"), can this cause infinite
40 -- loops in some weird situations?
41 -- * Do not poll for information not defined in template to be used
51 proc_dir
="/proc/acpi/"
53 if not statusd_laptopstatus
then
54 statusd_laptopstatus
= {
55 interval
= 10, -- Polling interval in seconds
56 temperature_important
= 66, -- Temperature which will cause important hint
57 temperature_critical
= 71, -- Temperature which will cause critical hint
58 batterypercent_important
= 10, -- Battery percent which will cause important hint
59 batterypercent_critical
= 5, -- Battery percent which will cause critical hint
60 batterytimeleft_important
= 600, -- Battery time left (in secs) which will cause important hint
61 batterytimeleft_critical
= 300, -- Battery time left (in secs) which will cause critical hint
65 if statusd
~= nil then
66 statusd_laptopstatus
=table.join(statusd
.get_config("laptopstatus"), statusd_laptopstatus
)
72 local laptopstatus_timer
75 RingBuffer
.__index
= RingBuffer
77 function RingBuffer
.create(size
)
79 setmetatable(buf
,RingBuffer
)
86 function RingBuffer
:add(val
)
87 if self
.current
> self
.size
then
90 self
.elements
[self
.current
] = val
91 self
.current
= self
.current
+ 1
94 function average(array
)
96 for i
,v
in ipairs(array
) do
106 rates_buffer
= RingBuffer
.create(20)
109 function read_val(file
)
110 local fd
= io
.open(file
)
112 local value
= fd
:read("*a")
118 -- Returns the full path of the first subdirectory of "path" containing a file
119 -- with name "filename" and first line equal to "content"
120 function lookup_subdir(path
, filename
, content
)
121 if path
:sub(-1) ~= "/" then path
= path
.. "/" end
122 local fd
=io
.open(path
)
123 if not fd
then return nil end
127 if pcall(require
, "lfs") then
130 return io
.popen("ls -1 " .. path
):lines()
134 for dir
in dir_iter() do
135 if not (dir
== "." or dir
== "..") then
136 local fd
=io
.open(path
.. dir
.. "/" .. filename
)
137 if fd
and ((not content
) or content
== fd
:read():lower()) then
138 return path
.. dir
.. "/"
144 local function get_cpu()
148 status
, _
, mhz
= string.find(read_val("/proc/cpuinfo"),
150 if not status
then error("could not get MHz") end
152 then return {speed
=string.format("%4dMHz", math
.ceil(mhz
/5)*5),
154 else return {speed
=NA
, hint
=hint
} end
157 local function on_ac()
159 return (tonumber(read_val(ac_dir
.. "online") or "") == 1)
161 return string.find(read_val(ac_dir
.. "state") or "", "state:%s+on.line")
165 local function get_thermal_sysfs()
166 local temp
, hint
= nil, "normal"
169 temp
=read_val(temp_dir
.. "temp")
170 temp
= tonumber(temp
)/1000
172 then if temp
>= statusd_laptopstatus
.temperature_important
then
173 hint
= "important" end
174 if temp
>= statusd_laptopstatus
.temperature_critical
then
175 hint
= "critical" end
176 return {temperature
=string.format("%02dC", temp
), hint
=hint
}
177 else return {temperature
=NA
, hint
= "hint"} end
180 local function get_thermal_procfs()
181 local temp
, hint
= nil, "normal"
185 status
, _
, temp
= string.find(read_val(temp_dir
.. "temperature"),
186 "temperature:%s+(%d+).*")
187 if not status
then error("could not get temperature") end
188 temp
= tonumber(temp
)
190 then if temp
>= statusd_laptopstatus
.temperature_important
then
191 hint
= "important" end
192 if temp
>= statusd_laptopstatus
.temperature_critical
then
193 hint
= "critical" end
194 return {temperature
=string.format("%02dC", temp
), hint
=hint
}
195 else return {temperature
=NA
, hint
= "hint"} end
198 local function get_thermal()
200 return get_thermal_sysfs()
202 return get_thermal_procfs()
210 local function get_battery_sysfs()
211 local percenthint
= "normal"
212 local timelefthint
= "normal"
216 now
= tonumber(read_val(bat_dir
.. "energy_now"))
217 full
= tonumber(read_val(bat_dir
.. "energy_full"))
221 now
= tonumber(read_val(bat_dir
.. "charge_now"))
222 full
= tonumber(read_val(bat_dir
.. "charge_full"))
225 --Some batteries erronously report a charge_now value of charge_full_design when full.
226 if now
> full
then now
=full
end
227 local percent
= math
.floor(now
/ full
* 100 + 5/10)
231 elseif last_power
~= nil and now
< last_power
then
232 rate_sysfs
= last_power
- now
233 rates_buffer
:add(rate_sysfs
)
234 secs
= statusd_laptopstatus
.interval
* (now
/ average(rates_buffer
.elements
))
236 hours
= math
.floor(mins
/ 60)
237 mins
= math
.floor(mins
- (hours
* 60))
238 timeleft
= string.format("%02d:%02d", hours
, mins
)
244 if percent
<= statusd_laptopstatus
.batterypercent_important
then percenthint
= "important" end
245 if percent
<= statusd_laptopstatus
.batterypercent_critical
then percenthint
= "critical" end
246 return { percent
=string.format("%02d%%", percent
), timeleft
=timeleft
, drain
=NA
, percenthint
=percenthint
, timelefthint
=timelefthint
}
247 else return { percent
=NA
, timeleft
=NA
, drain
=NA
, percenthint
=percenthint
, timelefthint
=timelefthint
} end
250 local function get_battery_procfs()
251 local percenthint
= "normal"
252 local timelefthint
= "normal"
253 local lastfull
, rate
, rateunit
, remaining
257 local statecontents
= read_val(bat_dir
.. "state")
259 status
, _
, lastfull
= string.find(read_val(bat_dir
.. "info"), "last full capacity:%s+(%d+).*")
260 if not status
then error("could not get full battery capacity") end
261 lastfull
= tonumber(lastfull
)
262 if string.find(statecontents
, "present rate:%s+unknown.*") then
265 status
, _
, rate
, rateunit
= string.find(statecontents
, "present rate:%s+(%d+)(.*)")
266 if not status
then error("could not get battery draining-rate") end
267 rate
= tonumber(rate
)
269 status
, _
, remaining
= string.find(statecontents
, "remaining capacity:%s+(%d+).*")
270 if not status
then error("could not get remaining capacity") end
271 remaining
= tonumber(remaining
)
273 local percent
= math
.floor(remaining
/ lastfull
* 100 + 5/10)
275 local hours
, secs
, mins
278 elseif rate
<= 0 then
281 secs
= 3600 * (remaining
/ rate
)
283 hours
= math
.floor(mins
/ 60)
284 mins
= math
.floor(mins
- (hours
* 60))
285 timeleft
= string.format("%02d:%02d", hours
, mins
)
288 if secs
~= nil and secs
<= statusd_laptopstatus
.batterytimeleft_important
then timelefthint
= "important" end
289 if secs
~= nil and secs
<= statusd_laptopstatus
.batterytimeleft_critical
then timelefthint
= "critical" end
290 if percent
<= statusd_laptopstatus
.batterypercent_important
then percenthint
= "important" end
291 if percent
<= statusd_laptopstatus
.batterypercent_critical
then percenthint
= "critical" end
293 return { percent
=string.format("%02d%%", percent
), timeleft
=timeleft
, drain
=tostring(rate
)..rateunit
, percenthint
=percenthint
, timelefthint
=timelefthint
}
294 else return { percent
=NA
, timeleft
=NA
, drain
=NA
, percenthint
=percenthint
, timelefthint
=timelefthint
} end
297 local function get_battery()
299 return get_battery_sysfs()
301 return get_battery_procfs()
305 local last_timeleft
= nil
310 ac_dir
=lookup_subdir(sys_dir
.. "power_supply" , "type", "mains")
313 ac_dir
=lookup_subdir(proc_dir
.. "ac_adapter", "state")
317 bat_dir
=lookup_subdir(sys_dir
.. "power_supply", "type", "battery")
320 bat_dir
=lookup_subdir(proc_dir
.. "battery", "state")
324 temp_dir
=lookup_subdir(sys_dir
.. "thermal", "type", "acpitz") or lookup_subdir(sys_dir
.. "thermal", "type", "thermal zone")
326 if(not temp_dir
) then
327 temp_dir
=lookup_subdir(proc_dir
.. "thermal_zone", "temperature")
332 local function update_laptopstatus ()
333 if not init
then do_init() end
335 thermal
= get_thermal()
336 battery
= get_battery()
338 -- Informing statusd OR printing to stdout if statusd not present
339 if statusd
~= nil then
340 statusd
.inform("laptopstatus_cpuspeed", cpu
.speed
)
341 statusd
.inform("laptopstatus_cpuspeed_template", "xxxxMHz")
342 statusd
.inform("laptopstatus_cpuspeed_hint", cpu
.hint
)
343 statusd
.inform("laptopstatus_temperature", thermal
.temperature
)
344 statusd
.inform("laptopstatus_temperature_template", "xxxC")
345 statusd
.inform("laptopstatus_temperature_hint", thermal
.hint
)
346 statusd
.inform("laptopstatus_batterypercent", battery
.percent
)
347 statusd
.inform("laptopstatus_batterypercent_template", "xxx%")
348 statusd
.inform("laptopstatus_batterypercent_hint", battery
.percenthint
)
349 if battery
.timeleft
~= NA
or last_timeleft
== AC
then
350 statusd
.inform("laptopstatus_batterytimeleft", battery
.timeleft
)
351 last_timeleft
= battery
.timeleft
353 statusd
.inform("laptopstatus_batterytimeleft_template", "hh:mm")
354 statusd
.inform("laptopstatus_batterytimeleft_hint", battery
.timelefthint
)
355 statusd
.inform("laptopstatus_batterydrain", battery
.drain
)
356 laptopstatus_timer
:set(statusd_laptopstatus
.interval
*1000, update_laptopstatus
)
358 io
.stdout
:write("CPU: "..cpu
.speed
.." "..thermal
.temperature
.." || BATT: "..battery
.percent
.." "..battery
.timeleft
.."\n")
363 if statusd
~= nil then
364 laptopstatus_timer
= statusd
.create_timer()
366 update_laptopstatus()