Skip to content
Merged
35 changes: 31 additions & 4 deletions pyectool/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,37 @@ from __future__ import annotations
__doc__: str
__version__: str

class ECTempInfo(dict[str, int | str]):
sensor_name: str
sensor_type: int
temp: int
temp_fan_off: int
temp_fan_max: int

class ECChargeStateInfo(dict[str, int]):
ac: int
chg_voltage: int
chg_current: int
chg_input_current: int
batt_state_of_charge: int

class ECController:
def __init__(self) -> None: ...
def hello(self) -> None: ...
def is_on_ac(self) -> bool: ...
def auto_fan_control(self) -> None: ...
def set_fan_duty(self, duty: int) -> None: ...
def get_max_temperature(self) -> float: ...
def get_max_non_battery_temperature(self) -> float: ...
def get_charge_state(self) -> ECChargeStateInfo: ...
def get_num_fans(self) -> int: ...
def enable_fan_auto_ctrl(self, fan_idx: int) -> None: ...
def enable_all_fans_auto_ctrl(self) -> None: ...
def set_fan_duty(self, percent: int, fan_idx: int) -> None: ...
def set_all_fans_duty(self, percent: int) -> None: ...
def set_fan_rpm(self, target_rpm: int, fan_idx: int) -> None: ...
def set_all_fans_rpm(self, target_rpm: int) -> None: ...
def get_fan_rpm(self, fan_idx: int) -> int: ...
def get_all_fans_rpm(self) -> list[int]: ...
def get_num_temp_sensors(self) -> int: ...
def get_temp(self, sensor_idx: int) -> int: ...
def get_all_temps(self) -> list[int]: ...
def get_max_temp(self) -> int: ...
def get_max_non_battery_temp(self) -> int: ...
def get_temp_info(self, sensor_idx: int) -> ECTempInfo: ...
134 changes: 121 additions & 13 deletions src/bindings/ECController.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,30 @@ void ECController::handle_error(int code, const std::string &msg) {
case EC_ERR_READMEM: reason = "EC memory read failed"; break;
case EC_ERR_EC_COMMAND: reason = "EC command failed"; break;
case EC_ERR_INVALID_PARAM: reason = "Invalid parameter"; break;
case EC_ERR_SENSOR_UNAVAILABLE:
reason = "Sensor unavailable or not calibrated/powered";
break;
case EC_ERR_UNSUPPORTED_VER:
reason = "Unsupported EC command version";
break;

case EC_ERR_INVALID_RESPONSE:
reason = "Invalid response from EC";
break;
default: reason = "Unknown error"; break;
}

throw std::runtime_error(msg + " (" + reason + ", code " + std::to_string(code) + ")");
}

int ECController::hello() {
int ret = ec_hello();
return ret;
}

// -----------------------------------------------------------------------------
// Top-level Power Functions
// -----------------------------------------------------------------------------

