refactor: sane-sysinfo: clean up a bit more
This commit is contained in:
parent
df0a8cf900
commit
a63f6281c5
|
@ -68,21 +68,65 @@ class Formatter:
|
|||
suffix_hr: str = SUFFIX_HR
|
||||
suffix_min: str = SUFFIX_MIN
|
||||
|
||||
@dataclass
|
||||
class ParsedPowerSupply:
|
||||
class PowerSupply:
|
||||
"""
|
||||
near-direct values from /sys/class/power_supply API endpoints
|
||||
reads values from /sys/class/power_supply/$dev/ API
|
||||
"""
|
||||
percent_charged: int | None = None
|
||||
# unitless: could be joules, could be something else
|
||||
charge_full: int | None = None
|
||||
# charge per hour
|
||||
charge_rate: int | None = None
|
||||
def __init__(self, sysfs_node: str):
|
||||
self.sysfs_node = sysfs_node
|
||||
self._cached_reads = {}
|
||||
|
||||
def try_read(self, rel_path: str) -> str | None:
|
||||
if rel_path not in self._cached_reads:
|
||||
self._cached_reads[rel_path] = self.try_read_uncached(rel_path)
|
||||
|
||||
return self._cached_reads[rel_path]
|
||||
|
||||
def try_read_uncached(self, rel_path: str) -> str | None:
|
||||
try:
|
||||
v = open(f"{self.sysfs_node}/{rel_path}").read()
|
||||
logger.debug(f"${self.sysfs_node}/{rel_path}: {v}")
|
||||
return v
|
||||
except:
|
||||
return None
|
||||
|
||||
def try_read_int(self, rel_path: str) -> int | None:
|
||||
s = self.try_read(rel_path)
|
||||
if s is None: return None
|
||||
return int(s)
|
||||
|
||||
@property
|
||||
def capacity(self) -> int | None:
|
||||
return self.try_read_int("capacity") # percent
|
||||
|
||||
@property
|
||||
def charge_full_design(self) -> int | None:
|
||||
return self.try_read_int("charge_full_design") # micro-Ah
|
||||
|
||||
@property
|
||||
def current_now(self) -> int | None:
|
||||
return self.try_read_int("current_now") # micro-A
|
||||
|
||||
@property
|
||||
def energy_full(self) -> int | None:
|
||||
return self.try_read_int("energy_full") # micro-Wh
|
||||
|
||||
# @property
|
||||
# def energy_now(self) -> int | None:
|
||||
# return self.try_read_int("energy_now") # micro-Wh
|
||||
|
||||
@property
|
||||
def power_now(self) -> int | None:
|
||||
return self.try_read_int("power_now") # micro-W (?)
|
||||
|
||||
@property
|
||||
def status(self) -> str | None:
|
||||
return self.try_read("status") # "Charging"/"Discharging"/"Not charging"
|
||||
|
||||
# @property
|
||||
# def voltage_now(self) -> int | None:
|
||||
# return self.try_read_int("voltage_now") # micro-V
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.percent_charged is not None and \
|
||||
self.charge_full is not None and \
|
||||
self.charge_rate is not None
|
||||
|
||||
class BatteryInfo:
|
||||
"""
|
||||
|
@ -92,21 +136,28 @@ class BatteryInfo:
|
|||
minutes_to_charged: int | None = None
|
||||
minutes_to_discharged: int | None = None
|
||||
|
||||
def __init__(self, p: ParsedPowerSupply):
|
||||
assert p.percent_charged is not None
|
||||
self.percent_charged = p.percent_charged
|
||||
def __init__(self, capacity: int, charges_per_hour: float | None, status: str | None):
|
||||
self.percent_charged = capacity
|
||||
|
||||
if p.charge_rate is not None and p.charge_rate < 0:
|
||||
# correct some batteries which flip signs in places
|
||||
if status.lower().strip() == "charging" and charges_per_hour:
|
||||
charges_per_hour = abs(charges_per_hour)
|
||||
logger.debug("status==charging => forcing charges_per_hour positive")
|
||||
elif status.lower().strip() == "discharging" and charges_per_hour:
|
||||
charges_per_hour = -abs(charges_per_hour)
|
||||
logger.debug("status==discharging => forcing charges_per_hour negative")
|
||||
|
||||
if charges_per_hour is not None and charges_per_hour < 0:
|
||||
self.minutes_to_discharged = int(
|
||||
60
|
||||
* p.charge_full * self.percent_charged/100
|
||||
/ -p.charge_rate
|
||||
* self.percent_charged/100
|
||||
/ -charges_per_hour
|
||||
)
|
||||
if p.charge_full is not None and p.charge_rate is not None and p.charge_rate > 0:
|
||||
if charges_per_hour is not None and charges_per_hour > 0:
|
||||
self.minutes_to_charged = int(
|
||||
60
|
||||
* p.charge_full * (100-self.percent_charged)/100
|
||||
/ p.charge_rate * 60
|
||||
* (100-self.percent_charged)/100
|
||||
/ charges_per_hour
|
||||
)
|
||||
|
||||
|
||||
|
@ -124,56 +175,24 @@ def render_icon(direction: ChargeDirection, percentage: float) -> str:
|
|||
|
||||
raise RuntimeError(f"invalid ChargeDirection {direction}")
|
||||
|
||||
def try_path(p: str) -> ParsedPowerSupply | None:
|
||||
def try_path(p: str) -> BatteryInfo | None:
|
||||
"""
|
||||
try to read battery information from some p = "/sys/class/power_supply/$node" path
|
||||
"""
|
||||
state = ParsedPowerSupply()
|
||||
try:
|
||||
capacity_text = open(f"{p}/capacity").read()
|
||||
except: pass
|
||||
else:
|
||||
logger.debug(f"read from {p}/capacity: {capacity_text}")
|
||||
state.percent_charged = int(capacity_text)
|
||||
ps = PowerSupply(p)
|
||||
if ps.capacity is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
charge_full_design_text = open(f"{p}/charge_full_design").read()
|
||||
current_now_text = open(f"{p}/current_now").read()
|
||||
except: pass
|
||||
else:
|
||||
logger.debug(f"read from {p}/charge_full_design: {charge_full_design_text}")
|
||||
logger.debug(f"read from {p}/charge_now: {charge_now_text}")
|
||||
state.charge_full = int(charge_full_design_text)
|
||||
if ps.charge_full_design and ps.current_now is not None:
|
||||
# current_now is positive when charging
|
||||
state.charge_rate = int(current_now_text)
|
||||
|
||||
if state.is_valid(): return state
|
||||
|
||||
try:
|
||||
energy_full_text = open(f"{p}/energy_full").read()
|
||||
power_now_text = open(f"{p}/power_now").read()
|
||||
except: pass
|
||||
else:
|
||||
logger.debug(f"read from {p}/energy_full: {energy_full_text}")
|
||||
logger.debug(f"read from {p}/power_now: {power_now_text}")
|
||||
state.charge_full = int(energy_full_text)
|
||||
charges_per_hour = ps.current_now / ps.charge_full_design
|
||||
elif ps.energy_full and ps.power_now is not None:
|
||||
# power_now is positive when discharging
|
||||
state.charge_rate = -int(power_now_text)
|
||||
|
||||
if state.is_valid(): return state
|
||||
|
||||
try:
|
||||
energy_full_text = open(f"{p}/energy_full").read()
|
||||
energy_now_text = open(f"{p}/energy_now").read()
|
||||
except: pass
|
||||
charges_per_hour = -ps.power_now / ps.energy_full
|
||||
else:
|
||||
logger.debug(f"read from {p}/energy_full: {energy_full_text}")
|
||||
logger.debug(f"read from {p}/energy_now: {energy_now_text}")
|
||||
state.charge_full = int(energy_full_text)
|
||||
# energy_now is positive when discharging
|
||||
state.charge_rate = -int(energy_now_text)
|
||||
charges_per_hour = None
|
||||
|
||||
return state if state.percent_charged is not None else None
|
||||
return BatteryInfo(ps.capacity, charges_per_hour, ps.status)
|
||||
|
||||
def try_all_paths() -> BatteryInfo | None:
|
||||
p = try_path("/sys/class/power_supply/axp20x-battery") # Pinephone
|
||||
|
@ -181,11 +200,10 @@ def try_all_paths() -> BatteryInfo | None:
|
|||
p = try_path("/sys/class/power_supply/BAT0") # Thinkpad
|
||||
|
||||
logger.debug(f"perc: {p.percent_charged if p else None}")
|
||||
logger.debug(f"full: {p.charge_full if p else None}, rate: {p.charge_rate if p else None}")
|
||||
logger.debug(" rate > 0 means charging, else discharging")
|
||||
logger.debug(f"charge: {p.percent_charged if p else None}")
|
||||
logger.debug(f"min-to-charge: {p.minutes_to_charged if p else None}, min-to-discharge: {p.minutes_to_discharged if p else None}")
|
||||
|
||||
if p.percent_charged is not None:
|
||||
return BatteryInfo(p)
|
||||
return p
|
||||
|
||||
def fmt_minutes(f: Formatter, icon: str, if_indefinite: str, minutes: int | None) -> str:
|
||||
logger.debug(f"charge/discharge time: {minutes} min")
|
||||
|
|
Loading…
Reference in New Issue
Block a user