diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d5bac063..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,55 +0,0 @@ -# .github/workflows/build.yml -name: Build ectool - -on: - push: - branches: [ main ] - pull_request: - -jobs: - build-linux: - runs-on: ubuntu-latest - steps: - - name: Checkout repository (with submodules) - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install dependencies - run: | - sudo apt update - sudo apt install -y cmake clang ninja-build git libftdi1-dev libusb-1.0-0-dev pkg-config - - - name: Print environment info (for debugging) - run: | - echo "CC=$CC" - echo "CXX=$CXX" - clang --version - cmake --version - ninja --version - pwd && ls -al - - - name: Configure CMake - run: | - mkdir _build - cd _build - CC=clang CXX=clang++ cmake -GNinja .. - - - name: Build - run: | - cd _build - ninja - - - name: List build output (for debugging) - run: | - echo "::group::Build Output" - find _build -type f - echo "::endgroup::" - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ectool-linux - path: | - _build/src/core/ectool - _build/src/core/libectool.so \ No newline at end of file diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml new file mode 100644 index 00000000..ede59162 --- /dev/null +++ b/.github/workflows/pip.yml @@ -0,0 +1,41 @@ +name: Pip + +on: + workflow_dispatch: + pull_request: + push: + branches: [main, dev] +jobs: + build: + name: Build on ${{ matrix.platform }} with Python ${{ matrix.python-version }} + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt update + sudo apt install -y libusb-1.0-0-dev libftdi1-dev pkg-config + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Build and install the package + run: | + pip install --verbose . + + - name: Test import + run: | + mkdir /tmp/testenv + cd /tmp/testenv + python -c "import pyectool; print('pyectool import successful')" diff --git a/.gitignore b/.gitignore index a007feab..f7fc85bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/* +*/__pycache__/* +dist/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0049ac19..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,35 +0,0 @@ -stages: - - build - -build windows/x64: - stage: build - before_script: - - git submodule update --init --recursive - script: - - mkdir _build - - cd _build; - - '& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -A x64 -T ClangCL ..' - - '& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" --build . --config RelWithDebInfo --parallel' - artifacts: - expire_in: never - paths: - - _build/src/RelWithDebInfo/ectool.exe - - _build/src/RelWithDebInfo/ectool.pdb - tags: - - windows - -build linux/x64: - stage: build - image: debian:latest - before_script: - - apt update && apt install -yy cmake clang ninja-build git libftdi1-dev libusb-1.0-0-dev pkg-config - - git submodule update --init --recursive - script: - - mkdir _build - - cd _build; - - CC=clang CXX=clang++ cmake -GNinja .. - - cmake --build . - artifacts: - expire_in: never - paths: - - _build/src/ectool diff --git a/.gitmodules b/.gitmodules index bfd41a09..bbf88077 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "extern/FrameworkWindowsUtils"] - path = extern/FrameworkWindowsUtils - url = https://github.com/DHowett/FrameworkWindowsUtils +[submodule "src/extern/FrameworkWindowsUtils"] + path = src/extern/FrameworkWindowsUtils + url = https://github.com/DHowett/FrameworkWindowsUtils diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7c086f3f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,59 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# See https://github.com/pre-commit/pre-commit + +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + +repos: +# Standard hooks +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + exclude: ^conda\.recipe/meta\.yaml$ + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +# Check linting and style issues +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.11.13" + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] + - id: ruff-format + exclude: ^(docs) + +# Changes tabs to spaces +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-tabs + exclude: ^(docs) + +# CMake formatting +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ diff --git a/CMakeLists.txt b/CMakeLists.txt index ee34523b..9ffd5f2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,26 +1,43 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.15...3.27) -if(${CMAKE_VERSION} VERSION_LESS 3.12) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -endif() +project( + ${SKBUILD_PROJECT_NAME} + VERSION ${SKBUILD_PROJECT_VERSION} + LANGUAGES CXX) + +# Required for building Python extension modules via pybind11 +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) -project(ECTool - VERSION 1.0.0 - DESCRIPTION "ChromeOS EC Tool" - LANGUAGES CXX) +# Find pybind11 (installed via pip/conda or system-wide) +find_package(pybind11 CONFIG REQUIRED) if(NOT WIN32) - find_package(PkgConfig REQUIRED) - pkg_check_modules(libusb REQUIRED libusb-1.0) - pkg_check_modules(libftdi1 REQUIRED libftdi1) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb REQUIRED libusb-1.0) + pkg_check_modules(libftdi1 REQUIRED libftdi1) else() + endif() set(CMAKE_CXX_STANDARD 17) add_subdirectory(src/core) +add_subdirectory(src/bindings) add_subdirectory(src/extern) if(WIN32) - add_subdirectory(src/getopt) + add_subdirectory(src/getopt) endif() + +install( + TARGETS ectool libectool libectool_py + RUNTIME DESTINATION pyectool/bin # ectool CLI binary + LIBRARY DESTINATION pyectool # libectool_py.so (shared Python module) + ARCHIVE DESTINATION pyectool/lib # libectool.a (static lib) +) + +install( + DIRECTORY src/include/ + DESTINATION pyectool/include + FILES_MATCHING + PATTERN "libectool.h") diff --git a/README.md b/README.md index 80d0395b..4f879bed 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,80 @@ -# libectool +# Pyectool -libectool is a shared library extracted from ectool, providing programmatic access to Embedded Controller (EC) functionalities on ChromeOS and compatible devices. +**Pyectool** is a Python package with C++ bindings for interacting with the Embedded Controller (EC) on ChromeOS and Framework devices. It is extracted from and based on [`ectool`](https://gitlab.howett.net/DHowett/ectool) utility, and exposes EC control functions directly to Python programs via a native extension. -## Features -- Exposes EC control functions via a shared library (`libectool.so`). -- Supports fan control, battery management, temperature monitoring, and more. -- Designed for integration into other applications. +## Features + +- Python bindings for EC functionality using `pybind11`. +- Supports fan duty control, temperature reading, AC power status, and more. +- Designed for integration with hardware management or fan control tools. +- Shared core logic with `libectool` for C/C++ integration. + +--- + +## Build & Install (Python Package) + +We use [`scikit-build-core`](https://scikit-build-core.readthedocs.io/en/latest/) to build the C++ extension via CMake. + +### Prerequisites + +Install the required system dependencies: -## Build Instructions ```sh -cd libectool -mkdir build && cd build -cmake .. -cmake --build . -``` -## Post Build Instructions -After building, you need to move `libectool.so` to a library directory where it can be found by your system: +sudo apt update +sudo apt install -y libusb-1.0-0-dev libftdi1-dev pkg-config +```` +### Clone the repository -### Option 1 — User-specific (Recommended for non-root users) +## Install system-wide ```sh -mkdir -p ~/.local/lib -cp src/core/libectool.so ~/.local/lib/libectool.so -export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH" +sudo pip install . ``` -To make it persistent across sessions, add the export to your shell configuration: -```sh -echo 'export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH"' >> ~/.bashrc +Or: + +```bash +sudo env "PIP_BREAK_SYSTEM_PACKAGES=1" pip install . ``` -### Option 2 — Global installation +(Required on modern distros like Ubuntu 24.04 due to PEP 668.) + +### Test from outside the repo dir +After installing, **do not run Python from the `libectool/` directory**, since it contains a `pyectool/` folder that may shadow the installed package. + +Instead, test from another location, e.g.: + ```sh -sudo cp src/core/libectool.so /usr/local/lib/libectool.so +cd .. +sudo python -c "import pyectool; print(pyectool.is_on_ac())" ``` + +## VENV INSTALLATION + +If you **don’t** want to touch system Python: + +### Create venv + +```bash +python3 -m venv ~/.venv/pyectool +source ~/.venv/pyectool/bin/activate +``` + +### Install your package + +Inside the venv: +```bash +pip install . +``` +### Test from outside the repo dir +```bash +cd .. +sudo env "PATH=$PATH" python -c "import pyectool; print(pyectool.is_on_ac())" +``` + +### Available Functions + +| Function | Description | +| ------------------------------------------ | -------------------------------------------------------------------------------- | +| `auto_fan_control()` | Enables automatic fan control by the EC. | +| `get_max_non_battery_temperature() -> float` | Returns the highest temperature (in °C) from all sensors except the battery. | +| `get_max_temperature() -> float` | Returns the highest temperature (in °C) from all EC sensors including battery. | +| `is_on_ac() -> bool` | Checks whether the device is running on AC power. | +| `set_fan_duty(percent: int)` | Sets the fan duty cycle manually (0–100%). | \ No newline at end of file diff --git a/pyectool/__init__.py b/pyectool/__init__.py new file mode 100644 index 00000000..e1bd1528 --- /dev/null +++ b/pyectool/__init__.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from .libectool_py import ( + __doc__, + __version__, + ascii_mode, + auto_fan_control, + get_max_non_battery_temperature, + get_max_temperature, + init, + is_on_ac, + release, + set_fan_duty, +) + +__all__ = [ + "__doc__", + "__version__", + "ascii_mode", + "auto_fan_control", + "get_max_non_battery_temperature", + "get_max_temperature", + "init", + "is_on_ac", + "release", + "set_fan_duty", +] diff --git a/pyectool/__init__.pyi b/pyectool/__init__.pyi new file mode 100644 index 00000000..7337574e --- /dev/null +++ b/pyectool/__init__.pyi @@ -0,0 +1,14 @@ +from __future__ import annotations + +__doc__: str +__version__: str + +def init() -> None: ... +def release() -> None: ... +def is_on_ac() -> bool: ... +def auto_fan_control() -> None: ... +def set_fan_duty(duty: int) -> None: ... +def get_max_temperature() -> float: ... +def get_max_non_battery_temperature() -> float: ... + +ascii_mode: bool diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d91681a7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["scikit-build-core>=0.10", "pybind11"] +build-backend = "scikit_build_core.build" + + +[project] +name = "pyectool" +version = "0.1.0" +description="Python bindings for ectool using pybind11, enabling seamless integration with other applications" +readme = "README.md" +authors = [ + { name = "Ahmed Gamea", email = "ahmed.gamea@ejust.edu.eg" }, +] + +[tool.scikit-build] +minimum-version = "build-system.requires" + +[tool.cibuildwheel] +build-frontend = "build[uv]" + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet +] +ignore = [ + "PLR09", # Too many X + "PLR2004", # Magic comparison +] +isort.required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["T20"] diff --git a/src/bindings/CMakeLists.txt b/src/bindings/CMakeLists.txt new file mode 100644 index 00000000..8b3dd757 --- /dev/null +++ b/src/bindings/CMakeLists.txt @@ -0,0 +1,7 @@ +# Create the Python module +python_add_library(libectool_py MODULE libectool_py.cc WITH_SOABI) + +# Link against required libraries +target_link_libraries(libectool_py PRIVATE pybind11::headers libectool) +target_include_directories(libectool_py PUBLIC ../include) +target_compile_definitions(libectool_py PUBLIC VERSION_INFO=${PROJECT_VERSION}) diff --git a/src/bindings/libectool_py.cc b/src/bindings/libectool_py.cc new file mode 100644 index 00000000..f77272aa --- /dev/null +++ b/src/bindings/libectool_py.cc @@ -0,0 +1,31 @@ +#include +#include "libectool.h" + +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + +namespace py = pybind11; + +PYBIND11_MODULE(libectool_py, m) { + m.doc() = "Python bindings for ectool"; + + // Optional: expose init/release explicitly + m.def("init", &libectool_init, "Initialize libectool"); + m.def("release", &libectool_release, "Release libectool"); + + // Expose API functions + m.def("is_on_ac", &is_on_ac, "Check if on AC power"); + m.def("auto_fan_control", &auto_fan_control, "Enable automatic fan control"); + m.def("set_fan_duty", &set_fan_duty, py::arg("duty"), "Set fan duty cycle (0-100)"); + m.def("get_max_temperature", &get_max_temperature, "Get max temperature"); + m.def("get_max_non_battery_temperature", &get_max_non_battery_temperature, "Get max non-battery temperature"); + + // Expose global variable ascii_mode + m.attr("ascii_mode") = py::cast(&ascii_mode, py::return_value_policy::reference); + + #ifdef VERSION_INFO + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); + #else + m.attr("__version__") = "dev"; + #endif +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ff2961a9..b66bfc3a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -9,127 +9,77 @@ set(ECTOOL_COMMON_SOURCES misc_util.cc crc.cc comm-host.cc - lock/gec_lock.cc -) + lock/gec_lock.cc) if(NOT WIN32) - list(APPEND ECTOOL_COMMON_SOURCES - comm-dev.cc - comm-i2c.cc - comm-lpc.cc - comm-servo-spi.cc - comm-usb.cc - lock/file_lock.cc - ) + list( + APPEND + ECTOOL_COMMON_SOURCES + comm-dev.cc + comm-i2c.cc + comm-lpc.cc + comm-servo-spi.cc + comm-usb.cc + lock/file_lock.cc) else() - list(APPEND ECTOOL_COMMON_SOURCES - comm-win32.cc - lock/win32_mutex_lock.cc - ) + list(APPEND ECTOOL_COMMON_SOURCES comm-win32.cc lock/win32_mutex_lock.cc) endif() - - # ============================ -# Add Executable Target: ectool +# Add Executable Target: ectool (CLI) # ============================ add_executable(ectool ectool.cc ${ECTOOL_COMMON_SOURCES}) -target_include_directories(ectool PRIVATE - ../include - ${libusb_INCLUDE_DIRS} -) +target_include_directories(ectool PRIVATE ../include ${libusb_INCLUDE_DIRS}) -target_compile_definitions(ectool PRIVATE - CHROMIUM_EC - EXTERNAL_ECTOOL_BUILD -) +target_compile_definitions(ectool PRIVATE CHROMIUM_EC EXTERNAL_ECTOOL_BUILD) -target_compile_options(ectool PRIVATE - -Wno-c99-designator - -Wno-address-of-packed-member - -Wno-format-security -) +target_compile_options( + ectool PRIVATE -Wno-c99-designator -Wno-address-of-packed-member + -Wno-format-security) if(WIN32) - target_compile_definitions(ectool PRIVATE - _CRT_SECURE_NO_WARNINGS - ) - target_link_libraries(ectool PRIVATE - getopt - CrosECDriver - onecoreuap_apiset.lib - ) - if(MSVC) - target_compile_options(ectool PRIVATE - /FI"..\\include\\win32_shim.h" - ) - else() - target_compile_options(ectool PRIVATE - -include "..\\include\\win32_shim.h" - ) - endif() - target_include_directories(ectool PRIVATE - ../include/win32 - ) + target_compile_definitions(ectool PRIVATE _CRT_SECURE_NO_WARNINGS) + target_link_libraries(ectool PRIVATE getopt CrosECDriver + onecoreuap_apiset.lib) + if(MSVC) + target_compile_options(ectool PRIVATE /FI"..\\include\\win32_shim.h") + else() + target_compile_options(ectool PRIVATE -include "..\\include\\win32_shim.h") + endif() + target_include_directories(ectool PRIVATE ../include/win32) endif() target_link_libraries(ectool ${libusb_LIBRARIES} ${libftdi1_LIBRARIES}) - # ============================ -# Add Shared Library : libectool.so +# Add STATIC Library : libectool # ============================ +add_library(libectool STATIC libectool.cc ${ECTOOL_COMMON_SOURCES}) -set(LIBECTOOL_SOURCES ${ECTOOL_COMMON_SOURCES} ectool_fanctrl.cc) +target_include_directories(libectool PUBLIC ../include ${libusb_INCLUDE_DIRS}) -add_library(libectool SHARED ${LIBECTOOL_SOURCES}) +target_compile_definitions(libectool PUBLIC CHROMIUM_EC EXTERNAL_ECTOOL_BUILD) -target_include_directories(libectool PRIVATE - ../include - ${libusb_INCLUDE_DIRS} -) +target_compile_options( + libectool PUBLIC -Wno-c99-designator -Wno-address-of-packed-member + -Wno-format-security) -target_compile_definitions(libectool PRIVATE - CHROMIUM_EC - EXTERNAL_ECTOOL_BUILD -) - -target_compile_options(libectool PRIVATE - -Wno-c99-designator - -Wno-address-of-packed-member - -Wno-format-security -) - -target_link_libraries(libectool PRIVATE - ${libusb_LIBRARIES} - ${libftdi1_LIBRARIES} -) +target_link_libraries(libectool PUBLIC ${libusb_LIBRARIES} + ${libftdi1_LIBRARIES}) if(WIN32) - target_compile_definitions(ectool PRIVATE - _CRT_SECURE_NO_WARNINGS - ) - target_link_libraries(ectool PRIVATE - getopt - CrosECDriver - onecoreuap_apiset.lib - ) - if(MSVC) - target_compile_options(ectool PRIVATE - /FI"..\\include\\win32_shim.h" - ) - else() - target_compile_options(ectool PRIVATE - -include "..\\include\\win32_shim.h" - ) - endif() - target_include_directories(ectool PRIVATE - ../include/win32 - ) + target_compile_definitions(libectool PUBLIC _CRT_SECURE_NO_WARNINGS) + target_link_libraries(libectool PUBLIC getopt CrosECDriver + onecoreuap_apiset.lib) + if(MSVC) + target_compile_options(libectool PUBLIC /FI"..\\include\\win32_shim.h") + else() + target_compile_options(libectool PUBLIC -include + "..\\include\\win32_shim.h") + endif() + target_include_directories(libectool PUBLIC ../include/win32) endif() -set_target_properties(libectool PROPERTIES - OUTPUT_NAME "ectool" # Generates `libectool.so` or `libectool.dll` -) - +set_target_properties(libectool PROPERTIES OUTPUT_NAME ectool + POSITION_INDEPENDENT_CODE ON) diff --git a/src/core/ectool_fanctrl.cc b/src/core/libectool.cc similarity index 81% rename from src/core/ectool_fanctrl.cc rename to src/core/libectool.cc index be7af241..8c8c64f0 100644 --- a/src/core/ectool_fanctrl.cc +++ b/src/core/libectool.cc @@ -2,6 +2,7 @@ #include #include +#include "libectool.h" #include "battery.h" #include "comm-host.h" #include "comm-usb.h" @@ -11,7 +12,6 @@ #include "ec_panicinfo.h" #include "ec_flash.h" #include "ec_version.h" -// #include "ectool.h" #include "i2c.h" #include "lightbar.h" #include "lock/gec_lock.h" @@ -30,21 +30,101 @@ #define GEC_LOCK_TIMEOUT_SECS 30 /* 30 secs */ #define interfaces COMM_ALL -int libectool_init(); -void libectool_release(); -int read_mapped_temperature(int id); -static uint8_t read_mapped_mem8(uint8_t offset); +int ascii_mode; +// ----------------------------------------------------------------------------- +// Helper functions +// ----------------------------------------------------------------------------- + +int libectool_init() +{ + char device_name[41] = CROS_EC_DEV_NAME; + uint16_t vid = USB_VID_GOOGLE, pid = USB_PID_HAMMER; + int i2c_bus = -1; + /* + * First try the preferred /dev interface (which has a built-in mutex). + * If the COMM_DEV flag is excluded or comm_init_dev() fails, + * then try alternative interfaces. + */ + if (!(interfaces & COMM_DEV) || comm_init_dev(device_name)) { + /* For non-USB alt interfaces, we need to acquire the GEC lock */ + if (!(interfaces & COMM_USB) && + acquire_gec_lock(GEC_LOCK_TIMEOUT_SECS) < 0) { + fprintf(stderr, "Could not acquire GEC lock.\n"); + return -1; + } + /* If the interface is set to USB, try that (no lock needed) */ + if (interfaces == COMM_USB) { +#ifndef _WIN32 + if (comm_init_usb(vid, pid)) { + fprintf(stderr, "Couldn't find EC on USB.\n"); + /* Release the lock if it was acquired */ + release_gec_lock(); + return -1; + } +#endif + } else if (comm_init_alt(interfaces, device_name, i2c_bus)) { + fprintf(stderr, "Couldn't find EC\n"); + release_gec_lock(); + return -1; + } + } + + /* Initialize ring buffers for sending/receiving EC commands */ + if (comm_init_buffer()) { + fprintf(stderr, "Couldn't initialize buffers\n"); + release_gec_lock(); + return -1; + } + + return 0; +} + +void libectool_release() +{ + /* Release the GEC lock. (This is safe even if no lock was acquired.) */ + release_gec_lock(); + +#ifndef _WIN32 + /* If the interface in use was USB, perform additional cleanup */ + if (interfaces == COMM_USB) + comm_usb_exit(); +#endif +} +static uint8_t read_mapped_mem8(uint8_t offset) +{ + int ret; + uint8_t val; -extern "C" { -int ascii_mode = 0; -bool is_on_ac(); -void pause_fan_control(); -void set_fan_speed(int speed); -float get_max_temperature(); -float get_max_non_battery_temperature(); + ret = ec_readmem(offset, sizeof(val), &val); + if (ret <= 0) { + fprintf(stderr, "failure in %s(): %d\n", __func__, ret); + exit(1); + } + return val; +} +int read_mapped_temperature(int id) +{ + int rv; + if (!read_mapped_mem8(EC_MEMMAP_THERMAL_VERSION)) { + /* + * The temp_sensor_init() is not called, which implies no + * temp sensor is defined. + */ + rv = EC_TEMP_SENSOR_NOT_PRESENT; + } else if (id < EC_TEMP_SENSOR_ENTRIES) + rv = read_mapped_mem8(EC_MEMMAP_TEMP_SENSOR + id); + else if (read_mapped_mem8(EC_MEMMAP_THERMAL_VERSION) >= 2) + rv = read_mapped_mem8(EC_MEMMAP_TEMP_SENSOR_B + id - + EC_TEMP_SENSOR_ENTRIES); + else { + /* Sensor in second bank, but second bank isn't supported */ + rv = EC_TEMP_SENSOR_NOT_PRESENT; + } + return rv; +} // ----------------------------------------------------------------------------- // Top-level endpoint functions @@ -62,35 +142,35 @@ bool is_on_ac() { return ac_present; } -void pause_fan_control() { +void auto_fan_control() { if (libectool_init() < 0) fprintf(stderr, "Failed initializing EC connection\n"); - + int rv = ec_command(EC_CMD_THERMAL_AUTO_FAN_CTRL, 0, NULL, 0, NULL, 0); if (rv < 0) fprintf(stderr, "Failed to enable auto fan control\n"); - + libectool_release(); } -void set_fan_speed(int speed) { +void set_fan_duty(int duty) { if (libectool_init() < 0) fprintf(stderr, "Failed initializing EC connection\n"); struct ec_params_pwm_set_fan_duty_v0 p_v0; int rv; - if (speed < 0 || speed > 100) { - fprintf(stderr, "Error: Fan speed must be between 0 and 100.\n"); + if (duty < 0 || duty > 100) { + fprintf(stderr, "Error: Fan duty cycle must be between 0 and 100.\n"); return; } - p_v0.percent = speed; + p_v0.percent = duty; rv = ec_command(EC_CMD_PWM_SET_FAN_DUTY, 0, &p_v0, sizeof(p_v0), NULL, 0); if (rv < 0) - fprintf(stderr, "Error: Can't set speed\n"); + fprintf(stderr, "Error: Can't set duty cycle\n"); libectool_release(); } @@ -99,7 +179,7 @@ void set_fan_speed(int speed) { float get_max_temperature() { if (libectool_init() < 0) fprintf(stderr, "Failed initializing EC connection\n"); - + float max_temp = -1.0f; int mtemp, temp; int id; @@ -135,10 +215,10 @@ float get_max_non_battery_temperature() { if (libectool_init() < 0) fprintf(stderr, "Failed initializing EC connection\n"); - - struct ec_params_temp_sensor_get_info p; - struct ec_response_temp_sensor_get_info r; - int rv; + + struct ec_params_temp_sensor_get_info p; + struct ec_response_temp_sensor_get_info r; + int rv; float max_temp = -1.0f; int mtemp, temp; int id; @@ -153,7 +233,7 @@ float get_max_non_battery_temperature() printf("%d: %d %s\n", p.id, r.sensor_type, r.sensor_name); - + if(strcmp(r.sensor_name, "Battery")){ // not eqaul to battery mtemp = read_mapped_temperature(p.id); switch (mtemp) { @@ -178,103 +258,7 @@ float get_max_non_battery_temperature() } } } - + libectool_release(); return max_temp; } -} - -// ----------------------------------------------------------------------------- -// Helper functions -// ----------------------------------------------------------------------------- - -int libectool_init() -{ - char device_name[41] = CROS_EC_DEV_NAME; - uint16_t vid = USB_VID_GOOGLE, pid = USB_PID_HAMMER; - int i2c_bus = -1; - /* - * First try the preferred /dev interface (which has a built-in mutex). - * If the COMM_DEV flag is excluded or comm_init_dev() fails, - * then try alternative interfaces. - */ - if (!(interfaces & COMM_DEV) || comm_init_dev(device_name)) { - /* For non-USB alt interfaces, we need to acquire the GEC lock */ - if (!(interfaces & COMM_USB) && - acquire_gec_lock(GEC_LOCK_TIMEOUT_SECS) < 0) { - fprintf(stderr, "Could not acquire GEC lock.\n"); - return -1; - } - /* If the interface is set to USB, try that (no lock needed) */ - if (interfaces == COMM_USB) { -#ifndef _WIN32 - if (comm_init_usb(vid, pid)) { - fprintf(stderr, "Couldn't find EC on USB.\n"); - /* Release the lock if it was acquired */ - release_gec_lock(); - return -1; - } -#endif - } else if (comm_init_alt(interfaces, device_name, i2c_bus)) { - fprintf(stderr, "Couldn't find EC\n"); - release_gec_lock(); - return -1; - } - } - - /* Initialize ring buffers for sending/receiving EC commands */ - if (comm_init_buffer()) { - fprintf(stderr, "Couldn't initialize buffers\n"); - release_gec_lock(); - return -1; - } - - return 0; -} - -void libectool_release() -{ - /* Release the GEC lock. (This is safe even if no lock was acquired.) */ - release_gec_lock(); - -#ifndef _WIN32 - /* If the interface in use was USB, perform additional cleanup */ - if (interfaces == COMM_USB) - comm_usb_exit(); -#endif -} - -int read_mapped_temperature(int id) -{ - int rv; - - if (!read_mapped_mem8(EC_MEMMAP_THERMAL_VERSION)) { - /* - * The temp_sensor_init() is not called, which implies no - * temp sensor is defined. - */ - rv = EC_TEMP_SENSOR_NOT_PRESENT; - } else if (id < EC_TEMP_SENSOR_ENTRIES) - rv = read_mapped_mem8(EC_MEMMAP_TEMP_SENSOR + id); - else if (read_mapped_mem8(EC_MEMMAP_THERMAL_VERSION) >= 2) - rv = read_mapped_mem8(EC_MEMMAP_TEMP_SENSOR_B + id - - EC_TEMP_SENSOR_ENTRIES); - else { - /* Sensor in second bank, but second bank isn't supported */ - rv = EC_TEMP_SENSOR_NOT_PRESENT; - } - return rv; -} - -static uint8_t read_mapped_mem8(uint8_t offset) -{ - int ret; - uint8_t val; - - ret = ec_readmem(offset, sizeof(val), &val); - if (ret <= 0) { - fprintf(stderr, "failure in %s(): %d\n", __func__, ret); - exit(1); - } - return val; -} \ No newline at end of file diff --git a/src/include/libectool.h b/src/include/libectool.h new file mode 100644 index 00000000..531bf8f3 --- /dev/null +++ b/src/include/libectool.h @@ -0,0 +1,28 @@ +#ifndef LIBECTOOL_H +#define LIBECTOOL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Library init/release +int libectool_init(); +void libectool_release(); + +// API functions to expose +bool is_on_ac(); +void auto_fan_control(); +void set_fan_duty(int duty); +float get_max_temperature(); +float get_max_non_battery_temperature(); + +/* ASCII mode for printing, default off */ +extern int ascii_mode; + +#ifdef __cplusplus +} +#endif + +#endif // LIBECTOOL_H diff --git a/tests/test_libectool.c b/tests/test_libectool.c new file mode 100644 index 00000000..6acbb062 --- /dev/null +++ b/tests/test_libectool.c @@ -0,0 +1,68 @@ +#include +#include +#include "libectool.h" + +void print_menu() { + printf("\n=== libectool Testing CLI ===\n"); + printf("1. Check if on AC power\n"); + printf("2. Pause fan control\n"); + printf("3. Set fan speed\n"); + printf("4. Get max temperature\n"); + printf("5. Get max non-battery temperature\n"); + printf("0. Exit\n"); + printf("Choose an option: "); +} + +int main() { + int choice; + int speed; + + while (1) { + print_menu(); + if (scanf("%d", &choice) != 1) { + // clear invalid input + int c; + while ((c = getchar()) != '\n' && c != EOF); + printf("Invalid input. Try again.\n"); + continue; + } + + switch (choice) { + case 1: { + bool ac = is_on_ac(); + printf("is_on_ac() = %d\n", ac); + break; + } + case 2: + printf("Enable automatic fan control...\n"); + auto_fan_control(); + break; + case 3: + printf("Enter fan speed (0-100): "); + if (scanf("%d", &speed) == 1) { + set_fan_duty(speed); + } else { + printf("Invalid speed.\n"); + // clear invalid input + int c; + while ((c = getchar()) != '\n' && c != EOF); + } + break; + case 4: { + float max_temp = get_max_temperature(); + printf("Max temperature = %.2f C\n", max_temp); + break; + } + case 5: { + float max_non_batt_temp = get_max_non_battery_temperature(); + printf("Max non-battery temperature = %.2f C\n", max_non_batt_temp); + break; + } + case 0: + printf("Exiting.\n"); + return 0; + default: + printf("Invalid choice. Try again.\n"); + } + } +}