109 lines
2.7 KiB
Python
109 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Set OneXPlayer Super X TDP limits through ryzenadj.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
MIN_WATTS = 3
|
|
MAX_WATTS = 120
|
|
MAX_BATTERY_WATTS = 80
|
|
LOCAL_RYZENADJ = Path("/usr/local/bin/ryzenadj")
|
|
AC_ONLINE_PATH = Path("/sys/class/power_supply/ACAD/online")
|
|
BATTERY_STATUS_PATH = Path("/sys/class/power_supply/BAT0/status")
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description="Set TDP through ryzenadj")
|
|
parser.add_argument("watts", type=int)
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
parser.add_argument("--json", action="store_true")
|
|
return parser.parse_args()
|
|
|
|
|
|
def ryzenadj_path() -> str:
|
|
if LOCAL_RYZENADJ.exists():
|
|
return str(LOCAL_RYZENADJ)
|
|
found = shutil.which("ryzenadj")
|
|
if found:
|
|
return found
|
|
raise FileNotFoundError("ryzenadj not found")
|
|
|
|
|
|
def emit(payload, as_json: bool):
|
|
if as_json:
|
|
print(json.dumps(payload, indent=2))
|
|
else:
|
|
print(payload)
|
|
|
|
|
|
def power_source() -> str:
|
|
try:
|
|
online = AC_ONLINE_PATH.read_text(encoding="utf-8").strip()
|
|
if online == "1":
|
|
return "ac"
|
|
if online == "0":
|
|
return "battery"
|
|
except OSError:
|
|
pass
|
|
|
|
try:
|
|
status = BATTERY_STATUS_PATH.read_text(encoding="utf-8").strip()
|
|
if status == "Discharging":
|
|
return "battery"
|
|
except OSError:
|
|
pass
|
|
|
|
return "unknown"
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
if args.watts < MIN_WATTS or args.watts > MAX_WATTS:
|
|
raise ValueError(f"watts must be in range {MIN_WATTS}-{MAX_WATTS}")
|
|
source = power_source()
|
|
if source == "battery" and args.watts > MAX_BATTERY_WATTS:
|
|
raise ValueError(
|
|
f"battery mode limit is {MAX_BATTERY_WATTS}W, requested {args.watts}W"
|
|
)
|
|
ryzenadj = ryzenadj_path()
|
|
milliwatts = args.watts * 1000
|
|
cmd = [
|
|
ryzenadj,
|
|
"--stapm-limit",
|
|
str(milliwatts),
|
|
"--fast-limit",
|
|
str(milliwatts),
|
|
"--slow-limit",
|
|
str(milliwatts),
|
|
]
|
|
payload = {
|
|
"watts": args.watts,
|
|
"milliwatts": milliwatts,
|
|
"power_source": source,
|
|
"backend": ryzenadj,
|
|
"command": cmd,
|
|
}
|
|
if args.dry_run:
|
|
payload["status"] = "dry-run"
|
|
emit(payload, args.json)
|
|
return
|
|
subprocess.run(cmd, check=True)
|
|
payload["status"] = "applied"
|
|
emit(payload, args.json)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except (FileNotFoundError, subprocess.CalledProcessError, ValueError) as exc:
|
|
print(f"Error: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|