refactor: sane-sysinfo: clean up a bit more
This commit is contained in:
@@ -68,21 +68,65 @@ class Formatter:
|
|||||||
suffix_hr: str = SUFFIX_HR
|
suffix_hr: str = SUFFIX_HR
|
||||||
suffix_min: str = SUFFIX_MIN
|
suffix_min: str = SUFFIX_MIN
|
||||||
|
|
||||||
@dataclass
|
class PowerSupply:
|
||||||
class ParsedPowerSupply:
|
|
||||||
"""
|
"""
|
||||||
near-direct values from /sys/class/power_supply API endpoints
|
reads values from /sys/class/power_supply/$dev/ API
|
||||||
"""
|
"""
|
||||||
percent_charged: int | None = None
|
def __init__(self, sysfs_node: str):
|
||||||
# unitless: could be joules, could be something else
|
self.sysfs_node = sysfs_node
|
||||||
charge_full: int | None = None
|
self._cached_reads = {}
|
||||||
# charge per hour
|
|
||||||
charge_rate: int | None = None
|
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:
|
class BatteryInfo:
|
||||||
"""
|
"""
|
||||||
@@ -92,21 +136,28 @@ class BatteryInfo:
|
|||||||
minutes_to_charged: int | None = None
|
minutes_to_charged: int | None = None
|
||||||
minutes_to_discharged: int | None = None
|
minutes_to_discharged: int | None = None
|
||||||
|
|
||||||
def __init__(self, p: ParsedPowerSupply):
|
def __init__(self, capacity: int, charges_per_hour: float | None, status: str | None):
|
||||||
assert p.percent_charged is not None
|
self.percent_charged = capacity
|
||||||
self.percent_charged = p.percent_charged
|
|
||||||
|
|
||||||
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(
|
self.minutes_to_discharged = int(
|
||||||
60
|
60
|
||||||
* p.charge_full * self.percent_charged/100
|
* self.percent_charged/100
|
||||||
/ -p.charge_rate
|
/ -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(
|
self.minutes_to_charged = int(
|
||||||
60
|
60
|
||||||
* p.charge_full * (100-self.percent_charged)/100
|
* (100-self.percent_charged)/100
|
||||||
/ p.charge_rate * 60
|
/ charges_per_hour
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -124,56 +175,24 @@ def render_icon(direction: ChargeDirection, percentage: float) -> str:
|
|||||||
|
|
||||||
raise RuntimeError(f"invalid ChargeDirection {direction}")
|
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
|
try to read battery information from some p = "/sys/class/power_supply/$node" path
|
||||||
"""
|
"""
|
||||||
state = ParsedPowerSupply()
|
ps = PowerSupply(p)
|
||||||
try:
|
if ps.capacity is None:
|
||||||
capacity_text = open(f"{p}/capacity").read()
|
return None
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
logger.debug(f"read from {p}/capacity: {capacity_text}")
|
|
||||||
state.percent_charged = int(capacity_text)
|
|
||||||
|
|
||||||
try:
|
if ps.charge_full_design and ps.current_now is not None:
|
||||||
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)
|
|
||||||
# current_now is positive when charging
|
# current_now is positive when charging
|
||||||
state.charge_rate = int(current_now_text)
|
charges_per_hour = ps.current_now / ps.charge_full_design
|
||||||
|
elif ps.energy_full and ps.power_now is not None:
|
||||||
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)
|
|
||||||
# power_now is positive when discharging
|
# power_now is positive when discharging
|
||||||
state.charge_rate = -int(power_now_text)
|
charges_per_hour = -ps.power_now / ps.energy_full
|
||||||
|
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
logger.debug(f"read from {p}/energy_full: {energy_full_text}")
|
charges_per_hour = None
|
||||||
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)
|
|
||||||
|
|
||||||
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:
|
def try_all_paths() -> BatteryInfo | None:
|
||||||
p = try_path("/sys/class/power_supply/axp20x-battery") # Pinephone
|
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
|
p = try_path("/sys/class/power_supply/BAT0") # Thinkpad
|
||||||
|
|
||||||
logger.debug(f"perc: {p.percent_charged if p else None}")
|
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(f"charge: {p.percent_charged if p else None}")
|
||||||
logger.debug(" rate > 0 means charging, else discharging")
|
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 p
|
||||||
return BatteryInfo(p)
|
|
||||||
|
|
||||||
def fmt_minutes(f: Formatter, icon: str, if_indefinite: str, minutes: int | None) -> str:
|
def fmt_minutes(f: Formatter, icon: str, if_indefinite: str, minutes: int | None) -> str:
|
||||||
logger.debug(f"charge/discharge time: {minutes} min")
|
logger.debug(f"charge/discharge time: {minutes} min")
|
||||||
|
Reference in New Issue
Block a user