Merge branch 'dev'

This commit is contained in:
NGnius (Graham) 2022-05-02 20:53:30 -04:00
commit 733681a6d9
4 changed files with 393 additions and 5 deletions

View file

@ -1,14 +1,15 @@
# PowerTools
![plugin_demo](https://raw.githubusercontent.com/NGnius/PowerTools/master/extras/ui.png)
![plugin_demo](./extras/ui.png)
Steam Deck power tweaks for power users.
This is generated from the template plugin for the [SteamOS Plugin Loader](https://github.com/SteamDeckHomebrew/PluginLoader).
You will need that installed for this plugin to work.
## Cool, whatever
## Cool, but that's too much work
Yeah, that's fair.
Fair enough.
In case you still want some of the functionality, without the nice GUI, here's some equivalent commands.
These should all be run as superuser, i.e. run `sudo su` and then run these commands in that.
@ -32,6 +33,43 @@ Use `cpupower` (usage: `cpupower --help`).
This isn't strictly how PowerTools does it, but it's a multi-step process which can involve changing the CPU governor.
All that can be done automatically by `cpupower frequency-set --freq {frequency}` where `{frequency}` is `1.7G`, `2.4G` or `2.8G`.
### Set GPU Power
Set Slow Powerplay Table (PPT):`echo {microwatts} > /sys/class/hwmon/hwmon4/power1_cap` where `{microwatts}` is a wattage in millionths of a Watt. This doesn't seem to do a lot.
Set Fast Powerplay Table (PPT): `echo {microwatts} > /sys/class/hwmon/hwmon4/power2_cap` where `{microwatts}` is a wattage in millionths of a Watt.
Get the entry limits for those two commands with `cat /sys/class/hwmon/hwmon4/power{number}_cap_max` where `{number}` is `1` (slowPPT) or `2` (fastPPT).
### Set Fan speed
Enable automatic control: `echo 0 > /sys/class/hwmon/hwmon5/recalculate` enables automatic fan control.
Disable automatic control: `echo 1 > /sys/class/hwmon/hwmon5/recalculate` disables automatic (temperature-based) fan control and starts using the set fan target instead.
Set the fan speed: `echo {rpm} > /sys/class/hwmon/hwmon5/fan1_target` where `{rpm}` is the RPM.
Read the actual fan RPM: `cat /sys/class/hwmon/hwmon5/fan1_input` gives the fan speed.
NOTE: There's a bug in the fan controller; if you enable automatic fan control it will forget any previously-set target despite it appearing to be set correctly (i.e. `cat /sys/class/hwmon/hwmon5/fan1_target` will display the correct value).
When you disable automatic fan control, you will need to set the fan RPM again.
### Battery stats
Get the battery charge right now: `cat /sys/class/hwmon/hwmon2/device/charge_now` gives charge in uAh (uAh * 7.7/1000000 = charge in Wh).
Get the maximum battery capacity: `cat /sys/class/hwmon/hwmon2/device/charge_full` gives charge in uAh.
Get the design battery capacity: `cat /sys/class/hwmon/hwmon2/device/charge_full_design` gives charge in uAh.
Get whether the deck is plugged in: `cat /sys/class/hwmon/hwmon5/curr1_input` gives the charger current in mA.
### Steam Deck kernel patches
This is how I figured out how the fan stuff works.
I've only scratched the surface of what this code allows, I'm sure it has more useful information.
https://lkml.org/lkml/2022/2/5/391
## License
This is licensed under GNU GPLv3.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 180 KiB

74
main.py
View file

@ -1,8 +1,19 @@
import time
#import subprocess
VERSION = "0.3.0"
class Plugin:
CPU_COUNT = 8
SCALING_FREQUENCIES = [1700000, 2400000, 2800000]
FAN_SPEEDS = [0, 1000, 2000, 3000, 4000, 5000, 6000]
auto_fan = True
async def get_version(self) -> str:
return VERSION
# CPU stuff
# call from main_view.html with setCPUs(count, smt)
async def set_cpus(self, count, smt=True) -> int:
@ -32,7 +43,10 @@ class Plugin:
return online_count
async def get_smt(self) -> bool:
return status_cpu(1) == status_cpu(2) and status_cpu(3) == status_cpu(4)
for cpu in range(1, self.CPU_COUNT, 2):
if (not status_cpu(cpu)) and status_cpu(cpu+1):
return False
return True
async def set_boost(self, enabled: bool) -> bool:
write_to_sys("/sys/devices/system/cpu/cpufreq/boost", int(enabled))
@ -66,6 +80,61 @@ class Plugin:
freq = int(freq_maybe)
return self.SCALING_FREQUENCIES.index(freq)
# GPU stuff
async def set_gpu_power(self, value: int, power_number: int) -> bool:
write_to_sys(gpu_power_path(power_number), value)
return True
async def get_gpu_power(self, power_number: int) -> int:
return int(read_from_sys(gpu_power_path(power_number), amount=-1).strip())
# Fan stuff
async def set_fan_tick(self, tick: int):
if tick >= len(self.FAN_SPEEDS):
# automatic mode
self.auto_fan = True
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 0)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", 4099) # 4099 is default
#subprocess.run(["systemctl", "start", "jupiter-fan-control.service"])
else:
# manual voltage
self.auto_fan = False
write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1)
write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", self.FAN_SPEEDS[tick])
#subprocess.run(["systemctl", "stop", "jupiter-fan-control.service"])
async def get_fan_tick(self) -> int:
fan_target = int(read_from_sys("/sys/class/hwmon/hwmon5/fan1_target", amount=-1).strip())
fan_input = int(read_from_sys("/sys/class/hwmon/hwmon5/fan1_input", amount=-1).strip())
fan_target_v = float(fan_target) / 1000
fan_input_v = float(fan_input) / 1000
if self.auto_fan:
return len(self.FAN_SPEEDS)
elif fan_target == 4099 or (int(round(fan_target_v)) != int(round(fan_input_v)) and fan_target not in self.FAN_SPEEDS):
# cannot read /sys/class/hwmon/hwmon5/recalculate, so guess based on available fan info
# NOTE: the fan takes time to ramp up, so fan_target will never approximately equal fan_input
# when fan_target was changed recently (hence set voltage caching)
return len(self.FAN_SPEEDS)
else:
# quantize voltage to nearest tick (price is right rules; closest without going over)
for i in range(len(self.FAN_SPEEDS)-1):
if fan_target <= self.FAN_SPEEDS[i]:
return i
return len(self.FAN_SPEEDS)-1 # any higher value is considered as highest manual setting
# Battery stuff
async def get_charge_now(self) -> int:
return int(read_from_sys("/sys/class/hwmon/hwmon2/device/charge_now", amount=-1).strip())
async def get_charge_full(self) -> int:
return int(read_from_sys("/sys/class/hwmon/hwmon2/device/charge_full", amount=-1).strip())
async def get_charge_design(self) -> int:
return int(read_from_sys("/sys/class/hwmon/hwmon2/device/charge_full_design", amount=-1).strip())
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded
async def _main(self):
pass
@ -81,6 +150,9 @@ def cpu_freq_scaling_path(cpu_number: int) -> str:
def cpu_governor_scaling_path(cpu_number: int) -> str:
return f"/sys/devices/system/cpu/cpu{cpu_number}/cpufreq/scaling_governor"
def gpu_power_path(power_number: int) -> str:
return f"/sys/class/hwmon/hwmon4/power{power_number}_cap"
def write_to_sys(path, value: int):
with open(path, mode="w") as f:

