Skip to content

Commit 5dfcd32

Browse files
Pyectool: Python Package Integration (#1)
* refactor: implement libectool library with core functionality and testing * feat: implement interactive CLI for libectool testing * feat: Add pybind11 bindings, scikit-build-core setup, and CI workflow for Python package - Implemented initial pybind11 class bindings to expose C++ API to Python. - Integrated scikit-build-core by updating CMakeLists.txt and pyproject.toml. - Added GitHub Actions workflow to build and test the Python package. * chore: update CI workflows and README, and bump version to 0.1.0
1 parent 40c7cb4 commit 5dfcd32

File tree

17 files changed

+585
-348
lines changed

17 files changed

+585
-348
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 55 deletions
This file was deleted.

.github/workflows/pip.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Pip
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches: [main, dev]
8+
jobs:
9+
build:
10+
name: Build on ${{ matrix.platform }} with Python ${{ matrix.python-version }}
11+
runs-on: ${{ matrix.platform }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
platform: [ubuntu-latest]
16+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17+
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
22+
- name: Install system dependencies
23+
run: |
24+
sudo apt update
25+
sudo apt install -y libusb-1.0-0-dev libftdi1-dev pkg-config
26+
27+
- name: Set up Python
28+
uses: actions/setup-python@v5
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
allow-prereleases: true
32+
33+
- name: Build and install the package
34+
run: |
35+
pip install --verbose .
36+
37+
- name: Test import
38+
run: |
39+
mkdir /tmp/testenv
40+
cd /tmp/testenv
41+
python -c "import pyectool; print('pyectool import successful')"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
build/*
2+
*/__pycache__/*
3+
dist/*

.gitlab-ci.yml

Lines changed: 0 additions & 35 deletions
This file was deleted.

.gitmodules

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[submodule "extern/FrameworkWindowsUtils"]
2-
path = extern/FrameworkWindowsUtils
3-
url = https://github.com/DHowett/FrameworkWindowsUtils
1+
[submodule "src/extern/FrameworkWindowsUtils"]
2+
path = src/extern/FrameworkWindowsUtils
3+
url = https://github.com/DHowett/FrameworkWindowsUtils

.pre-commit-config.yaml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# To use:
2+
#
3+
# pre-commit run -a
4+
#
5+
# Or:
6+
#
7+
# pre-commit install # (runs every time you commit in git)
8+
#
9+
# To update this file:
10+
#
11+
# pre-commit autoupdate
12+
#
13+
# See https://github.com/pre-commit/pre-commit
14+
15+
ci:
16+
autoupdate_commit_msg: "chore: update pre-commit hooks"
17+
autofix_commit_msg: "style: pre-commit fixes"
18+
19+
repos:
20+
# Standard hooks
21+
- repo: https://github.com/pre-commit/pre-commit-hooks
22+
rev: v5.0.0
23+
hooks:
24+
- id: check-added-large-files
25+
- id: check-case-conflict
26+
- id: check-merge-conflict
27+
- id: check-symlinks
28+
- id: check-yaml
29+
exclude: ^conda\.recipe/meta\.yaml$
30+
- id: debug-statements
31+
- id: end-of-file-fixer
32+
- id: mixed-line-ending
33+
- id: requirements-txt-fixer
34+
- id: trailing-whitespace
35+
36+
# Check linting and style issues
37+
- repo: https://github.com/astral-sh/ruff-pre-commit
38+
rev: "v0.11.13"
39+
hooks:
40+
- id: ruff
41+
args: ["--fix", "--show-fixes"]
42+
- id: ruff-format
43+
exclude: ^(docs)
44+
45+
# Changes tabs to spaces
46+
- repo: https://github.com/Lucas-C/pre-commit-hooks
47+
rev: v1.5.5
48+
hooks:
49+
- id: remove-tabs
50+
exclude: ^(docs)
51+
52+
# CMake formatting
53+
- repo: https://github.com/cheshirekow/cmake-format-precommit
54+
rev: v0.6.13
55+
hooks:
56+
- id: cmake-format
57+
additional_dependencies: [pyyaml]
58+
types: [file]
59+
files: (\.cmake|CMakeLists.txt)(.in)?$

CMakeLists.txt

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
1-
cmake_minimum_required(VERSION 3.1)
1+
cmake_minimum_required(VERSION 3.15...3.27)
22

3-
if(${CMAKE_VERSION} VERSION_LESS 3.12)
4-
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
5-
endif()
3+
project(
4+
${SKBUILD_PROJECT_NAME}
5+
VERSION ${SKBUILD_PROJECT_VERSION}
6+
LANGUAGES CXX)
7+
8+
# Required for building Python extension modules via pybind11
9+
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
610

7-
project(ECTool
8-
VERSION 1.0.0
9-
DESCRIPTION "ChromeOS EC Tool"
10-
LANGUAGES CXX)
11+
# Find pybind11 (installed via pip/conda or system-wide)
12+
find_package(pybind11 CONFIG REQUIRED)
1113

1214
if(NOT WIN32)
13-
find_package(PkgConfig REQUIRED)
14-
pkg_check_modules(libusb REQUIRED libusb-1.0)
15-
pkg_check_modules(libftdi1 REQUIRED libftdi1)
15+
find_package(PkgConfig REQUIRED)
16+
pkg_check_modules(libusb REQUIRED libusb-1.0)
17+
pkg_check_modules(libftdi1 REQUIRED libftdi1)
1618
else()
19+
1720
endif()
1821

1922
set(CMAKE_CXX_STANDARD 17)
2023

2124
add_subdirectory(src/core)
25+
add_subdirectory(src/bindings)
2226
add_subdirectory(src/extern)
2327

2428
if(WIN32)
25-
add_subdirectory(src/getopt)
29+
add_subdirectory(src/getopt)
2630
endif()
31+
32+
install(
33+
TARGETS ectool libectool libectool_py
34+
RUNTIME DESTINATION pyectool/bin # ectool CLI binary
35+
LIBRARY DESTINATION pyectool # libectool_py.so (shared Python module)
36+
ARCHIVE DESTINATION pyectool/lib # libectool.a (static lib)
37+
)
38+
39+
install(
40+
DIRECTORY src/include/
41+
DESTINATION pyectool/include
42+
FILES_MATCHING
43+
PATTERN "libectool.h")

README.md

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,80 @@
1-
# libectool
1+
# Pyectool
22

3-
libectool is a shared library extracted from ectool, providing programmatic access to Embedded Controller (EC) functionalities on ChromeOS and compatible devices.
3+
**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.
44

5-
## Features
6-
- Exposes EC control functions via a shared library (`libectool.so`).
7-
- Supports fan control, battery management, temperature monitoring, and more.
8-
- Designed for integration into other applications.
5+
## Features
6+
7+
- Python bindings for EC functionality using `pybind11`.
8+
- Supports fan duty control, temperature reading, AC power status, and more.
9+
- Designed for integration with hardware management or fan control tools.
10+
- Shared core logic with `libectool` for C/C++ integration.
11+
12+
---
13+
14+
## Build & Install (Python Package)
15+
16+
We use [`scikit-build-core`](https://scikit-build-core.readthedocs.io/en/latest/) to build the C++ extension via CMake.
17+
18+
### Prerequisites
19+
20+
Install the required system dependencies:
921

10-
## Build Instructions
1122
```sh
12-
cd libectool
13-
mkdir build && cd build
14-
cmake ..
15-
cmake --build .
16-
```
17-
## Post Build Instructions
18-
After building, you need to move `libectool.so` to a library directory where it can be found by your system:
23+
sudo apt update
24+
sudo apt install -y libusb-1.0-0-dev libftdi1-dev pkg-config
25+
````
26+
### Clone the repository
1927

20-
### Option 1 — User-specific (Recommended for non-root users)
28+
## Install system-wide
2129
```sh
22-
mkdir -p ~/.local/lib
23-
cp src/core/libectool.so ~/.local/lib/libectool.so
24-
export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH"
30+
sudo pip install .
2531
```
26-
To make it persistent across sessions, add the export to your shell configuration:
27-
```sh
28-
echo 'export LD_LIBRARY_PATH="$HOME/.local/lib:$LD_LIBRARY_PATH"' >> ~/.bashrc
32+
Or:
33+
34+
```bash
35+
sudo env "PIP_BREAK_SYSTEM_PACKAGES=1" pip install .
2936
```
30-
### Option 2 — Global installation
37+
(Required on modern distros like Ubuntu 24.04 due to PEP 668.)
38+
39+
### Test from outside the repo dir
40+
After installing, **do not run Python from the `libectool/` directory**, since it contains a `pyectool/` folder that may shadow the installed package.
41+
42+
Instead, test from another location, e.g.:
43+
3144
```sh
32-
sudo cp src/core/libectool.so /usr/local/lib/libectool.so
45+
cd ..
46+
sudo python -c "import pyectool; print(pyectool.is_on_ac())"
3347
```
48+
49+
## VENV INSTALLATION
50+
51+
If you **don’t** want to touch system Python:
52+
53+
### Create venv
54+
55+
```bash
56+
python3 -m venv ~/.venv/pyectool
57+
source ~/.venv/pyectool/bin/activate
58+
```
59+
60+
### Install your package
61+
62+
Inside the venv:
63+
```bash
64+
pip install .
65+
```
66+
### Test from outside the repo dir
67+
```bash
68+
cd ..
69+
sudo env "PATH=$PATH" python -c "import pyectool; print(pyectool.is_on_ac())"
70+
```
71+
72+
### Available Functions
73+
74+
| Function | Description |
75+
| ------------------------------------------ | -------------------------------------------------------------------------------- |
76+
| `auto_fan_control()` | Enables automatic fan control by the EC. |
77+
| `get_max_non_battery_temperature() -> float` | Returns the highest temperature (in °C) from all sensors except the battery. |
78+
| `get_max_temperature() -> float` | Returns the highest temperature (in °C) from all EC sensors including battery. |
79+
| `is_on_ac() -> bool` | Checks whether the device is running on AC power. |
80+
| `set_fan_duty(percent: int)` | Sets the fan duty cycle manually (0–100%). |

pyectool/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from __future__ import annotations
2+
3+
from .libectool_py import (
4+
__doc__,
5+
__version__,
6+
ascii_mode,
7+
auto_fan_control,
8+
get_max_non_battery_temperature,
9+
get_max_temperature,
10+
init,
11+
is_on_ac,
12+
release,
13+
set_fan_duty,
14+
)
15+
16+
__all__ = [
17+
"__doc__",
18+
"__version__",
19+
"ascii_mode",
20+
"auto_fan_control",
21+
"get_max_non_battery_temperature",
22+
"get_max_temperature",
23+
"init",
24+
"is_on_ac",
25+
"release",
26+
"set_fan_duty",
27+
]

0 commit comments

Comments
 (0)