diff --git a/battery-graph/README.md b/battery-graph/README.md new file mode 100644 index 0000000..74dc063 --- /dev/null +++ b/battery-graph/README.md @@ -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. | + +``` + diff --git a/battery-graph/battery-desktop.luau b/battery-graph/battery-desktop.luau new file mode 100644 index 0000000..310c507 --- /dev/null +++ b/battery-graph/battery-desktop.luau @@ -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 + diff --git a/battery-graph/battery-graph.luau b/battery-graph/battery-graph.luau new file mode 100644 index 0000000..5b61823 --- /dev/null +++ b/battery-graph/battery-graph.luau @@ -0,0 +1,8 @@ +function update() + noctalia.setUpdateInterval(1000) + barWidget.setGlyph("battery") +end + +function onClick() + noctalia.togglePanel("frai3mega/battery-graph:battery-panel") +end diff --git a/battery-graph/battery-panel.luau b/battery-graph/battery-panel.luau new file mode 100644 index 0000000..022e19a --- /dev/null +++ b/battery-graph/battery-panel.luau @@ -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 + diff --git a/battery-graph/plugin.toml b/battery-graph/plugin.toml new file mode 100644 index 0000000..b0fcdf9 --- /dev/null +++ b/battery-graph/plugin.toml @@ -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" diff --git a/battery-graph/thumbnail.webp b/battery-graph/thumbnail.webp new file mode 100644 index 0000000..6f336c9 Binary files /dev/null and b/battery-graph/thumbnail.webp differ diff --git a/battery-graph/translations/en.json b/battery-graph/translations/en.json new file mode 100644 index 0000000..0bbccf4 --- /dev/null +++ b/battery-graph/translations/en.json @@ -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" + } +}