Metadata-Version: 2.4
Name: gtec-ble
Version: 2.1.0
Summary: A Bluetooth Low Energy (BLE) API for g.tec devices
Author-email: "g.tec medical engineering GmbH" <support@gtec.at>
License-Expression: LicenseRef-GNCL
Project-URL: Homepage, https://www.gtec.at
Project-URL: Documentation, https://gpype.gtec.at
Project-URL: Repository, https://github.com/gtec-medical-engineering/gtec-ble-py
Keywords: bluetooth,ble,gtec,eeg,bci,medical
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE-GNCL.txt
Requires-Dist: numpy>=1.14.4
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pytest-html; extra == "dev"
Requires-Dist: coverage; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: wheel; extra == "dev"
Requires-Dist: Cython>=3.1; extra == "dev"
Requires-Dist: cibuildwheel>=2.21; extra == "dev"
Requires-Dist: flake8>=6.0; extra == "dev"
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: isort>=5.12; extra == "dev"
Dynamic: license-file

# gtec-ble

A Python Bluetooth Low Energy (BLE) API for [g.tec](https://www.gtec.at)
biosignal amplifiers.

`gtec-ble` is a thin, Pythonic wrapper around the native `gtecble` C library
(from [gtec-ble-c](https://github.com/gtec-medical-engineering/gtec-ble-c)).
The matching native binary is bundled inside each platform wheel, so
`pip install gtec-ble` is self-contained — no separate SDK download or
compiler required.

> This package is intended to be used as part of
> [g.Pype](https://gpype.gtec.at). It is licensed under the g.tec
> Non-Commercial License (GNCL) — see [`LICENSE-GNCL.txt`](LICENSE-GNCL.txt).

## Installation

```bash
pip install gtec-ble
```

Prebuilt wheels are published for:

| Platform | Architecture | Python |
|----------|--------------|--------|
| Windows  | x64 (AMD64)  | 3.10 – 3.14 |
| macOS    | arm64 (Apple Silicon) | 3.10 – 3.14 |
| Linux    | x86_64 (glibc / manylinux) | 3.10 – 3.14 |

See [Platform support](#platform-support) for details and current limitations.

## Quick start

```python
import uuid
import hashlib
from gtec_ble import Amplifier

# Registration key is derived from the machine MAC address.
mac = uuid.getnode()
key = hashlib.sha256(f"{mac:012x}".encode()).hexdigest()
Amplifier.register(key)

# Discover devices.
devices = Amplifier.get_connected_devices()
print(f"Found devices: {devices}")

# Connect to the first device and stream data.
with Amplifier(devices[0]) as amp:
    def on_data(data):
        # data is a 1-D numpy float32 array of one sample per channel.
        print(f"Received {len(data)} channels")

    amp.set_data_callback(on_data)
    amp.start()
    # ... acquire ...
    amp.stop()

# Release the BLE stack before the process exits.
Amplifier.shutdown()
```

See [`docs/api/python-api.md`](docs/api/python-api.md) for the full API
reference and [`examples/`](examples/) for runnable examples.

## Platform support

The bundled native library comes from the `gtec-ble-c` project, which
publishes prebuilt binaries for **Windows x64**, **Linux x86_64**, and
**macOS arm64**. These are deliberate scope choices, not code limits:

- **macOS** wheels are **arm64 (Apple Silicon) only** — a deliberate choice.
  The loader and packaging already handle `x86_64`/`universal2`, but Intel Macs
  are end-of-life and CoreBluetooth is validated only on arm64, so no
  Intel/universal2 wheel is published. Enabling it is a small on-demand change
  (build `gtec-ble-c` with `build-macos.sh --arch universal`, publish that
  artifact, map it in `fetch_native_libs.py`, and set `archs = ["universal2"]`
  in `[tool.cibuildwheel.macos]`) — worth doing only for a concrete Intel-Mac
  consumer.
- **Linux** wheels are `manylinux` x86_64 and require a **systemd-based**
  distro. The BlueZ backend in `gtec-ble-c` is written against `sd-bus`, so the
  bundled `libgtecble.so` (a runtime `dlopen` plugin, left untouched by
  `auditwheel`) resolves `libsystemd.so.0` from the host at runtime. This is by
  design and covers every mainstream distro (Ubuntu/Debian/Fedora/RHEL/Arch/
  openSUSE). The glibc baseline is inherited from the `gtec-ble-c` build, so a
  reasonably recent distro is required. **`musl`/Alpine and aarch64 are not
  published** — musl would be a spike (link `basu`, a standalone `sd-bus` for
  musl), done only on demand, not a preemptive port.
- **Windows** wheels are x64 (AMD64). 32-bit is not built.

## Native binary pinning

The version of the `gtecble` native library bundled into the wheels is pinned
in [`native_version.txt`](native_version.txt). At build time,
[`scripts/fetch_native_libs.py`](scripts/fetch_native_libs.py) downloads the
matching `gtecble-sdk-<version>-<platform>.zip` asset from the `gtec-ble-c`
GitHub Releases and places the shared library under
`src/python/gtec_ble/lib/native/<os>/<arch>/`.

Because `gtec-ble-c` is a private repository, CI (and manual builds against
private releases) need a GitHub token with **read access to `gtec-ble-c`**,
provided via `GTEC_BLE_C_TOKEN`, `GH_TOKEN`, `GH_PAT` or `GITHUB_TOKEN` (checked
in that order; the `gh` CLI's own login is used as a fallback). In CI it is the
repository secret `GTEC_BLE_C_TOKEN`. Use either a **classic PAT with the `repo` scope**,
or a **fine-grained PAT** whose resource owner is the `gtec-ble-c` org, with
`gtec-ble-c` in its selected repositories and **`Contents: Read`** permission
(org approval may be required). A fine-grained token that does not list
`gtec-ble-c` returns HTTP 404 — GitHub hides private repos a token cannot see —
so the download fails even though the token is otherwise valid.

## Development

Requires Python 3.10+ and a C compiler (for the Cython extension).

```bash
python -m venv .venv
# Windows: .venv\Scripts\activate   |   Unix: source .venv/bin/activate
pip install -e ".[dev]"

# Fetch the native library for the current platform (needs GH_TOKEN).
python scripts/fetch_native_libs.py

# Run the unit tests (mocked native library — no hardware needed).
pytest tests/unit/python
```

`make` targets wrap the common tasks (use `make.bat <target>` on Windows):

```
make build      Fetch the native lib + build a wheel
make wheels     Build wheels for all target Pythons (cibuildwheel)
make test       Run the unit tests
make lint       Run flake8
make install    Install the built wheel
make fetch      Download the native library
make examples   Syntax-check the bundled examples
make clean      Remove build artifacts and caches
```

### Building wheels

Wheels are built with [`cibuildwheel`](https://cibuildwheel.pypa.io):

```bash
python scripts/fetch_native_libs.py            # current platform
pipx run cibuildwheel --output-dir wheelhouse   # or: python -m cibuildwheel
```

For a single local wheel you can also use `python -m build --wheel`. Only
wheels are distributed — because the package requires the native library to
import, a source (sdist) install cannot work standalone, so no sdist is
published (unsupported platforms get a clean "no matching distribution" error).

## Versioning & releasing

[`CHANGELOG.md`](CHANGELOG.md) is the **single source of truth** for the
version: the latest `## [X.Y.Z]` heading defines it. `setup.py` stamps that
value into the wheel, and the release workflow derives the `v<version>` git tag
from it (there is no separate `VERSION` file to keep in sync).

Releases are CHANGELOG-driven and publish to PyPI via OIDC trusted publishing:

1. Add a new `## [X.Y.Z] - <date>` section at the top of
   [`CHANGELOG.md`](CHANGELOG.md) and push it to `main`.
2. Trigger the **Release** workflow from the Actions tab. It reads the version
   from `CHANGELOG.md`, tags `v<version>`, builds wheels for all platforms,
   creates a GitHub Release with the wheels attached, and publishes to PyPI.
   Wheels only — no sdist.

PyPI trusted publishing must be configured once for this repository (a
"pending publisher" pointing at `release.yml`) before the first release.

## License

g.tec Non-Commercial License (GNCL) v1.0 — see
[`LICENSE-GNCL.txt`](LICENSE-GNCL.txt). For commercial use, contact
[g.tec medical engineering GmbH](https://www.gtec.at).