View file

@ -6,6 +6,10 @@
<script src="/static/library.js"></script>
<script>
// Python functions
function getVersion() {
return call_plugin_method("get_version", {});
}
function setCPUs(value, smt) {
return call_plugin_method("set_cpus", {"count":value, "smt": smt});
}
@ -33,6 +37,34 @@
function getMaxBoost() {
return call_plugin_method("get_max_boost", {});
}
function setGPUPower(value, index) {
return call_plugin_method("set_gpu_power", {"value": value, "power_number": index});
}
function getGPUPower(index) {
return call_plugin_method("get_gpu_power", {"power_number": index});
}
function setFanTick(tick) {
return call_plugin_method("set_fan_tick", {"tick": tick});
}
function getFanTick() {
return call_plugin_method("get_fan_tick", {});
}
function getChargeNow() {
return call_plugin_method("get_charge_now", {});
}
function getChargeFull() {
return call_plugin_method("get_charge_full", {});
}
function getChargeDesign() {
return call_plugin_method("get_charge_design", {});
}
// other logic
@ -42,6 +74,11 @@
setToggleState(document.getElementById("smtToggle"), await getSMT());
selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8);
selectNotch("frequencyNotch", await getMaxBoost(), 3);
await onReadyGPU();
selectNotch("fanNotch", await getFanTick(), 8);
await updateBatteryStats();
// this is unimportant; always do it last
await updateVersion();
}
async function setCPUNotch(index) {
@ -93,6 +130,62 @@
selectNotch(ROOT_ID, await getMaxBoost(), 3);
}
async function onSetFanNotch(index) {
const ROOT_ID = "fanNotch";
await setFanTick(index);
selectNotch(ROOT_ID, index, 8);
}
async function onReadyGPU() {
let power1_cap = await getGPUPower(1);
let power2_cap = await getGPUPower(2);
if (power1_cap <= 0) {
selectNotch("slowPPTNotch", 0, 3);
document.getElementById("slowPPTAutoDefault").innerText = "Default";
} else if (power1_cap > 15000000) {
selectNotch("slowPPTNotch", 2, 3);
document.getElementById("slowPPTAutoDefault").innerText = "Default";
} else {
selectNotch("slowPPTNotch", 1, 3);
}
if (power2_cap <= 0) {
selectNotch("fastPPTNotch", 0, 3);
document.getElementById("fastPPTAutoDefault").innerText = "Default";
} else if (power2_cap > 15000000) {
selectNotch("fastPPTNotch", 2, 3);
document.getElementById("fastPPTAutoDefault").innerText = "Default";
} else {
selectNotch("fastPPTNotch", 1, 3);
}
}
async function onSetSlowPPTNotch(index) {
const ROOT_ID = "slowPPTNotch";
document.getElementById("slowPPTAutoDefault").innerText = "Default";
if (index == 0) {
await setGPUPower(0, 1);
} else if (index == 1) {
await setGPUPower(15000000, 1);
} else {
await setGPUPower(29000000, 1);
}
selectNotch(ROOT_ID, index, 3);
}
async function onSetFastPPTNotch(index) {
const ROOT_ID = "fastPPTNotch";
document.getElementById("fastPPTAutoDefault").innerText = "Default";
if (index == 0) {
await setGPUPower(0, 2);
} else if (index == 1) {
await setGPUPower(15000000, 2);
} else {
await setGPUPower(30000000, 2);
}
selectNotch(ROOT_ID, index, 3);
}
function selectNotch(rootId, index, elements) {
// WARNING: this yeets any style in div of slider
const ENABLED_CLASS = "gamepadslider_TickActive_j418S";
@ -108,11 +201,37 @@
}
}
}
async function updateBatteryStats() {
//console.log("Updating battery stats");
let batCapacityNow = document.getElementById("batCapacityNow");
let batCapacityFull = document.getElementById("batCapacityFull");
let chargeNow = await getChargeNow();
let chargeFull = await getChargeFull();
let chargeDesign = await getChargeDesign();
batCapacityNow.innerText = (7.7 * chargeNow / 1000000).toFixed(2).toString() + " Wh (" + (100 * chargeNow / chargeFull).toFixed(0).toString() + "%)";
batCapacityFull.innerText = (7.7 * chargeFull / 1000000).toFixed(2).toString() + " Wh (" + (100 * chargeFull / chargeDesign).toFixed(0).toString() + "%)";
}
let versionCount = -1;
async function updateVersion() {
let version = await getVersion();
let target = document.getElementById("versionStr");
target.innerText = "v" + version;
if (versionCount >= 9) {
target.innerText += " by NGnius ;) ";
versionCount = 0;
} else {
versionCount += 1;
}
}
</script>
<style type="text/css" media="screen"></style>
</head>
<body onload="onReady()">
<body onload="onReady()" style="/*margin:0px;padding:0px;*/overflow-x:hidden;">
<!-- CPU -->
<!-- SMT toggle switch, roughly copied from https://github.com/SteamDeckHomebrew/ExtraSettingsPlugin/blob/main/main_view.html -->
<!-- Due to a bug in MangoHud, this has been hidden for now -->
<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable" style="display:none;">
@ -130,10 +249,12 @@
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_1W1to">Disables odd-numbered CPUs</div>
</div>
</div>
</div>
</div>
<!-- CPUs selector -->
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
@ -199,10 +320,12 @@
</div>
</div>
</div>
<div class="gamepaddialog_FieldDescription_1W1to">Allows the CPU to go above max frequency</div>
</div>
</div>
</div>
</div>
<!-- Frequency selector -->
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
@ -236,5 +359,160 @@
WARNING: This will change the CPU governor.
</div>
</div>
<!-- GPU -->
<!-- SlowPPT power limit (number 1) -->
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">GPU SlowPPT Power</div>
</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div id="slowPPTNotch" class="gamepadslider_SliderControlAndNotches_23hjX Focusable" tabindex="0" style="--normalized-slider-value:0.33;">
<div class="gamepadslider_SliderControl_1udlG">
<div class="gamepadslider_SliderTrack_2_vG6 gamepadslider_SliderHasNotches_1Lr71 "></div>
<div class="gamepadslider_SliderHandleContainer_8xNY6">
<div class="gamepadslider_SliderHandle_11PBf"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2yM7S Panel Focusable">
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="slowPPTNotch0" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">0</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="slowPPTNotch1" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW" id="slowPPTAutoDefault">Auto</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="slowPPTNotch2" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetSlowPPTNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">Max</div>
</div>
</div>
</div>
</div>
</div>
<!-- FastPPT power limit (number 2) -->
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">GPU FastPPT Power</div>
</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div id="fastPPTNotch" class="gamepadslider_SliderControlAndNotches_23hjX Focusable" tabindex="0" style="--normalized-slider-value:0.33;">
<div class="gamepadslider_SliderControl_1udlG">
<div class="gamepadslider_SliderTrack_2_vG6 gamepadslider_SliderHasNotches_1Lr71 "></div>
<div class="gamepadslider_SliderHandleContainer_8xNY6">
<div class="gamepadslider_SliderHandle_11PBf"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2yM7S Panel Focusable">
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fastPPTNotch0" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">0</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fastPPTNotch1" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW" id="fastPPTAutoDefault">Auto</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fastPPTNotch2" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFastPPTNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">Max</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fan RPM selector -->
<!-- TODO: Make this non-notched slider when PluginLoader PR#41 is merged -->
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">Fan RPM</div>
</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div id="fanNotch" class="gamepadslider_SliderControlAndNotches_23hjX Focusable" tabindex="0" style="--normalized-slider-value:0.33;">
<div class="gamepadslider_SliderControl_1udlG">
<div class="gamepadslider_SliderTrack_2_vG6 gamepadslider_SliderHasNotches_1Lr71 "></div>
<div class="gamepadslider_SliderHandleContainer_8xNY6">
<div class="gamepadslider_SliderHandle_11PBf"></div>
</div>
</div>
<div class="gamepadslider_SliderNotchContainer_2yM7S Panel Focusable">
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch0" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(0)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">0</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch1" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(1)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">1K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch2" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(2)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">2K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch3" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(3)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">3K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch4" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(4)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">4K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch5" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(5)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">5K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch6" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(6)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">6K</div>
</div>
<div class="gamepadslider_SliderNotch_LYPXt">
<div id="fanNotch7" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(7)'></div>
<div class="gamepadslider_SliderNotchLabel_dbACW">Auto</div>
</div>
</div>
</div>
</div>
</div>
<!-- Battery Info -->
<div class="quickaccesscontrols_PanelSection_3gY0a" onclick="updateBatteryStats()" style="margin-bottom:0px;">
<div class="quickaccesscontrols_PanelSectionTitle_1IigU">
<div class="quickaccesscontrols_Text_1cokl">Battery</div>
</div>
<div class="Panel Focusable" tabindex="0">
<div class="quickaccesscontrols_PanelSectionRow_3LM_Z">
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">Now (Charge%)</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div class="gamepaddialog_LabelFieldValue_3pteV" id="batCapacityNow"> :'( (|-_-|) </div>
</div>
</div>
</div>
</div>
<div class="quickaccesscontrols_PanelSectionRow_3LM_Z">
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;padding-left:0px;padding-right:0px;">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">Max (Health%)</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div class="gamepaddialog_LabelFieldValue_3pteV" id="batCapacityFull"> 9000+ (420%) </div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="quickaccesscontrols_PanelSectionRow_3LM_Z" onclick="updateVersion()">
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable" style="--indent-level:0;">
<div class="gamepaddialog_FieldLabelRow_2VcTl">
<div class="gamepaddialog_FieldLabel_3jMlJ">PowerTools</div>
<div class="gamepaddialog_FieldChildren_2rhav">
<div class="gamepaddialog_LabelFieldValue_3pteV" id="versionStr"> v0.42.0 </div>
</div>
</div>
</div>
</div>
</body>
</html>