Last active
January 30, 2026 11:19
-
-
Save frankkienl/be0fbac89f668d128f78b58e13ff8221 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Frankkie's test met uitlezen van stroom statistieken op Linux Mint\n", | |
| "==================================================================\n", | |
| "\n", | |
| "Ik heb op internet diverse CLI commando's gevonden om stroom statistieken uit te lezen op een Linux systeem.\n", | |
| "Hieronder een overzicht van de commando's die ik heb getest en hun output.\n", | |
| "\n", | |
| "Uiteindelijk doel:\n", | |
| "Ik wil het batterij percentage, en of de laptop aan het opladen is, kunnen uitlezen via een script.\n", | |
| "Zodat ik dit kan rapporteren aan Home Assistant. (via MQTT of iets dergelijks)\n", | |
| "\n" | |
| ], | |
| "id": "57311d9f03d1a414" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "`/sys/class/power_supply/`\n", | |
| "----------------------\n", | |
| "In de Linux filesystem is er een directory `/sys/class/power_supply/` waar informatie te vinden is over de stroomvoorziening van het systeem.\n", | |
| "We gaan deze directory uitlezen om te zien welke bestanden er zijn en wat hun inhoud is.\n", | |
| "Dit is altijd aanwezig op een Linux (Mint) systeem. En je hoeft dus geen extra pakketten te installeren." | |
| ], | |
| "id": "9509499ecce99543" | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_18_jupyter", | |
| "Line_70_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:29.064427514Z", | |
| "start_time": "2026-01-30T11:18:28.872226927Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "import java.io.File\n", | |
| "import java.io.IOException\n", | |
| "import java.util.concurrent.TimeUnit\n", | |
| "\n", | |
| "fun String.runCommandFireAndForget(workingDir: File = File(\".\")) {\n", | |
| " ProcessBuilder(*split(\" \").toTypedArray())\n", | |
| " .directory(workingDir)\n", | |
| " .redirectOutput(ProcessBuilder.Redirect.INHERIT)\n", | |
| " .redirectError(ProcessBuilder.Redirect.INHERIT)\n", | |
| " .start()\n", | |
| " .waitFor(60, TimeUnit.SECONDS)\n", | |
| "}\n", | |
| "\n", | |
| "fun String.runCommand(workingDir: File = File(\".\")): String? {\n", | |
| " try {\n", | |
| " val parts = this.split(\"\\\\s\".toRegex())\n", | |
| " val proc = ProcessBuilder(*parts.toTypedArray())\n", | |
| " .directory(workingDir)\n", | |
| " .redirectOutput(ProcessBuilder.Redirect.PIPE)\n", | |
| " .redirectError(ProcessBuilder.Redirect.PIPE)\n", | |
| " .start()\n", | |
| "\n", | |
| " proc.waitFor(60, TimeUnit.SECONDS)\n", | |
| " return proc.inputStream.bufferedReader().readText()\n", | |
| " } catch(e: IOException) {\n", | |
| " e.printStackTrace()\n", | |
| " return null\n", | |
| " }\n", | |
| "}" | |
| ], | |
| "id": "679b0efbd14f2f20", | |
| "outputs": [], | |
| "execution_count": 59 | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_42_jupyter", | |
| "Line_44_jupyter", | |
| "Line_59_jupyter", | |
| "Line_57_jupyter", | |
| "Line_58_jupyter", | |
| "Line_60_jupyter", | |
| "Line_55_jupyter", | |
| "Line_56_jupyter", | |
| "Line_39_jupyter", | |
| "Line_53_jupyter", | |
| "Line_71_jupyter", | |
| "Line_41_jupyter", | |
| "Line_69_jupyter", | |
| "Line_38_jupyter", | |
| "Line_68_jupyter", | |
| "Line_37_jupyter", | |
| "Line_54_jupyter", | |
| "Line_40_jupyter", | |
| "Line_50_jupyter", | |
| "Line_66_jupyter", | |
| "Line_80_jupyter", | |
| "Line_67_jupyter", | |
| "Line_36_jupyter", | |
| "Line_34_jupyter", | |
| "Line_52_jupyter", | |
| "Line_65_jupyter", | |
| "Line_35_jupyter", | |
| "Line_49_jupyter", | |
| "Line_51_jupyter", | |
| "Line_31_jupyter", | |
| "Line_32_jupyter", | |
| "Line_48_jupyter", | |
| "Line_62_jupyter", | |
| "Line_47_jupyter", | |
| "Line_61_jupyter", | |
| "Line_30_jupyter", | |
| "Line_33_jupyter", | |
| "Line_64_jupyter", | |
| "Line_45_jupyter", | |
| "Line_63_jupyter", | |
| "Line_46_jupyter", | |
| "Line_29_jupyter", | |
| "Line_81_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:59.565458725Z", | |
| "start_time": "2026-01-30T11:18:59.243995691Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "// ls /sys/class/power_supply/\n", | |
| "val powerSupplyPath = \"/sys/class/power_supply/\"\n", | |
| "val powerSupplyDir = File(powerSupplyPath)\n", | |
| "val powerSupplyFiles = powerSupplyDir.listFiles()\n", | |
| "if (powerSupplyFiles != null) {\n", | |
| " var count = 1\n", | |
| " println(\"Power supply devices found: ${powerSupplyFiles.size}\")\n", | |
| " for (file in powerSupplyFiles) {\n", | |
| " print(\"$count: \")\n", | |
| " print(file.name.trim())\n", | |
| " print(\" -\")\n", | |
| " print(file.path.trim())\n", | |
| " count++\n", | |
| " println()\n", | |
| " }\n", | |
| "} else {\n", | |
| " println(\"No power supply devices found.\")\n", | |
| "}" | |
| ], | |
| "id": "e4c416ee9fb65839", | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "6: ACAD -/sys/class/power_supply/ACAD\n" | |
| ] | |
| } | |
| ], | |
| "execution_count": 67 | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_28_jupyter", | |
| "Line_19_jupyter", | |
| "Line_27_jupyter", | |
| "Line_43_jupyter", | |
| "Line_72_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:29.320967591Z", | |
| "start_time": "2026-01-30T11:18:29.228809855Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "\"cat /sys/class/power_supply/BAT1/capacity\".runCommand()?.let { result ->\n", | |
| " println(\"Battery capacity: ${result.trim()}%\")\n", | |
| "}" | |
| ], | |
| "id": "80216e77ed7e3be0", | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Battery capacity: 77%\n" | |
| ] | |
| } | |
| ], | |
| "execution_count": 61 | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "`upower`\n", | |
| "----------------------\n", | |
| "\n", | |
| "Met `upower` kan je 'stroom apparaten' uitlezen.\n", | |
| "Hieronder valt de laptop-accu.\n", | |
| "En ook de batterijen van aangesloten apparaten, zoals een draadloze muis, toetsenbord of koptelefoon.\n", | |
| "Ook kan je hiermee informatie uitlezen over de USB-C poorten waarmee de accu kan worden opgeladen.\n", | |
| "Tenslotte is er ook een 'DisplayDevice' die informatie geeft over het stroomverbruik van de laptop.\n", | |
| "\n", | |
| "Eerst gaan we controleren of `upower` geïnstalleerd is.\n" | |
| ], | |
| "id": "f81f9b0c7c735bdf" | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_3_jupyter", | |
| "Line_13_jupyter", | |
| "Line_20_jupyter", | |
| "Line_21_jupyter", | |
| "Line_5_jupyter", | |
| "Line_11_jupyter", | |
| "Line_4_jupyter", | |
| "Line_9_jupyter", | |
| "Line_12_jupyter", | |
| "Line_18_jupyter", | |
| "Line_7_jupyter", | |
| "Line_17_jupyter", | |
| "Line_10_jupyter", | |
| "Line_16_jupyter", | |
| "Line_8_jupyter", | |
| "Line_19_jupyter", | |
| "Line_15_jupyter", | |
| "Line_22_jupyter", | |
| "Line_6_jupyter", | |
| "Line_23_jupyter", | |
| "Line_73_jupyter", | |
| "Line_74_jupyter", | |
| "Line_75_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:29.617236866Z", | |
| "start_time": "2026-01-30T11:18:29.323384195Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "// Controleer of upower is geïnstalleerd\n", | |
| "val upowerPath = \"which upower\".runCommand()\n", | |
| "val isUpowerInstalled = !upowerPath.isNullOrBlank()\n", | |
| "if (isUpowerInstalled) {\n", | |
| " println(\"upower is installed at: $upowerPath\")\n", | |
| "} else {\n", | |
| " println(\"upower is not installed. Please install it using 'sudo apt install upower'\")\n", | |
| "}\n", | |
| "\n" | |
| ], | |
| "id": "22d50fc18fe0278b", | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "upower is installed at: /usr/bin/upower\n", | |
| "\n" | |
| ] | |
| } | |
| ], | |
| "execution_count": 62 | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Met `upower` kan je de lijst van power apparaten opvragen.\n", | |
| "\n", | |
| "Dat gaan we doen met: `upower -e` // lijst van power apparaten\n" | |
| ], | |
| "id": "2e8ccb9fd75411f9" | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_20_jupyter", | |
| "Line_13_jupyter", | |
| "Line_21_jupyter", | |
| "Line_11_jupyter", | |
| "Line_12_jupyter", | |
| "Line_26_jupyter", | |
| "Line_18_jupyter", | |
| "Line_25_jupyter", | |
| "Line_7_jupyter", | |
| "Line_24_jupyter", | |
| "Line_19_jupyter", | |
| "Line_8_jupyter", | |
| "Line_15_jupyter", | |
| "Line_22_jupyter", | |
| "Line_6_jupyter", | |
| "Line_14_jupyter", | |
| "Line_23_jupyter", | |
| "Line_76_jupyter", | |
| "Line_77_jupyter", | |
| "Line_78_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:29.898524626Z", | |
| "start_time": "2026-01-30T11:18:29.619853146Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "var upowerBatteryDeviceName: String? = null\n", | |
| "var upowerDisplayDeviceName: String? = null\n", | |
| "\n", | |
| "if (isUpowerInstalled) {\n", | |
| " // Haal de lijst van power devices op\n", | |
| " val devicesOutput = \"upower -e\".runCommand()\n", | |
| " println(\"Power devices:\\n$devicesOutput\")\n", | |
| "\n", | |
| " // Van deze lijst, zoek naar de batterij\n", | |
| " val batteryDevices = mutableListOf<String>()\n", | |
| " devicesOutput?.lines()?.forEach { device ->\n", | |
| " // Zoek naar batterij(en)\n", | |
| " if (device.isNotBlank() && device.contains(\"battery\", ignoreCase = true)) {\n", | |
| " println(\"Battery: $device\")\n", | |
| " batteryDevices.add(device)\n", | |
| " }\n", | |
| " // Zoek naar display apparaten\n", | |
| " if (device.isNotBlank() && device.contains(\"display\", ignoreCase = true)) {\n", | |
| " upowerDisplayDeviceName = device\n", | |
| " }\n", | |
| " }\n", | |
| " upowerBatteryDeviceName = batteryDevices.firstOrNull()\n", | |
| "}" | |
| ], | |
| "id": "300372f7027701ec", | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Battery: /org/freedesktop/UPower/devices/battery_BAT1\n" | |
| ] | |
| } | |
| ], | |
| "execution_count": 63 | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Verwachtte output: (kijk of output hierboven overeenkomt)\n", | |
| "```\n", | |
| "frankkie@frankkie-frmw:~$ upower -e\n", | |
| "/org/freedesktop/UPower/devices/battery_BAT1\n", | |
| "/org/freedesktop/UPower/devices/line_power_ACAD\n", | |
| "/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o001\n", | |
| "/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o002\n", | |
| "/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o003\n", | |
| "/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o004\n", | |
| "/org/freedesktop/UPower/devices/mouse_dev_D1_00_D8_81_02_C4\n", | |
| "/org/freedesktop/UPower/devices/headset_dev_88_C9_E8_D3_8D_30\n", | |
| "/org/freedesktop/UPower/devices/DisplayDevice\n", | |
| "```" | |
| ], | |
| "id": "7af965fa7cef7630" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Als het goed is hebben we nu de naam van het batterij apparaat.\n", | |
| "Die gaan we nu gebruiken om de details van de batterij op te vragen met: `upower -i <device>`" | |
| ], | |
| "id": "b0cf7e95150d651f" | |
| }, | |
| { | |
| "metadata": { | |
| "executionRelatedData": { | |
| "compiledClasses": [ | |
| "Line_20_jupyter", | |
| "Line_21_jupyter", | |
| "Line_27_jupyter", | |
| "Line_9_jupyter", | |
| "Line_26_jupyter", | |
| "Line_18_jupyter", | |
| "Line_17_jupyter", | |
| "Line_24_jupyter", | |
| "Line_10_jupyter", | |
| "Line_16_jupyter", | |
| "Line_19_jupyter", | |
| "Line_15_jupyter", | |
| "Line_14_jupyter", | |
| "Line_23_jupyter", | |
| "Line_79_jupyter" | |
| ] | |
| }, | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:30.258142286Z", | |
| "start_time": "2026-01-30T11:18:29.922069718Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "if (isUpowerInstalled) {\n", | |
| " // Voor de batterij, haal de details op\n", | |
| " val device = upowerBatteryDeviceName ?:\"\"\n", | |
| " if (device.isNotBlank() && device.contains(\"battery\", ignoreCase = true)) {\n", | |
| " val detailsOutput = \"upower -i $device\".runCommand()\n", | |
| "\n", | |
| " // Huidge percentage eruit halen\n", | |
| " val percentage =\n", | |
| " detailsOutput?.lines()?.firstOrNull {\n", | |
| " it.trim().startsWith(\"percentage:\")\n", | |
| " }?.split(\":\")?.get(1)?.trim()\n", | |
| "\n", | |
| " // Print het percentage\n", | |
| " if (percentage != null) {\n", | |
| " println(\"Battery percentage: $percentage\")\n", | |
| " }\n", | |
| "\n", | |
| " // Kijken of de batterij aan het opladen is\n", | |
| " val state = detailsOutput?.lines()?.firstOrNull {\n", | |
| " it.trim().startsWith(\"state:\")\n", | |
| " }?.split(\":\")?.get(1)?.trim()\n", | |
| " val energyRate = detailsOutput?.lines()?.firstOrNull {\n", | |
| " it.trim().startsWith(\"energy-rate:\")\n", | |
| " }?.split(\":\")?.get(1)?.trim()\n", | |
| " if (state != null) {\n", | |
| " println(\"Battery state: $state\")\n", | |
| " if (state.equals(\"charging\", ignoreCase = true)) {\n", | |
| " println(\"Battery is charging at rate: $energyRate\")\n", | |
| " }\n", | |
| " }\n", | |
| " }\n", | |
| "\n", | |
| " // Voor het display apparaat, haal de details op\n", | |
| " val displayDevice = upowerDisplayDeviceName ?:\"\"\n", | |
| " if (displayDevice.isNotBlank() && displayDevice.contains(\"display\", ignoreCase = true)) {\n", | |
| " val displayDetailsOutput = \"upower -i $displayDevice\".runCommand()\n", | |
| " // Zoek naar 'time to full'\n", | |
| " val timeToFull = displayDetailsOutput?.lines()?.firstOrNull {\n", | |
| " it.trim().startsWith(\"time to full:\")\n", | |
| " }?.split(\":\")?.get(1)?.trim()\n", | |
| " if (timeToFull != null) {\n", | |
| " println(\"Display time to full: $timeToFull\")\n", | |
| " }\n", | |
| " }\n", | |
| "}" | |
| ], | |
| "id": "f2449af1632576f2", | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Battery percentage: 77%\n", | |
| "Battery state: discharging\n" | |
| ] | |
| } | |
| ], | |
| "execution_count": 64 | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "We hebben nu het batterij percentage en de status van de batterij kunnen uitlezen met `upower`.\n", | |
| "\n", | |
| "\n", | |
| "Verwachtte output (voorbeeld): (Kijk of output hierboven overeenkomt)\n", | |
| "```\n", | |
| "Battery percentage: 58%\n", | |
| "Battery state: charging\n", | |
| "Battery is charging at rate: 38,4368 W\n", | |
| "Display time to full: 57,2 minutes\n", | |
| "```\n", | |
| "\n", | |
| "Output kan verschillen afhankelijk van of de laptop aan het opladen is of niet." | |
| ], | |
| "id": "843aea97615849bb" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "TODO: `acpi`\n", | |
| "----------------------" | |
| ], | |
| "id": "73d2ed3f2dacc238" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": "", | |
| "id": "d828715ca9005a9" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "TODO: `Powerstat`\n", | |
| "----------------------" | |
| ], | |
| "id": "38903d238fdbff9d" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "TODO: `turbostat`\n", | |
| "----------------------" | |
| ], | |
| "id": "2e495ecd41631755" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "end_time": "2026-01-30T11:18:30.269704412Z", | |
| "start_time": "2026-01-30T11:18:30.262138571Z" | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": "", | |
| "id": "bf1adafa36341fc4", | |
| "outputs": [], | |
| "execution_count": 65 | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "Kotlin", | |
| "language": "kotlin", | |
| "name": "kotlin" | |
| }, | |
| "language_info": { | |
| "name": "kotlin", | |
| "version": "2.2.20", | |
| "mimetype": "text/x-kotlin", | |
| "file_extension": ".kt", | |
| "pygments_lexer": "kotlin", | |
| "codemirror_mode": "text/x-kotlin", | |
| "nbconvert_exporter": "" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 5 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment