From 2b50f0816fa5c398527ea737e8a1237920e0eb67 Mon Sep 17 00:00:00 2001 From: Stephen Horvath Date: Thu, 25 Dec 2025 19:35:33 +1000 Subject: [PATCH] Implement fan set points --- pyproject.toml | 2 +- python3-cros_ec_python.json | 4 +- yafi/thermals.py | 125 +++++++++++++++++++++++++++++------- yafi/ui/thermals.ui | 8 ++- yafi/ui/yafi.cmb | 4 +- yafi/yafi.gresource | Bin 28436 -> 28604 bytes 6 files changed, 113 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7af35d2..ab1819e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "Yet Another Framework Interface" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "cros_ec_python >= 0.2.0", + "cros_ec_python >= 0.3.0", "PyGObject" ] classifiers = [ diff --git a/python3-cros_ec_python.json b/python3-cros_ec_python.json index f6e23c0..8bb2a8a 100644 --- a/python3-cros_ec_python.json +++ b/python3-cros_ec_python.json @@ -7,8 +7,8 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/33/11/c23a7acaa333589921a2f524517eb719dfb72628153ae22fcdf2e9052ac6/cros_ec_python-0.2.0-py3-none-any.whl", - "sha256": "d38e493fbcaf23bc4b613d1342a036cecc6506284afc74f37013a3eac85a01b9" + "url": "https://files.pythonhosted.org/packages/6c/7a/10d978a02bbe37530490cfd14e0994c433dc29c81b3afcdbde453d512528/cros_ec_python-0.3.0-py3-none-any.whl", + "sha256": "aeb14ebdbd60ec6d6a4b11df1482a295466da4a908a468d168efd4cc141e7e3d" } ] } diff --git a/yafi/thermals.py b/yafi/thermals.py index ce85c51..c830f42 100644 --- a/yafi/thermals.py +++ b/yafi/thermals.py @@ -27,11 +27,17 @@ import cros_ec_python.exceptions as ec_exceptions 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 = [] @@ -40,10 +46,40 @@ class ThermalsPage(Gtk.Box): 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: @@ -51,13 +87,18 @@ class ThermalsPage(Gtk.Box): 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()), @@ -81,41 +122,77 @@ class ThermalsPage(Gtk.Box): ): def handle_fan_rpm(entry): - rpm = int(entry.get_text()) + rpm = int(entry.get_value()) ec_commands.pwm.pwm_set_fan_rpm(app.cros_ec, rpm) self.fan_set_rpm.connect( - "notify::text", lambda entry, _: handle_fan_rpm(entry) + "notify::value", lambda entry, _: handle_fan_rpm(entry) ) else: self.fan_set_rpm.set_sensitive(False) else: self.fan_mode.set_sensitive(False) - # Temperature sensors - while temp_child := self.temperatures.get_last_child(): - self.temperatures.remove(temp_child) - self.temp_items.clear() + # 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] + ) - 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 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) - for key, value in ec_temp_sensors.items(): - new_row = Adw.ActionRow(title=key, subtitle=f"{value[0]}°C") - new_row.add_css_class("property") - self.temperatures.append(new_row) - self.temp_items.append(new_row) - - self._update_thermals(app) + self.first_run = False # Schedule _update_thermals to run every second GLib.timeout_add_seconds(1, self._update_thermals, app) diff --git a/yafi/ui/thermals.ui b/yafi/ui/thermals.ui index d307315..a7f71fc 100644 --- a/yafi/ui/thermals.ui +++ b/yafi/ui/thermals.ui @@ -1,5 +1,5 @@ - + @@ -92,6 +92,12 @@ False + + + False + Fan Set Points + + diff --git a/yafi/ui/yafi.cmb b/yafi/ui/yafi.cmb index 31ef059..527e985 100644 --- a/yafi/ui/yafi.cmb +++ b/yafi/ui/yafi.cmb @@ -1,9 +1,9 @@ - + - + diff --git a/yafi/yafi.gresource b/yafi/yafi.gresource index 7e4aff9cebed1e5c589dc9f4af99ec480d5cce05..03312f36e4b051402c13ea1597b4d1fb88f637ea 100644 GIT binary patch delta 357 zcmbPok8#g^#tCog1>6}Ju7x_C;ACK60I}H^d>ABvv%u7x_C;ACK60I}H^d>9IVbO{h!DEuu4iG$e8K=BG7JpqU< z8!Kv9fZ`xFNPG&Ao&m(G?^s=A28x5&tU&PvKzaob*X%d`zylNqu{nU^8-Vl;{mAiX(+ zfnmO5(^`-?h|L8u2S^*IFfix~x`c57#X)R2pm+q37RhH|NG#PWE=es*ElJ({igB71 n(@oCFU6Li6zuA1{nEcP$dUAsW=j8riuE`JF%r{H8FVO%1vCvG$