Metadata-Version: 2.4
Name: rpi-hw-lock
Version: 0.1.1
Summary: Temporarily stop systemd services for exclusive Raspberry Pi hardware access
Author: Kazuki Kumahata
License-Expression: MIT
Project-URL: Homepage, https://github.com/ours-labs/rpi-hw-lock
Project-URL: Repository, https://github.com/ours-labs/rpi-hw-lock
Project-URL: Issues, https://github.com/ours-labs/rpi-hw-lock/issues
Keywords: raspberry-pi,systemd,gpio,spi,i2c,uart,lock,mutex,hardware
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
Dynamic: license-file

# rpi-hw-lock

`rpi-hw-lock` is a lightweight Python library for safely obtaining exclusive access to Raspberry Pi hardware that is normally used by long-running systemd services.

It temporarily stops selected services before a block of code accesses GPIO, SPI, I2C, or UART devices, then restarts only the services that were active originally. Cleanup runs even when the protected block raises an exception.

> This library controls system services. Review the service list and sudoers rules carefully before using it on a production device.

## Installation

```bash
pip install rpi-hw-lock
```

The package has no runtime dependencies outside the Python standard library. It requires Python 3.9 or later and a Linux system using systemd.

## Usage

```python
from rpi_hw_lock import exclusive_hardware_access

with exclusive_hardware_access(["sensor-tiered-client.service"]):
    # Access GPIO, SPI, I2C, or UART hardware here.
    ...
# Services that were active before the block are running again here.
```

Multiple services can be controlled together:

```python
with exclusive_hardware_access(["service-a.service", "service-b.service"]):
    ...
```

## Configure sudoers

Stopping and starting a system service normally requires elevated privileges. Create a narrowly scoped sudoers rule that permits only the required actions for the required services:

```bash
sudo visudo -f /etc/sudoers.d/rpi-hw-lock
```

Example, adjusted for your user and service name:

```text
pi ALL=(root) NOPASSWD: /usr/bin/systemctl stop sensor-tiered-client.service
pi ALL=(root) NOPASSWD: /usr/bin/systemctl start sensor-tiered-client.service
```

Do not grant unrestricted passwordless access to `systemctl`.

You can check the configuration before attempting exclusive access:

```python
from rpi_hw_lock import check_permissions

check_permissions(["sensor-tiered-client.service"])
```

## API

| API | Purpose |
| --- | --- |
| `exclusive_hardware_access(services, timeout=10.0, verify_stopped=True)` | Temporarily stops one or more services and restores their original active state. |
| `check_permissions(services, timeout=5.0)` | Checks whether non-interactive sudo access is configured. |
| `is_active(service)` | Returns whether a service is currently active. |
| `ServiceControlError` | Raised when a stop, start, or verification operation fails. |

## Behavior and limitations

- Services that were already inactive are not started when the context exits.
- With `verify_stopped=True`, each stopped service is checked before the protected block runs.
- A normal `systemctl stop` is not treated as a failure by `Restart=on-failure`.
- If restarting a service fails while another exception is already propagating, the restart failure is logged so that the original exception is not hidden. Operators must monitor logs and recover the service manually.
- This library coordinates with systemd. It is not a cross-process lock for programs that access the same hardware outside those services.

## Development

```bash
python -m unittest discover -s tests -v
python -m build
python -m twine check dist/*
```

## License

MIT
