onexplayer-superxcontrol/oxp-tdp

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)