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