Metadata-Version: 2.4
Name: senseair-sunrise
Version: 0.1.0
Summary: Python driver for Senseair Sunrise, Sunlight and S12 CO2 sensors (I2C and Modbus RTU)
Project-URL: Homepage, https://github.com/Senseair-AB/Python_Senseair_Sunrise
Author: Senseair firmware team
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: co2,i2c,modbus,s12,senseair,sensor,sunlight,sunrise
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pyserial>=3.5
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: ftdi
Requires-Dist: pyftdi>=0.55; extra == 'ftdi'
Provides-Extra: rpi
Requires-Dist: gpiozero>=2.0; extra == 'rpi'
Requires-Dist: smbus2>=0.4; extra == 'rpi'
Description-Content-Type: text/markdown

# senseair-sunrise

Python driver for **Senseair Sunrise / Sunlight** and **S12** CO2 sensors over **I2C** and
**Modbus RTU (UART)**, on Linux (incl. Raspberry Pi), Windows and macOS.

A Pythonic port of the [Senseair Arduino library](https://github.com/Senseair-AB/Senseair_Sunrise).

| Sensor | I2C class | Modbus class |
|--------|-----------|--------------|
| Sunrise / Sunlight | `SunriseI2C` | `SunriseModbus` |
| S12 | `S12I2C` | `S12Modbus` |

All four share the measurement / configuration / calibration API inherited from `SunriseBase`.
Methods raise a `SunriseError` subclass on failure and return dataclasses. A successful
`read_measurement()` still requires checking `Measurement.error_status` against the `ErrorStatus`
flags to confirm the reading is valid.

## Installation

```bash
pip install senseair-sunrise              # core (Modbus RTU works everywhere)
pip install "senseair-sunrise[rpi]"       # + smbus2/gpiozero for Raspberry Pi native I2C/GPIO
pip install "senseair-sunrise[ftdi]"      # + pyftdi for an FT232H/FT2232H USB-I2C bridge
```

To install from a local checkout of this repository (e.g. before it is published), use a path
instead of the package name — the same extras apply:

```bash
pip install -e .                          # editable install from a clone of this repo
pip install -e ".[rpi]"                   # editable + Raspberry Pi I2C/GPIO extras
pip install /path/to/python-sunrise       # non-editable, from a local path
```

Modbus RTU needs only `pyserial`. I2C and the EN/nRDY pins go through pluggable backends, so the
same sensor classes run on a Raspberry Pi or, via an FTDI USB bridge, on Windows/macOS.

On a Raspberry Pi, the EN/nRDY pins can be driven through two interchangeable backends (both
implement the `Pin` protocol):

* **`GpiodPin` (`libgpiod`)** — the install-friendly default for `examples/rpi_pins.py`. It talks to
  the kernel's GPIO character device directly, with no separate pin factory to install:

  ```bash
  sudo apt install python3-libgpiod    # prebuilt, no compiler
  python examples/rpi_pins.py --gpio-backend gpiod --en-pin 17 --nrdy-pin 27
  ```

  `GpiodPin` works with both libgpiod v1 (Raspberry Pi OS Bookworm's apt package) and v2. Pins are
  integer BCM line offsets on a `gpiochip`; on a **Raspberry Pi 5** the 40-pin header may live on a
  chip other than `/dev/gpiochip0`, so pass `--gpiochip` if line requests fail.

* **`GpiozeroPin` (`gpiozero`)** — also needs a separate pin-factory backend. Install `lgpio` from apt,
  which ships a prebuilt module:

  ```bash
  sudo apt install python3-lgpio
  ```

  Prefer apt over `pip install lgpio`: the PyPI source build needs `swig` and a C toolchain, and
  prebuilt wheels are not always available for newer Python versions. Without any backend, `gpiozero`
  falls back to its experimental sysfs pin factory, which fails on current kernels. (To see an
  apt-installed module from a virtualenv, create it with `--system-site-packages`.)

## Usage

Continuous mode over Modbus RTU:

```python
import time
from senseair_sunrise import SunriseModbus, describe_error_status

sensor = SunriseModbus("/dev/ttyUSB0")   # default address 0x68
sensor.enable()

while True:
    meas = sensor.read_measurement()
    print(f"{meas.co2_ppm} ppm  {meas.temperature_c:.2f} C")
    for flag in describe_error_status(meas.error_status):
        print("  -", flag)
    time.sleep(16)
```

Over I2C on a Raspberry Pi (or an FTDI USB bridge on Windows/macOS):

```python
from senseair_sunrise import SunriseI2C, SMBus2Bus      # FtdiI2CBus for an FTDI bridge

sensor = SunriseI2C(SMBus2Bus(1))                        # /dev/i2c-1
sensor.enable()
print(sensor.read_measurement().co2_ppm)
```

Driving the enable / nRDY pins (recommended for single mode) uses the pluggable `Pin` backends
(`GpiozeroPin`, `FtdiPin`), passed as `enable_pin=` / `nrdy_pin=`. Over an FTDI bridge, get the GPIO
controller from the bus with `FtdiI2CBus.get_gpio()` and wrap individual bits in `FtdiPin`.

### I2C bus speed and latency

The sensors support an I2C clock of **up to 100 kHz** (`constants.I2C_CLOCK_HZ`; the S12 also
supports 400 kHz Fast Mode, but this library targets 100 kHz). The two backends configure the bus
differently:

- **`FtdiI2CBus(url, frequency=...)`** defaults to a conservative **20 kHz** due to the FTDI MPSSE engine limitations, the actual bitrate for write operations over I2C is very slow (see pyftdi for details).
  The FTDI latency timer is set to `SUNRISE_I2C_IDLE_TIMEOUT_MS // 3` (~5 ms) so the host
  services the bus before the sensor sleeps after 15 ms of idle.
- **`SMBus2Bus`** runs at whatever clock the **kernel/OS** configures (e.g.
  `dtparam=i2c_arm_baudrate=` in `/boot/config.txt` on a Raspberry Pi). It cannot be set or read from
  Python, so make sure the OS configures it at or below 100 kHz.

### Examples

Runnable scripts live in [`examples/`](examples/) — `sunrise_continuous.py`, `sunrise_single.py`,
`sunrise_change_address.py`, `sunrise_scale_factor.py`, `s12_continuous.py`, `s12_single.py`, and
`calibrate.py`. Each takes `--protocol modbus|i2c` plus `--port` / `--bus` / `--ftdi`; the
continuous/single scripts also expose `--set-continuous` / `--set-single`, `--samples` and
`--period` for measurement settings. Two more cover I2C wiring setups: `multi_sensor.py` reads
several sensors sharing one bus (distinct addresses), `ftdi_pins.py` drives the EN/nRDY pins
through an FTDI bridge's GPIO, and `rpi_pins.py` does the same from a Raspberry Pi's native GPIO
and I2C port (`--gpio-backend gpiozero` or `gpiod`). Run any script with `--help` for details.

## Development

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest          # hardware-free unit tests
ruff check .
mypy src
```

## License

BSD-3-Clause. See [`LICENSE`](LICENSE).
