Add FraI3mega/battery-graph (#115)

* feat(battery-graph): Initialize plugin files

* feat(battery-graph): Add bar widget

* feat(battery-graph): Implement logic

* docs(battery-graph): Fill out the template README

* feat(battery-graph): Improve panel

* feat(battery-graph): Implement settings

* feat(battery-graph): Improve panel styling

* feat(battery-graph): Add desktop widget

* docs(battery-graph): Improve wording

* docs(battery-graph): Add image

* docs(battery-graph): Add tags

* docs(battery-graph): Improve README

* fix(battery-graph): Fix translations

* docs(battery-graph): Fix tags

* fix(battery-graph): Fix author name

* docs(battery-graph): Rename thumbnail

* docs(battery-graph): Add dependencies

* docs(battery-graph): Improve README

* docs(battery-graph): Bump version to 1.0.0

* fix(battery-graph): Remove leftover log

* fix(battery-graph): Wrap battery path in quotes

* fix(battery-graph): Implement path validation

* docs(battery-graph): Clarify setting

* fix(battery-graph): Wrap grapth_time in quoutes

* docs(battery-graph): Fix setting description
This commit is contained in:
FraI3mega
2026-07-27 13:31:04 -04:00
committed by GitHub
parent be0b03d85d
commit 0319dc40f7
7 changed files with 271 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Battery graph
Uses the Upowerd to display the historical charge of the battery on a graph.
## Plugin
| Field | Value |
| --- | --- |
| ID | `frai3mega/battery-graph` |
| Entries | Bar widget: `battery-graph-widget`; panel: `battery-panel`; Desktop widget: `battery-graph-desktop` |
## Requirements
Requires a running `upowerd` deamon.
Install `gdbus` on `PATH`.
## Usage
You can use it as a desktop/locksreen widget or on the bar.
Show the panel using
```sh
noctalia msg panel-toggle frai3mega/battery-graph:battery-panel
```
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `graph_time` | `int` | `6` | Changes the timeframe of the graph |
| `battery_path` | `string` | `/org/freedesktop/UPower/devices/battery_BAT0` | The path to the battery to be graphed |
| `interpol_time` | `int` | `5` | The time beetween points on the graph. |
```
+79
View File
@@ -0,0 +1,79 @@
graph_time = 3600 * noctalia.getConfig("graph_time")
battery_path = noctalia.getConfig("battery_path")
interpol_time = 60 * noctalia.getConfig("interpol_time")
local function validate(s)
if s:match("^/org/freedesktop/UPower/devices/[%a%d_]+$") ~= nil then
return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
else
noctalia.log("Invalid battery dbus path. Check that it is correct")
end
end
local cmd = table.concat({"gdbus call --system --dest org.freedesktop.UPower --object-path",validate(battery_path),"--method org.freedesktop.UPower.Device.GetHistory charge",graph_time, "1000"}, " ")
function update()
noctalia.setUpdateInterval(interpol_time * 1000)
noctalia.runAsync(cmd, parse)
end
function parse(result)
local points = {}
for tuple in result.stdout:gmatch("%(([^%)]+)%)") do
local fields = {}
for field in tuple:gmatch("[^,]+") do
field = field:match("^%s*(.-)%s*$") -- trim whitespace
field = field:gsub("^%S+%s+", "") -- strip "uint32 " style prefix (only if followed by more content)
table.insert(fields, field)
end
local ts, value = tonumber(fields[1]), tonumber(fields[2])
if value ~= 0.0 then
points[ts] = value
end
end
local interpolated_points = interpolate(points)
render(table.concat(interpolated_points, " "), interpolated_points)
end
function interpolate(points )
local interpolated_points = {}
local tkeys = {}
for k in pairs(points) do
table.insert(tkeys, k)
end
table.sort(tkeys)
-- use the keys to retrieve the values in the sorted order
local prev_sample = 1
local first_sample_time = tkeys[1]
table.insert(interpolated_points, tonumber(points[tkeys[1]])/100)
local time_done = 0
while tkeys[prev_sample + 1] and time_done < graph_time do
local cur_time = time_done + first_sample_time
if cur_time > tkeys[prev_sample + 1] then
prev_sample += 1
end
table.insert(interpolated_points, tonumber(points[tkeys[prev_sample]])/100)
time_done += interpol_time
end
return interpolated_points
end
function render(point_str,points)
panel.render(ui.column({ gap = 8, radius = 8, align = "stretch", fill = "surface_variant" }, {
ui.row( {paddingH= 10, paddingV = 6},{ ui.label({text = noctalia.tr("text.graph_name"), fontSize = 24, fontWeight = "heavy", textAlign = "center"}),}),
ui.graph({
values = points,
color = "primary",
lineWidth = 2,
fillOpacity = 0.45,
height = 200,
width = 400
}),
}))
end
+8
View File
@@ -0,0 +1,8 @@
function update()
noctalia.setUpdateInterval(1000)
barWidget.setGlyph("battery")
end
function onClick()
noctalia.togglePanel("frai3mega/battery-graph:battery-panel")
end
+82
View File
@@ -0,0 +1,82 @@
graph_time = 3600 * noctalia.getConfig("graph_time")
battery_path = noctalia.getConfig("battery_path")
interpol_time = 60 * noctalia.getConfig("interpol_time")
local function quote(s)
return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
end
local function validate(s)
if s:match("^/org/freedesktop/UPower/devices/[%a%d_]+$") ~= nil then
return quote(s)
else
noctalia.log("Invalid battery dbus path. Check that it is correct")
end
end
local cmd = table.concat({"gdbus call --system --dest org.freedesktop.UPower --object-path",validate(battery_path),"--method org.freedesktop.UPower.Device.GetHistory charge",quote( graph_time ), "1000"}, " ")
function onOpen()
-- noctalia.setUpdateInterval(1000)
noctalia.runAsync(cmd, parse)
end
function parse(result)
local points = {}
for tuple in result.stdout:gmatch("%(([^%)]+)%)") do
local fields = {}
for field in tuple:gmatch("[^,]+") do
field = field:match("^%s*(.-)%s*$") -- trim whitespace
field = field:gsub("^%S+%s+", "") -- strip "uint32 " style prefix (only if followed by more content)
table.insert(fields, field)
end
local ts, value = tonumber(fields[1]), tonumber(fields[2])
if value ~= 0.0 then
points[ts] = value
end
end
local interpolated_points = interpolate(points)
render(table.concat(interpolated_points, " "), interpolated_points)
end
function interpolate(points )
local interpolated_points = {}
local tkeys = {}
for k in pairs(points) do
table.insert(tkeys, k)
end
table.sort(tkeys)
-- use the keys to retrieve the values in the sorted order
local prev_sample = 1
local first_sample_time = tkeys[1]
table.insert(interpolated_points, tonumber(points[tkeys[1]])/100)
local time_done = 0
while tkeys[prev_sample + 1] and time_done < graph_time do
local cur_time = time_done + first_sample_time
if cur_time > tkeys[prev_sample + 1] then
prev_sample += 1
end
table.insert(interpolated_points, tonumber(points[tkeys[prev_sample]])/100)
time_done += interpol_time
end
return interpolated_points
end
function render(point_str,points)
panel.render(ui.column({ gap = 8, radius = 8, align = "stretch", fill = "surface_variant" }, {
ui.row( {paddingH= 10, paddingV = 6},{ ui.label({text = noctalia.tr("text.graph_name"), fontSize = 24, fontWeight = "heavy", textAlign = "center"}),}),
ui.graph({
values = points,
color = "primary",
lineWidth = 2,
fillOpacity = 0.45,
height = 200,
}),
}))
end
+44
View File
@@ -0,0 +1,44 @@
id = "frai3mega/battery-graph"
name = "Battery graph"
version = "1.0.0"
author = "frai3mega"
license = "MIT"
icon = "battery"
description = "Plots battery level on a graph"
plugin_api = 3
tags = ["hardware", "utility", "desktop", "panel"]
dependencies = ["upowerd", "gdbus"]
[[setting]]
key = "graph_time"
type = "int"
label_key = "settings.graph_time.label"
description_key = "settings.graph_time.description"
default = 6
[[setting]]
key = "battery_path"
type = "string"
label_key = "settings.battery_path.label"
description_key = "settings.battery_path.description"
default = "/org/freedesktop/UPower/devices/battery_BAT0"
[[setting]]
key = "interpol_time"
type = "int"
label_key = "settings.interpol_time.label"
description_key = "settings.interpol_time.description"
default = 5
[[widget]]
id = "battery-graph-widget"
entry = "battery-graph.luau"
[[panel]]
id = "battery-panel"
entry = "battery-panel.luau"
[[desktop_widget]]
id = "battery-graph-desktop"
entry = "battery-desktop.luau"
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"settings": {
"graph_time": {
"label": "Graph timeframe",
"description": "How long of a timeframe does the graph plot in hours"
},
"battery_path": {
"label": "Battery path",
"description": "Dbus path to the upower battery device"
},
"interpol_time": {
"label": "Time between points",
"description": "How long should the thime between points be in minutes"
}
},
"text": {
"graph_name": "Battery"
}
}