Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Build Python Wheels

on:
pull_request:
branches: [main]
paths:
- 'lib/**'
- 'components/**'
- 'pyproject.toml'
- '.github/workflows/build_wheels.yml'
push:
branches: [main]
release:
types: [published]
workflow_dispatch:

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest # linux x86_64
- os: ubuntu-24.04-arm # linux aarch64
- os: macos-13 # macos x86_64
- os: macos-latest # macos arm64
- os: windows-latest # windows amd64

steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
submodules: 'recursive'
# full history + tags so setuptools-scm can compute the version
fetch-depth: 0

- name: Build wheels
run: pipx run cibuildwheel
# configuration (python versions, test command, etc.) lives in
# [tool.cibuildwheel] in pyproject.toml

- name: Upload wheels
uses: actions/upload-artifact@v7
with:
name: wheels-${{ matrix.os }}
path: wheelhouse/*.whl

build_sdist:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +19 to +50
name: Build source distribution
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v7
with:
submodules: 'recursive'
fetch-depth: 0

- name: Build sdist
run: pipx run build --sdist

- name: Upload sdist
uses: actions/upload-artifact@v7
with:
name: sdist
path: dist/*.tar.gz

publish_pypi:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +51 to +70
name: Publish to PyPI
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
# only publish on published releases; PR / push builds just verify the
# wheels build and import correctly
if: github.event_name == 'release' && github.event.action == 'published'
environment:
name: pypi
url: https://pypi.org/p/espp
permissions:
# required for PyPI trusted publishing (OIDC)
id-token: write

steps:
- name: Download all distributions
uses: actions/download-artifact@v5
with:
pattern: '*'
merge-multiple: true
path: dist

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ only need to do one of them; they are not steps to be run one after another.**
update --init --recursive` to ensure that you have the latest versions of all
the submodules which are required to build the components in this repository.

- **Use the cross-platform components from Python or desktop C++ (no ESP
hardware needed).** The portable subset of espp (tasks, timers, sockets,
RTSP, RTPS, CDR, COBS, serialization, math, and more) is also packaged as the
[`espp` python package](https://pypi.org/project/espp/):

```console
pip install espp
# or, straight from the repository:
pip install git+https://github.com/esp-cpp/espp.git
```

See [lib/](./lib) for details, including how to build the same subset as a
C++ static library for PC, and [python/](./python) for example scripts.

## Additional Information and Links

* [Documentation](https://esp-cpp.github.io/espp/) - github hosted version of
Expand Down
72 changes: 43 additions & 29 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ message(STATUS "Building for PC: C++ & Python")

project(espp)

include(cmake/CPM.cmake)
CPMAddPackage(
NAME pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
VERSION 3.0.4
DOWNLOAD_ONLY YES
)
if(pybind11_ADDED)
add_subdirectory(${pybind11_SOURCE_DIR})
else()
message(FATAL_ERROR "pybind11 not found. Please ensure it is available in the specified version.")
# Prefer an installed pybind11 (provided as a build requirement when building
# python wheels via pip / scikit-build-core); fall back to fetching it with CPM
# for standalone CMake builds (e.g. ./build.sh).
find_package(pybind11 CONFIG QUIET)
if(NOT pybind11_FOUND)
include(cmake/CPM.cmake)
CPMAddPackage(
NAME pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
VERSION 3.0.4
)
if(NOT pybind11_ADDED)
message(FATAL_ERROR "pybind11 not found. Please ensure it is available in the specified version.")
endif()
endif()

set(CMAKE_COLOR_MAKEFILE ON)
Expand All @@ -37,25 +40,36 @@ if(APPLE)
set(LINK_ARG "-all_load")
endif()

set(TARGET_NAME "espp_pc")
if(SKBUILD)
# Building a python wheel via scikit-build-core (pip install). Only build the
# espp._espp extension module and install it into the `espp` package; the
# pure-python package files come from lib/python_bindings/espp via the
# `wheel.packages` setting in pyproject.toml.
espp_add_python_module()
install(TARGETS _espp LIBRARY DESTINATION espp)
else()
Comment on lines +48 to +50
# Standalone build (./build.sh / ./build.ps1): build the C++ static library
# and the python package, and install both into ./pc for local use.
set(TARGET_NAME "espp_pc")

# main library (which can be built for pc, android, and iOS)
add_library( # Specifies the name of the library.
${TARGET_NAME}
# Sets the library as a static (.a) library.
STATIC
# Provides a relative path to your source file(s).
${ESPP_SOURCES} )
set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_options(${TARGET_NAME} PRIVATE "${LINK_ARG}")
target_link_libraries(${TARGET_NAME} ${ESPP_EXTERNAL_LIBS})
target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)
# main library (which can be built for pc, android, and iOS)
add_library( # Specifies the name of the library.
${TARGET_NAME}
# Sets the library as a static (.a) library.
STATIC
# Provides a relative path to your source file(s).
${ESPP_SOURCES} )
set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_options(${TARGET_NAME} PRIVATE "${LINK_ARG}")
target_link_libraries(${TARGET_NAME} ${ESPP_EXTERNAL_LIBS})
target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)

# install build output and headers
install(TARGETS ${TARGET_NAME}
ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc)
# install build output and headers
install(TARGETS ${TARGET_NAME}
ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc)

espp_install_includes(${PROJECT_SOURCE_DIR}/pc)
espp_install_includes(${PROJECT_SOURCE_DIR}/pc)

# Build and install the python binding
espp_install_python_module(${PROJECT_SOURCE_DIR}/pc)
# Build and install the python package (espp/ with the _espp extension inside)
espp_install_python_module(${PROJECT_SOURCE_DIR}/pc)
endif()
52 changes: 48 additions & 4 deletions lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

- [ESPP Library](#espp-library)
- [Description](#description)
- [Installing the python package with pip](#installing-the-python-package-with-pip)
- [Building for PC (C++ & Python)](#building-for-pc-c--python)
- [Updating the python bindings](#updating-the-python-bindings)
- [Setup](#setup)
Expand Down Expand Up @@ -34,8 +35,43 @@ Some examples can be found in these folders:
- [../python](../python): This folder contains python code which uses the espp
library.

All the examples in these folders require that the espp library is built first,
as described below.
All the examples in these folders require that the espp library is either
installed via pip or built first, as described below.

## Installing the python package with pip

The python bindings are packaged as the [`espp` package on
PyPI](https://pypi.org/project/espp/) (see
[README_PYPI.md](./README_PYPI.md)), built via scikit-build-core from the
[pyproject.toml](../pyproject.toml) at the repository root:

``` console
pip install espp
```

You can also install straight from git (requires CMake >= 3.20 and a C++20
compiler; pip clones the needed submodules automatically):

``` console
pip install git+https://github.com/esp-cpp/espp.git
```

When developing the bindings (or running the [../python](../python) example
scripts against local changes), use an editable install from the repository
root:

``` console
pip install -e .
```

Re-run it after changing C++ code to rebuild the extension (the CMake build
dir is persistent, so rebuilds are incremental); pure-python changes are
picked up automatically.

Wheels for Linux (x86_64 / aarch64), macOS (x86_64 / arm64), and Windows
(amd64) are built in CI by
[build_wheels.yml](../.github/workflows/build_wheels.yml) and published to
PyPI on each release.

## Building for PC (C++ & Python)

Expand All @@ -56,7 +92,9 @@ This will build and install the following files:

* `./pc/libespp_pc` - C++ static library for use with other C++ code.
* `./pc/include` - All the header files need for using the library from C++ code.
* `./pc/espp.so` - C++ shared library for python binding for use with python code.
* `./pc/espp/` - The `espp` python package (pure-python files, type stubs, and
the compiled `espp._espp` pybind11 extension) - add `./pc` to your
`PYTHONPATH` / `sys.path` to `import espp` from python code.

## Updating the python bindings

Expand Down Expand Up @@ -108,7 +146,13 @@ by hand:
- `std::shared_ptr` holders + bases (the RTP packetizer hierarchy, `JpegFrame`),
- unqualified RTSP nested types / nested-struct statics in named-ctor defaults
(`espp::RtspClient::frame_callback_t`, `espp::RtspServer::Config::default_*`, ...),
- `def_readwrite` → `def_readonly` for non-copyable members (`RtspSession::Track` sockets).
- `def_readwrite` → `def_readonly` for non-copyable members (`RtspSession::Track` sockets),
- `py::call_guard<py::gil_scoped_release>()` on blocking methods (`Task::stop`,
`Timer::cancel`, socket send/receive/accept, the RTSP request methods, ...).
Without it, any method that joins the task thread or blocks on I/O deadlocks
when called from python, because the blocked-on thread needs the GIL to invoke
its python callback (see `_GIL_RELEASE_METHODS` in `autogenerate_bindings.py`;
the hand-written `rtps` bindings apply the same guard).

The litgen dependency is unpinned and recent versions (0.20–0.22) regressed
nested-scope/template generation, which is why this automation is needed.
Expand Down
65 changes: 65 additions & 0 deletions lib/README_PYPI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# espp

Python bindings for the cross-platform components of
[espp](https://github.com/esp-cpp/espp) — a collection of C++ components
originally built for ESP32 / ESP-IDF development, many of which are pure,
portable C++ and useful on the desktop as well.

The bindings are generated with [pybind11](https://github.com/pybind/pybind11)
and expose real native threads, sockets, and protocol implementations — unlike
Python threading, an `espp.Task` or `espp.Timer` runs on a real OS thread.

## Installation

```console
pip install espp
```

Or straight from the repository:

```console
pip install git+https://github.com/esp-cpp/espp.git
```

Building from source requires CMake (>= 3.20) and a C++20 compiler.

## What's included

- **Task / Timer**: native-threaded tasks and periodic timers with callbacks
- **Sockets**: UDP / TCP client & server helpers (unicast & multicast)
- **RTSP**: client & server with MJPEG / H.264 (de)packetizers
- **RTPS**: minimal RTPS participant for DDS interop (e.g. with embedded espp nodes)
- **CDR**: OMG Common Data Representation reader / writer
- **COBS**: consistent overhead byte stuffing encoder / decoder (incl. streaming)
- **Serialization**: alpaca-based binary serialization
- **Math**: vectors, Bézier curves, range mapping, fast approximations, filters (lowpass, Butterworth, ...), PID
- **Utilities**: logger, event manager, state machine, FTP, joystick, color, ...

The package ships complete type stubs (`py.typed`), so you get full
autocompletion and type checking in your editor.

## Quick example

```python
import time
import espp

def task_fn():
print("hello from a native thread!")
time.sleep(0.5)
return False # don't stop the task

task = espp.Task(task_fn, espp.Task.BaseConfig("example task"))
task.start()
time.sleep(2)
task.stop()
```

More example scripts live in the
[`python/`](https://github.com/esp-cpp/espp/tree/main/python) folder of the
repository.

## Links

- Documentation: https://esp-cpp.github.io/espp/
- Source & issues: https://github.com/esp-cpp/espp
Loading
Loading