]> git.madduck.net Git - etc/awesome.git/blob - widget/bat.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge pull request #398 from BarbUk/feature/tpbat/configurable-battery-thresholds
[etc/awesome.git] / widget / bat.lua
1 --[[
2
3          Licensed under GNU General Public License v2
4           * (c) 2013,      Luca CPZ
5           * (c) 2010-2012, Peter Hofmann
6
7 --]]
8
9 local helpers    = require("lain.helpers")
10 local fs         = require("gears.filesystem")
11 local naughty    = require("naughty")
12 local wibox      = require("wibox")
13 local math       = math
14 local string     = string
15 local ipairs     = ipairs
16 local tonumber   = tonumber
17
18 -- Battery infos
19 -- lain.widget.bat
20
21 local function factory(args)
22     local pspath = args.pspath or "/sys/class/power_supply/"
23
24     if not fs.is_dir(pspath) then
25         naughty.notify { text = "lain.widget.bat: invalid power supply path", timeout = 0 }
26         return
27     end
28
29     local bat       = { widget = wibox.widget.textbox() }
30     local args      = args or {}
31     local timeout   = args.timeout or 30
32     local notify    = args.notify or "on"
33     local n_perc    = args.n_perc or { 5, 15 }
34     local batteries = args.batteries or (args.battery and {args.battery}) or {}
35     local ac        = args.ac or "AC0"
36     local settings  = args.settings or function() end
37
38     function bat.get_batteries()
39         helpers.line_callback("ls -1 " .. pspath, function(line)
40             local bstr =  string.match(line, "BAT%w+")
41             if bstr then
42                 batteries[#batteries + 1] = bstr
43             else
44                 ac = string.match(line, "A%w+") or "AC0"
45             end
46         end)
47     end
48
49     if #batteries == 0 then bat.get_batteries() end
50
51     bat_notification_critical_preset = {
52         title   = "Battery exhausted",
53         text    = "Shutdown imminent",
54         timeout = 15,
55         fg      = "#000000",
56         bg      = "#FFFFFF"
57     }
58
59     bat_notification_low_preset = {
60         title   = "Battery low",
61         text    = "Plug the cable!",
62         timeout = 15,
63         fg      = "#202020",
64         bg      = "#CDCDCD"
65     }
66
67     bat_notification_charged_preset = {
68         title   = "Battery full",
69         text    = "You can unplug the cable",
70         timeout = 15,
71         fg      = "#202020",
72         bg      = "#CDCDCD"
73     }
74
75     bat_now = {
76         status    = "N/A",
77         ac_status = "N/A",
78         perc      = "N/A",
79         time      = "N/A",
80         watt      = "N/A"
81     }
82
83     bat_now.n_status = {}
84     bat_now.n_perc   = {}
85     for i = 1, #batteries do
86         bat_now.n_status[i] = "N/A"
87         bat_now.n_perc[i] = 0
88     end
89
90     -- used to notify full charge only once before discharging
91     local fullnotification = false
92
93     function bat.update()
94         local sum_rate_current = 0
95         local sum_rate_voltage = 0
96         local sum_rate_power   = 0
97         local sum_rate_energy  = 0
98         local sum_energy_now   = 0
99         local sum_energy_full  = 0
100
101         for i, battery in ipairs(batteries) do
102             local bstr    = pspath .. battery
103             local present = helpers.first_line(bstr .. "/present")
104
105             if tonumber(present) == 1 then
106                 -- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW]
107                 local rate_current = tonumber(helpers.first_line(bstr .. "/current_now"))
108                 local rate_voltage = tonumber(helpers.first_line(bstr .. "/voltage_now"))
109                 local rate_power   = tonumber(helpers.first_line(bstr .. "/power_now"))
110
111                 -- energy_now(P)[uWh], charge_now(I)[uAh]
112                 local energy_now = tonumber(helpers.first_line(bstr .. "/energy_now") or
113                                    helpers.first_line(bstr .. "/charge_now"))
114
115                 -- energy_full(P)[uWh], charge_full(I)[uAh]
116                 local energy_full = tonumber(helpers.first_line(bstr .. "/energy_full") or
117                                     helpers.first_line(bstr .. "/charge_full"))
118
119                 local energy_percentage = tonumber(helpers.first_line(bstr .. "/capacity")) or
120                                           math.floor((energy_now / energy_full) * 100)
121
122                 bat_now.n_status[i] = helpers.first_line(bstr .. "/status") or "N/A"
123                 bat_now.n_perc[i]   = energy_percentage or bat_now.n_perc[i]
124
125                 sum_rate_current = sum_rate_current + (rate_current or 0)
126                 sum_rate_voltage = sum_rate_voltage + (rate_voltage or 0)
127                 sum_rate_power   = sum_rate_power + (rate_power or 0)
128                 sum_rate_energy  = sum_rate_energy + (rate_power or (((rate_voltage or 0) * (rate_current or 0)) / 1e6))
129                 sum_energy_now   = sum_energy_now + (energy_now or 0)
130                 sum_energy_full  = sum_energy_full + (energy_full or 0)
131             end
132         end
133
134         -- When one of the battery is charging, others' status are either
135         -- "Full", "Unknown" or "Charging". When the laptop is not plugged in,
136         -- one or more of the batteries may be full, but only one battery
137         -- discharging suffices to set global status to "Discharging".
138         bat_now.status = bat_now.n_status[1]
139         for _,status in ipairs(bat_now.n_status) do
140             if status == "Discharging" or status == "Charging" then
141                 bat_now.status = status
142             end
143         end
144         bat_now.ac_status = tonumber(helpers.first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
145
146         if bat_now.status ~= "N/A" then
147             if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
148                 bat_now.perc  = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
149                 bat_now.time  = "00:00"
150                 bat_now.watt  = 0
151
152             -- update {perc,time,watt} iff battery not full and rate > 0
153             elseif bat_now.status ~= "Full" then
154                 local rate_time = 0
155                 -- Calculate time and watt if rates are greater then 0
156                 if (sum_rate_power > 0 or sum_rate_current > 0) then
157                     local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
158
159                     if bat_now.status == "Charging" then
160                         rate_time = (sum_energy_full - sum_energy_now) / div
161                     else -- Discharging
162                         rate_time = sum_energy_now / div
163                     end
164
165                     if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
166                         rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
167                         rate_time = rate_time * 10^(rate_time_magnitude - 2)
168                     end
169                  end
170
171                 local hours   = math.floor(rate_time)
172                 local minutes = math.floor((rate_time - hours) * 60)
173                 bat_now.perc  = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
174                 bat_now.time  = string.format("%02d:%02d", hours, minutes)
175                 bat_now.watt  = tonumber(string.format("%.2f", sum_rate_energy / 1e6))
176             elseif bat_now.status == "Full" then
177                 bat_now.perc  = 100
178                 bat_now.time  = "00:00"
179                 bat_now.watt  = 0
180             end
181         end
182
183         widget = bat.widget
184         settings()
185
186         -- notifications for critical, low, and full levels
187         if notify == "on" then
188             if bat_now.status == "Discharging" then
189                 if tonumber(bat_now.perc) <= n_perc[1] then
190                     bat.id = naughty.notify({
191                         preset = bat_notification_critical_preset,
192                         replaces_id = bat.id
193                     }).id
194                 elseif tonumber(bat_now.perc) <= n_perc[2] then
195                     bat.id = naughty.notify({
196                         preset = bat_notification_low_preset,
197                         replaces_id = bat.id
198                     }).id
199                 end
200                 fullnotification = false
201             elseif bat_now.status == "Full" and not fullnotification then
202                 bat.id = naughty.notify({
203                     preset = bat_notification_charged_preset,
204                     replaces_id = bat.id
205                 }).id
206                 fullnotification = true
207             end
208         end
209     end
210
211     helpers.newtimer("batteries", timeout, bat.update)
212
213     return bat
214 end
215
216 return factory