bool ECController::is_on_ac() {
int ac;
Expand All @@ -24,26 +42,116 @@ bool ECController::is_on_ac() {
return ac;
}

void ECController::auto_fan_control() {
int ret = ec_auto_fan_control();
ec_charge_state_info ECController::get_charge_state() {
ec_charge_state_info info;
int ret = ec_get_charge_state(&info);
handle_error(ret, "Failed to get charge state");
return info;
}

// -----------------------------------------------------------------------------
// Top-level fan control Functions
// -----------------------------------------------------------------------------

int ECController::get_num_fans() {
int val = 0;
int ret = ec_get_num_fans(&val);
handle_error(ret, "Failed to get number of fans");
return val;
}

void ECController::enable_fan_auto_ctrl(int fan_idx) {
int ret = ec_enable_fan_auto_ctrl(fan_idx);
handle_error(ret, "Failed to enable auto fan control");
}

void ECController::set_fan_duty(int duty) {
int ret = ec_set_fan_duty(duty);
void ECController::enable_all_fans_auto_ctrl() {
int ret = ec_enable_all_fans_auto_ctrl();
handle_error(ret, "Failed to enable auto control for all fans");
}

void ECController::set_fan_duty(int percent, int fan_idx) {
int ret = ec_set_fan_duty(percent, fan_idx);
handle_error(ret, "Failed to set fan duty");
}

float ECController::get_max_temperature() {
float t;
int ret = ec_get_max_temperature(&t);
void ECController::set_all_fans_duty(int percent) {
int ret = ec_set_all_fans_duty(percent);
handle_error(ret, "Failed to set duty for all fans");
}

void ECController::set_fan_rpm(int target_rpm, int fan_idx) {
int ret = ec_set_fan_rpm(target_rpm, fan_idx);
handle_error(ret, "Failed to set fan RPM");
}

void ECController::set_all_fans_rpm(int target_rpm) {
int ret = ec_set_all_fans_rpm(target_rpm);
handle_error(ret, "Failed to set RPM for all fans");
}

int ECController::get_fan_rpm(int fan_idx) {
int rpm = 0;
int ret = ec_get_fan_rpm(&rpm, fan_idx);
handle_error(ret, "Failed to get fan RPM");
return rpm;
}

std::vector<int> ECController::get_all_fans_rpm() {
int num_fans = get_num_fans();
std::vector<int> rpms(num_fans);
int num_fans_out = 0;

int ret = ec_get_all_fans_rpm(rpms.data(), num_fans, &num_fans_out);
handle_error(ret, "Failed to get all fan RPMs");
return rpms;
}

// -----------------------------------------------------------------------------
// Top-level temperature Functions
// -----------------------------------------------------------------------------
int ECController::get_num_temp_sensors() {
int val = 0;
int ret = ec_get_num_temp_sensors(&val);
handle_error(ret, "Failed to get number of temp sensors");
return val;
}

int ECController::get_temp(int sensor_idx) {
int temp = 0;
int ret = ec_get_temp(sensor_idx, &temp);
handle_error(ret, "Failed to get temperature");
return temp;
}

std::vector<int> ECController::get_all_temps() {
int max_entries = get_num_temp_sensors();
std::vector<int> temps(max_entries);
int num_sensors = 0;

int ret = ec_get_all_temps(temps.data(), max_entries, &num_sensors);
handle_error(ret, "Failed to get all temperatures");
temps.resize(num_sensors); // Trim unused entries
return temps;
}

int ECController::get_max_temp() {
int temp = 0;
int ret = ec_get_max_temp(&temp);
handle_error(ret, "Failed to get max temperature");
return t;
return temp;
}

int ECController::get_max_non_battery_temp() {
int temp = 0;
int ret = ec_get_max_non_battery_temp(&temp);
handle_error(ret, "Failed to get max non-battery temperature");
return temp;
}

float ECController::get_max_non_battery_temperature() {
float t;
int ret = ec_get_max_non_battery_temperature(&t);
handle_error(ret, "Failed to get non-battery temperature");
return t;
ec_temp_info ECController::get_temp_info(int sensor_idx) {
ec_temp_info info;
int ret = ec_get_temp_info(sensor_idx, &info);
handle_error(ret, "Failed to get temp sensor info");
return info;
}
26 changes: 22 additions & 4 deletions src/bindings/ECController.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
#include "libectool.h"

class ECController {
public:
int hello();

bool is_on_ac();
void auto_fan_control();
void set_fan_duty(int duty);
float get_max_temperature();
float get_max_non_battery_temperature();
ec_charge_state_info get_charge_state();

int get_num_fans();
void enable_fan_auto_ctrl(int fan_idx);
void enable_all_fans_auto_ctrl();
void set_fan_duty(int percent, int fan_idx);
void set_all_fans_duty(int percent);
void set_fan_rpm(int target_rpm, int fan_idx);
void set_all_fans_rpm(int target_rpm);
int get_fan_rpm(int fan_idx);
std::vector<int> get_all_fans_rpm();

int get_num_temp_sensors();
int get_temp(int sensor_idx);
std::vector<int> get_all_temps();
int get_max_temp();
int get_max_non_battery_temp();
ec_temp_info get_temp_info(int sensor_idx);

private:
void handle_error(int code, const std::string &msg);
Expand Down
115 changes: 105 additions & 10 deletions src/bindings/PyECController.cc
Original file line number Diff line number Diff line change
@@ -1,25 +1,120 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "ECController.h"
#include "libectool.h"

#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)

namespace py = pybind11;

py::dict temp_info_to_dict(const ec_temp_info& info) {
py::dict d;
d["sensor_name"] = std::string(info.sensor_name);
d["sensor_type"] = info.sensor_type;
d["temp"] = info.temp;
d["temp_fan_off"] = info.temp_fan_off;
d["temp_fan_max"] = info.temp_fan_max;
return d;
}

py::dict charge_state_to_dict(const ec_charge_state_info& info) {
py::dict d;
d["ac"] = static_cast<bool>(info.ac);
d["chg_voltage"] = info.chg_voltage;
d["chg_current"] = info.chg_current;
d["chg_input_current"] = info.chg_input_current;
d["batt_state_of_charge"] = info.batt_state_of_charge;
return d;
}


PYBIND11_MODULE(libectool_py, m) {
m.doc() = "Python bindings for ectool";

py::class_<ECController>(m, "ECController")
.def(py::init<>())
.def("is_on_ac", &ECController::is_on_ac, "Check if on AC power")
.def("auto_fan_control", &ECController::auto_fan_control, "Enable automatic fan control")
.def("set_fan_duty", &ECController::set_fan_duty,
"Set fan duty cycle (0-100)", py::arg("duty"))
.def("get_max_temperature", &ECController::get_max_temperature,
"Get max temperature")
.def("get_max_non_battery_temperature",
&ECController::get_max_non_battery_temperature,
"Get max non-battery temperature");
.def(py::init<>())
.def("hello", &ECController::hello, "Send hello command to EC")

.def("is_on_ac", &ECController::is_on_ac, "Check if on AC power")

.def("get_charge_state", [](ECController& self) {
return charge_state_to_dict(self.get_charge_state());
}, "Get charge state info")

.def("get_num_fans", &ECController::get_num_fans,
"Get number of fans")

.def("enable_fan_auto_ctrl",
&ECController::enable_fan_auto_ctrl,
"Enable auto control for a fan",
py::arg("fan_idx"))

.def("enable_all_fans_auto_ctrl",
&ECController::enable_all_fans_auto_ctrl,
"Enable auto control for all fans")

.def("set_fan_duty",
&ECController::set_fan_duty,
"Set fan duty cycle (0-100)",
py::arg("percent"), py::arg("fan_idx"))

.def("set_all_fans_duty",
&ECController::set_all_fans_duty,
"Set all fans duty cycle (0-100)",
py::arg("percent"))

.def("set_fan_rpm",
&ECController::set_fan_rpm,
"Set fan RPM",
py::arg("target_rpm"), py::arg("fan_idx"))

.def("set_all_fans_rpm",
&ECController::set_all_fans_rpm,
"Set all fans RPM",
py::arg("target_rpm"))

.def("get_fan_rpm",
&ECController::get_fan_rpm,
"Get single fan RPM",
py::arg("fan_idx"))

.def("get_all_fans_rpm",
[](ECController &self) {
return py::cast(self.get_all_fans_rpm());
},
"Get all fans RPM as list")

.def("get_num_temp_sensors",
&ECController::get_num_temp_sensors,
"Get number of temperature sensors")

.def("get_temp",
&ECController::get_temp,
"Get temperature in Celsius for one sensor",
py::arg("sensor_idx"))

.def("get_all_temps",
[](ECController &self) {
return py::cast(self.get_all_temps());
},
"Get all temperature values as list")

.def("get_max_temp",
&ECController::get_max_temp,
"Get maximum temperature across all sensors")

.def("get_max_non_battery_temp",
&ECController::get_max_non_battery_temp,
"Get maximum non-battery temperature")

.def("get_temp_info",
[](ECController &self, int sensor_idx) {
ec_temp_info info = self.get_temp_info(sensor_idx);
return temp_info_to_dict(info);
},
"Get detailed temperature info for a sensor",
py::arg("sensor_idx"));

#ifdef VERSION_INFO
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
Expand Down
Loading