Metadata-Version: 2.4
Name: rpi-sensor-lib
Version: 0.2.6
Summary: A consistent Python API for Raspberry Pi sensors
Author: kazuki1729
License-Expression: MIT
Project-URL: Homepage, https://github.com/ours-labs/rpi-sensor-lib
Project-URL: Issues, https://github.com/ours-labs/rpi-sensor-lib/issues
Keywords: raspberry-pi,sensor,mcp3208,bme280,dht22,mh-z19c,gpio,spi,i2c,uart
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: System :: Hardware
Classifier: Intended Audience :: Developers
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: adc
Requires-Dist: spidev>=3.5; extra == "adc"
Provides-Extra: bme280
Requires-Dist: smbus2>=0.4; extra == "bme280"
Requires-Dist: RPi.bme280>=0.2.4; extra == "bme280"
Provides-Extra: gpio
Requires-Dist: lgpio>=0.2.2; extra == "gpio"
Provides-Extra: uart
Requires-Dist: pyserial>=3.5; extra == "uart"
Provides-Extra: direct
Requires-Dist: spidev>=3.5; extra == "direct"
Requires-Dist: smbus2>=0.4; extra == "direct"
Requires-Dist: RPi.bme280>=0.2.4; extra == "direct"
Requires-Dist: lgpio>=0.2.2; extra == "direct"
Requires-Dist: pyserial>=3.5; extra == "direct"
Provides-Extra: dev
Requires-Dist: black>=24; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: flake8>=7; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: setuptools>=77; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# rpi-sensor-lib

