mirror of
https://github.com/Steve-Tech/YAFI.git
synced 2026-04-19 16:50:36 +00:00
225 lines
9.2 KiB
Python
225 lines
9.2 KiB
Python
# thermals.py
|
|
#
|
|
# Copyright 2025 Stephen Horvath
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
from gi.repository import Gtk, Adw, GLib
|
|
|
|
import cros_ec_python.commands as ec_commands
|
|
import cros_ec_python.exceptions as ec_exceptions
|
|
|
|
@Gtk.Template(resource_path='/au/stevetech/yafi/ui/thermals.ui')
|
|
class ThermalsPage(Gtk.Box):
|
|
__gtype_name__ = 'ThermalsPage'
|
|
|
|
first_run = True
|
|
|
|
fan_rpm = Gtk.Template.Child()
|
|
fan_mode = Gtk.Template.Child()
|
|
fan_set_rpm = Gtk.Template.Child()
|
|
fan_set_percent = Gtk.Template.Child()
|
|
fan_percent_scale = Gtk.Template.Child()
|
|
fan_set_points = Gtk.Template.Child()
|
|
set_points = []
|
|
ec_set_points_supported = False
|
|
ec_set_points = []
|
|
|
|
temperatures = Gtk.Template.Child()
|
|
temp_items = []
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
def setup(self, app):
|
|
# Temperature sensors
|
|
while temp_child := self.temperatures.get_last_child():
|
|
self.temperatures.remove(temp_child)
|
|
self.temp_items.clear()
|
|
|
|
try:
|
|
ec_temp_sensors = ec_commands.thermal.get_temp_sensors(app.cros_ec)
|
|
except ec_exceptions.ECError as e:
|
|
if e.ec_status == ec_exceptions.EcStatus.EC_RES_INVALID_COMMAND:
|
|
# Generate some labels if the command is not supported
|
|
ec_temp_sensors = {}
|
|
temps = ec_commands.memmap.get_temps(app.cros_ec)
|
|
for i, temp in enumerate(temps):
|
|
ec_temp_sensors[f"Sensor {i}"] = (temp, None)
|
|
else:
|
|
raise e
|
|
|
|
for key, value in ec_temp_sensors.items():
|
|
off_row = Adw.ActionRow(title=key, subtitle=f"{value[0]}°C")
|
|
off_row.add_css_class("property")
|
|
self.temperatures.append(off_row)
|
|
self.temp_items.append(off_row)
|
|
|
|
self._update_thermals(app)
|
|
|
|
# Don't let the user change the fans if they can't get back to auto
|
|
if ec_commands.general.get_cmd_versions(
|
|
app.cros_ec, ec_commands.thermal.EC_CMD_THERMAL_AUTO_FAN_CTRL
|
|
):
|
|
self.ec_set_points_supported = ec_commands.general.check_cmd_version(
|
|
app.cros_ec, ec_commands.thermal.EC_CMD_THERMAL_GET_THRESHOLD, 1
|
|
) and ec_commands.general.check_cmd_version(
|
|
app.cros_ec, ec_commands.thermal.EC_CMD_THERMAL_SET_THRESHOLD, 1
|
|
)
|
|
|
|
def handle_fan_mode(mode):
|
|
match mode:
|
|
case 0: # Auto
|
|
self.fan_set_rpm.set_visible(False)
|
|
self.fan_set_percent.set_visible(False)
|
|
ec_commands.thermal.thermal_auto_fan_ctrl(app.cros_ec)
|
|
self.fan_set_points.set_visible(self.ec_set_points_supported)
|
|
case 1: # Percent
|
|
self.fan_set_points.set_visible(False)
|
|
self.fan_set_rpm.set_visible(False)
|
|
self.fan_set_percent.set_visible(True)
|
|
case 2: # RPM
|
|
self.fan_set_points.set_visible(False)
|
|
self.fan_set_rpm.set_visible(True)
|
|
self.fan_set_percent.set_visible(False)
|
|
|
|
handle_fan_mode(self.fan_mode.get_selected())
|
|
|
|
self.fan_mode.connect(
|
|
"notify::selected",
|
|
lambda combo, _: handle_fan_mode(combo.get_selected()),
|
|
)
|
|
|
|
if ec_commands.general.get_cmd_versions(
|
|
app.cros_ec, ec_commands.pwm.EC_CMD_PWM_SET_FAN_DUTY
|
|
):
|
|
|
|
def handle_fan_percent(scale):
|
|
percent = int(scale.get_value())
|
|
ec_commands.pwm.pwm_set_fan_duty(app.cros_ec, percent)
|
|
self.fan_set_percent.set_subtitle(f"{percent} %")
|
|
|
|
self.fan_percent_scale.connect("value-changed", handle_fan_percent)
|
|
else:
|
|
self.fan_set_percent.set_sensitive(False)
|
|
|
|
if ec_commands.general.get_cmd_versions(
|
|
app.cros_ec, ec_commands.pwm.EC_CMD_PWM_SET_FAN_TARGET_RPM
|
|
):
|
|
|
|
def handle_fan_rpm(entry):
|
|
rpm = int(entry.get_value())
|
|
ec_commands.pwm.pwm_set_fan_rpm(app.cros_ec, rpm)
|
|
|
|
self.fan_set_rpm.connect(
|
|
"notify::value", lambda entry, _: handle_fan_rpm(entry)
|
|
)
|
|
else:
|
|
self.fan_set_rpm.set_sensitive(False)
|
|
else:
|
|
self.fan_mode.set_sensitive(False)
|
|
|
|
# Set points
|
|
if self.ec_set_points_supported and self.first_run:
|
|
def handle_set_point(entry, key):
|
|
index = entry.ec_index
|
|
temp = int(entry.get_value())
|
|
# Don't allow an off temp higher than max temp and vice versa
|
|
match key:
|
|
case "temp_fan_off":
|
|
if temp > self.ec_set_points[index]["temp_fan_max"]:
|
|
entry.set_value(self.ec_set_points[index]["temp_fan_off"])
|
|
return
|
|
case "temp_fan_max":
|
|
if temp < self.ec_set_points[index]["temp_fan_off"]:
|
|
entry.set_value(self.ec_set_points[index]["temp_fan_max"])
|
|
return
|
|
self.ec_set_points[entry.ec_index][key] = temp
|
|
ec_commands.thermal.thermal_set_thresholds(
|
|
app.cros_ec, index,
|
|
self.ec_set_points[index]
|
|
)
|
|
|
|
for i, sensor in enumerate(ec_temp_sensors):
|
|
ec_set_point = ec_commands.thermal.thermal_get_thresholds(app.cros_ec, i)
|
|
self.ec_set_points.append(ec_set_point)
|
|
off_row = Adw.SpinRow(
|
|
title=f"Fan On - {sensor}",
|
|
subtitle=f"Turn fan on when above temp (°C)",
|
|
)
|
|
off_row.ec_index = i
|
|
# 0K to 65535K for 16bit unsigned range
|
|
# Actually the EC takes 32bits, but let's keep it like this for sanity
|
|
off_row.set_adjustment(Gtk.Adjustment(
|
|
lower=-273,
|
|
upper=65_262,
|
|
page_increment=10,
|
|
step_increment=1,
|
|
value=ec_set_point["temp_fan_off"],
|
|
))
|
|
off_row.connect(
|
|
"notify::value", lambda entry, _: handle_set_point(entry, "temp_fan_off")
|
|
)
|
|
max_row = Adw.SpinRow(
|
|
title=f"Fan Max - {sensor}",
|
|
subtitle=f"Max fan speed when above temp (°C)",
|
|
)
|
|
max_row.ec_index = i
|
|
max_row.set_adjustment(Gtk.Adjustment(
|
|
lower=-273,
|
|
upper=65_262,
|
|
page_increment=10,
|
|
step_increment=1,
|
|
value=ec_set_point["temp_fan_max"],
|
|
))
|
|
max_row.connect(
|
|
"notify::value", lambda entry, _: handle_set_point(entry, "temp_fan_max")
|
|
)
|
|
self.fan_set_points.add_row(off_row)
|
|
self.fan_set_points.add_row(max_row)
|
|
|
|
self.first_run = False
|
|
|
|
# Schedule _update_thermals to run every second
|
|
GLib.timeout_add_seconds(1, self._update_thermals, app)
|
|
|
|
def _update_thermals(self, app):
|
|
# memmap reads should always be supported
|
|
ec_fans = ec_commands.memmap.get_fans(app.cros_ec)
|
|
self.fan_rpm.set_subtitle(f"{ec_fans[0]} RPM")
|
|
|
|
ec_temp_sensors = ec_commands.memmap.get_temps(app.cros_ec)
|
|
# The temp sensors disappear sometimes, so we need to handle that
|
|
for i in range(min(len(self.temp_items), len(ec_temp_sensors))):
|
|
self.temp_items[i].set_subtitle(f"{ec_temp_sensors[i]}°C")
|
|
|
|
# Check if this has already failed and skip if it has
|
|
if not ec_commands.pwm.EC_CMD_PWM_GET_FAN_TARGET_RPM in app.no_support:
|
|
try:
|
|
ec_target_rpm = ec_commands.pwm.pwm_get_fan_rpm(app.cros_ec)
|
|
self.fan_set_rpm.set_subtitle(f"{ec_target_rpm} RPM")
|
|
except ec_exceptions.ECError as e:
|
|
# If the command is not supported, we can ignore it
|
|
if e.ec_status == ec_exceptions.EcStatus.EC_RES_INVALID_COMMAND:
|
|
app.no_support.append(ec_commands.pwm.EC_CMD_PWM_GET_FAN_TARGET_RPM)
|
|
self.fan_set_rpm.set_subtitle("")
|
|
else:
|
|
# If it's another error, we should raise it
|
|
raise e
|
|
|
|
return app.current_page == 0
|