bat.lua 8.1 KB

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