[![PyPI](https://img.shields.io/pypi/v/rpi-sensor-lib.svg)](https://pypi.org/project/rpi-sensor-lib/)
![Python](https://img.shields.io/badge/Python-3.9%2B-blue?logo=python&logoColor=white)
![Raspberry Pi](https://img.shields.io/badge/Raspberry%20Pi-supported-c51a4a?logo=raspberrypi&logoColor=white)
![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)

A Python library that provides a consistent API for reading temperature,
humidity, pressure, CO2, light, sound, analog input, and button sensors
connected to a Raspberry Pi.

![Hardware demo](https://raw.githubusercontent.com/ours-labs/rpi-sensor-lib/main/docs/demo.jpg)

## Supported hardware

| Sensor or component | Interface | Public class |
| --- | --- | --- |
| BME280 | I2C | `BME280Sensor` |
| DHT22 | GPIO | `RobustDHT22` |
| MH-Z19C | UART | `MHZ19C` |
| MCP3208 + Grove light sensor | SPI | `GroveLightSensor` |
| MCP3208 + Grove sound sensor | SPI | `GroveSoundSensor` |
| MCP3208 + joystick | SPI | `JoystickMCP3208` |
| MCP3208 + potentiometer | SPI | `PotentiometerMCP3208` |
| Tactile button | GPIO | `TactileButton` |

Every public class supports the `with` statement. Since version 0.2.0,
invalid configuration and communication failures raise explicit exceptions
instead of returning incorrect measurements or `None`.

## Installation

Install the core package when using a separately installed pi4gpio backend:

```bash
pip install rpi-sensor-lib
```

Install all drivers required for direct Raspberry Pi hardware access:

```bash
pip install "rpi-sensor-lib[direct]"
```

You can also install only the drivers you need:

```bash
pip install "rpi-sensor-lib[adc]"      # MCP3208 / spidev
pip install "rpi-sensor-lib[bme280]"   # BME280 / smbus2
pip install "rpi-sensor-lib[gpio]"     # DHT22 and button / lgpio
pip install "rpi-sensor-lib[uart]"     # MH-Z19C / pyserial
```

To install the latest GitHub revision directly:

```bash
pip install "rpi-sensor-lib[direct] @ git+https://github.com/ours-labs/rpi-sensor-lib.git"
```

## Raspberry Pi setup

Open `Interface Options` in `sudo raspi-config` and enable SPI, I2C, or the
serial port as required by your sensors.

### Standard MCP3208 wiring

| MCP3208 | Raspberry Pi |
| --- | --- |
| VREF / VDD | 3.3 V |
| AGND / DGND | GND |
| CLK | GPIO 11 (SCLK) |
| DOUT | GPIO 9 (MISO) |
| DIN | GPIO 10 (MOSI) |
| CS/SHDN | GPIO 8 (CE0) |

### Other defaults

| Sensor | Default configuration |
| --- | --- |
| BME280 | I2C bus 1, address `0x76` or `0x77` |
| DHT22 | GPIO 26 |
| MH-Z19C | `/dev/serial0`, 9600 baud |
| Tactile button | GPIO 17, internal pull-up, connected to GND when pressed |

## Quick start

### MCP3208 analog input

```python
from rpi_sensors import GroveLightSensor, GroveSoundSensor

with GroveLightSensor(channel=0) as light, GroveSoundSensor(channel=1) as sound:
    print(light.read_raw())
    print(light.read_voltage())
    print(sound.read_ratio())
```

```python
from rpi_sensors import JoystickMCP3208, PotentiometerMCP3208

with JoystickMCP3208(deadzone=150) as joystick:
    print(joystick.read_xy(ch_x=0, ch_y=1, normalize=True))

with PotentiometerMCP3208(channel=2, interval_sec=0.1) as potentiometer:
    print(potentiometer.read_percentage())
    print(potentiometer.read_angle())
```

### BME280

```python
from rpi_sensors import BME280InitializationError, BME280Sensor

with BME280Sensor(port=1, address=0x76) as sensor:
    try:
        temperature, humidity, pressure = sensor.read()
        print(temperature, humidity, pressure)
    except BME280InitializationError as exc:
        print(f"BME280 initialization failed: {exc}")
```

If a temporary I2C failure prevents initialization, the next `read()` call
reopens the bus and retries loading the calibration data.

### DHT22

```python
from rpi_sensors import DHT22ReadError, RobustDHT22

with RobustDHT22(pin=26, max_retries=8, read_interval=2.0) as sensor:
    try:
        print(sensor.read())
    except DHT22ReadError as exc:
        print(f"DHT22 read failed: {exc}")
```

### MH-Z19C

```python
from rpi_sensors import MHZ19C, MHZ19CError

with MHZ19C(serial_device="/dev/serial0") as sensor:
    try:
        print(sensor.read_co2())
    except MHZ19CError as exc:
        print(f"CO2 read failed: {exc}")
```

UART responses are checked for length, header, command, and checksum.

### Tactile button

```python
import time
from rpi_sensors import TactileButton

with TactileButton(pin=17) as button:
    while True:
        just_pressed, released_duration, held_time = button.update()
        if just_pressed:
            print("Pressed")
        time.sleep(0.01)
```

## pi4gpio backend

Install `pi4gpio_client` separately, then set the following variables before
starting a process that should access every sensor through `pi4gpiod`:

```bash
export RPI_SENSOR_BACKEND=pi4gpio
export PI4GPIO_SOCKET_PATH=/run/pi4gpio/pi4gpio.sock  # Only when customized
```

`RPI_SENSOR_BACKEND` accepts only `direct` or `pi4gpio`.

The DHT22 pi4gpio path validates edge count, polarity, timestamps, pulse
widths, checksum, physical ranges, and abrupt changes from the previous value.
Ambiguous waveforms fail closed and are retried instead of being corrected
into a measurement. To record raw failed edges, specify a writable absolute
path:

```bash
export RPI_SENSOR_DHT22_EDGE_LOG_PATH=/var/tmp/dht22-edge-errors.jsonl
```

## Development

```bash
python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"
.venv/bin/python -m unittest discover -s tests -v
.venv/bin/python -m build
.venv/bin/python -m twine check dist/*
```

On Windows, replace `.venv/bin/python` with `.venv\Scripts\python`.

See [CHANGELOG.md](CHANGELOG.md) for release history,
[CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidance, and
[SECURITY.md](SECURITY.md) for private vulnerability reporting.

## License

MIT License. See [LICENSE](LICENSE).

## Author

Created and maintained by
[kazuki1729](https://github.com/kazuki1729).
