pympacds-gpio 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pympacds_gpio-0.1.0/PKG-INFO +104 -0
- pympacds_gpio-0.1.0/README.md +93 -0
- pympacds_gpio-0.1.0/pyproject.toml +69 -0
- pympacds_gpio-0.1.0/setup.cfg +4 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio/__init__.py +3 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio/contracts.py +58 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio/service.py +363 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio/shim.py +192 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/PKG-INFO +104 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/SOURCES.txt +17 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/dependency_links.txt +1 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/entry_points.txt +2 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/requires.txt +2 -0
- pympacds_gpio-0.1.0/src/pympacds_gpio.egg-info/top_level.txt +1 -0
- pympacds_gpio-0.1.0/tests/test_contracts.py +104 -0
- pympacds_gpio-0.1.0/tests/test_dbus_integration.py +141 -0
- pympacds_gpio-0.1.0/tests/test_schema.py +30 -0
- pympacds_gpio-0.1.0/tests/test_service.py +320 -0
- pympacds_gpio-0.1.0/tests/test_shim.py +349 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pympacds-gpio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GPIO server service for pympacds
|
|
5
|
+
Author-email: Oscar Diaz <odiaz@ieee.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pympacds>=0.2.0
|
|
10
|
+
Requires-Dist: gpiod>=2.0
|
|
11
|
+
|
|
12
|
+
# pympacds-gpio
|
|
13
|
+
|
|
14
|
+
GPIO server service for [pympacds](https://github.com/dargor0/pympacds). Monitors
|
|
15
|
+
GPIO line state and edge events and exposes reads, writes, and change
|
|
16
|
+
notifications over D-Bus.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Reads (`get_value`, `get_lines`) and writes (`set_value`) of GPIO lines over
|
|
21
|
+
D-Bus, with per-output-line default (reset) values
|
|
22
|
+
(`get_default_value` / `set_default_value`).
|
|
23
|
+
- Edge-event watching (`line_changed` signal) driven by the kernel
|
|
24
|
+
`gpiod` edge-event API, polled asynchronously with a short timeout so the
|
|
25
|
+
event loop is never stalled.
|
|
26
|
+
- Kernel-side debounce via `debounce_us` (passed to libgpiod), and
|
|
27
|
+
`active_low` inversion for watched lines. No software debounce.
|
|
28
|
+
- A configurable line-name mapping (`[gpio.linemap]`) so lines can be
|
|
29
|
+
referenced by human-readable name or numeric offset on the D-Bus API.
|
|
30
|
+
- Runtime configuration: `[gpio]` keys are writable via the framework
|
|
31
|
+
`ConfigContract`.
|
|
32
|
+
- Exports the framework `HealthContract` (always) and `ConfigContract`
|
|
33
|
+
(opt-in via `[dbus] contract_config = true`) alongside the GPIO contract.
|
|
34
|
+
|
|
35
|
+
### Why gpiod
|
|
36
|
+
|
|
37
|
+
The service uses the [gpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/)
|
|
38
|
+
Python bindings (libgpiod v2 API) because it is the reference userspace
|
|
39
|
+
interface to the kernel GPIO character-device subsystem: line requests, edge
|
|
40
|
+
events, and debounce are all handled in the kernel. It is isolated in this
|
|
41
|
+
service's own package, so the core `pympacds` framework keeps its
|
|
42
|
+
zero-dependency property.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pympacds-gpio
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
The service reads its parameters from the `[gpio]` INI section and the optional
|
|
53
|
+
`[gpio.linemap]` section (see `config/gpio.ini.example`):
|
|
54
|
+
|
|
55
|
+
| Key | Default | Description |
|
|
56
|
+
|-----|---------|-------------|
|
|
57
|
+
| `chip` | `""` | GPIO chip path (empty = auto-detect) |
|
|
58
|
+
| `lines` | `""` | Comma-separated line names/offsets to expose and read |
|
|
59
|
+
| `watch_lines` | `""` | Lines watched for edge events |
|
|
60
|
+
| `edge` | `both` | Edge to watch: `rising`, `falling`, `both` |
|
|
61
|
+
| `debounce_us` | `0` | Kernel debounce period (µs) for watched lines |
|
|
62
|
+
| `active_low` | `false` | Invert line logic for watched lines |
|
|
63
|
+
| `output_lines` | `""` | Lines driven as outputs |
|
|
64
|
+
| `default_values` | `""` | `line=value` pairs for output defaults (e.g. `relay1=0,5=1`) |
|
|
65
|
+
|
|
66
|
+
The `[gpio.linemap]` section maps human-readable names to offsets (`btn = 0`).
|
|
67
|
+
Names must not begin with a digit.
|
|
68
|
+
|
|
69
|
+
Run with:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pympacds-gpio -c /etc/pympacds/gpio.ini
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Device access (permissions)
|
|
76
|
+
|
|
77
|
+
The service does **not** require root. Access to `/dev/gpiochip*` is granted
|
|
78
|
+
via a udev rule (shipped by the Debian package) that assigns the device to the
|
|
79
|
+
`gpio` group with mode `0660`; add the service user to the `gpio` group:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sudo usermod -aG gpio <service-user>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## D-Bus API
|
|
86
|
+
|
|
87
|
+
Interface `org.pympacds.GPIO` at object path `/org/pympacds/gpio`:
|
|
88
|
+
|
|
89
|
+
| Member | Type | Description |
|
|
90
|
+
|--------|------|-------------|
|
|
91
|
+
| `get_value(line)` | `get_value(s) -> b` | Read a line's value |
|
|
92
|
+
| `set_value(line, value)` | `set_value(sb) -> b` | Set an output line |
|
|
93
|
+
| `get_default_value(line)` | `get_default_value(s) -> b` | Default (reset) value of an output |
|
|
94
|
+
| `set_default_value(line, value)` | `set_default_value(sb) -> b` | Set the default value |
|
|
95
|
+
| `get_lines()` | `get_lines() -> s` | JSON values of all exposed lines |
|
|
96
|
+
| `status()` | `status() -> s` | JSON chip/line configuration + values |
|
|
97
|
+
| `line_changed(name, offset, value)` | signal `(sub)` | Emitted on a watched edge event |
|
|
98
|
+
| `watched_lines` | property `(as)` | List of watched lines |
|
|
99
|
+
|
|
100
|
+
A `line` argument is a decimal offset or a `[gpio.linemap]` name, resolved
|
|
101
|
+
number-first-then-name. An unresolvable line produces a D-Bus error reply.
|
|
102
|
+
|
|
103
|
+
The framework `HealthContract` (at `.../health`) and `ConfigContract`
|
|
104
|
+
(`[dbus] contract_config = true`) are also exported.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# pympacds-gpio
|
|
2
|
+
|
|
3
|
+
GPIO server service for [pympacds](https://github.com/dargor0/pympacds). Monitors
|
|
4
|
+
GPIO line state and edge events and exposes reads, writes, and change
|
|
5
|
+
notifications over D-Bus.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Reads (`get_value`, `get_lines`) and writes (`set_value`) of GPIO lines over
|
|
10
|
+
D-Bus, with per-output-line default (reset) values
|
|
11
|
+
(`get_default_value` / `set_default_value`).
|
|
12
|
+
- Edge-event watching (`line_changed` signal) driven by the kernel
|
|
13
|
+
`gpiod` edge-event API, polled asynchronously with a short timeout so the
|
|
14
|
+
event loop is never stalled.
|
|
15
|
+
- Kernel-side debounce via `debounce_us` (passed to libgpiod), and
|
|
16
|
+
`active_low` inversion for watched lines. No software debounce.
|
|
17
|
+
- A configurable line-name mapping (`[gpio.linemap]`) so lines can be
|
|
18
|
+
referenced by human-readable name or numeric offset on the D-Bus API.
|
|
19
|
+
- Runtime configuration: `[gpio]` keys are writable via the framework
|
|
20
|
+
`ConfigContract`.
|
|
21
|
+
- Exports the framework `HealthContract` (always) and `ConfigContract`
|
|
22
|
+
(opt-in via `[dbus] contract_config = true`) alongside the GPIO contract.
|
|
23
|
+
|
|
24
|
+
### Why gpiod
|
|
25
|
+
|
|
26
|
+
The service uses the [gpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/)
|
|
27
|
+
Python bindings (libgpiod v2 API) because it is the reference userspace
|
|
28
|
+
interface to the kernel GPIO character-device subsystem: line requests, edge
|
|
29
|
+
events, and debounce are all handled in the kernel. It is isolated in this
|
|
30
|
+
service's own package, so the core `pympacds` framework keeps its
|
|
31
|
+
zero-dependency property.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install pympacds-gpio
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
The service reads its parameters from the `[gpio]` INI section and the optional
|
|
42
|
+
`[gpio.linemap]` section (see `config/gpio.ini.example`):
|
|
43
|
+
|
|
44
|
+
| Key | Default | Description |
|
|
45
|
+
|-----|---------|-------------|
|
|
46
|
+
| `chip` | `""` | GPIO chip path (empty = auto-detect) |
|
|
47
|
+
| `lines` | `""` | Comma-separated line names/offsets to expose and read |
|
|
48
|
+
| `watch_lines` | `""` | Lines watched for edge events |
|
|
49
|
+
| `edge` | `both` | Edge to watch: `rising`, `falling`, `both` |
|
|
50
|
+
| `debounce_us` | `0` | Kernel debounce period (µs) for watched lines |
|
|
51
|
+
| `active_low` | `false` | Invert line logic for watched lines |
|
|
52
|
+
| `output_lines` | `""` | Lines driven as outputs |
|
|
53
|
+
| `default_values` | `""` | `line=value` pairs for output defaults (e.g. `relay1=0,5=1`) |
|
|
54
|
+
|
|
55
|
+
The `[gpio.linemap]` section maps human-readable names to offsets (`btn = 0`).
|
|
56
|
+
Names must not begin with a digit.
|
|
57
|
+
|
|
58
|
+
Run with:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pympacds-gpio -c /etc/pympacds/gpio.ini
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Device access (permissions)
|
|
65
|
+
|
|
66
|
+
The service does **not** require root. Access to `/dev/gpiochip*` is granted
|
|
67
|
+
via a udev rule (shipped by the Debian package) that assigns the device to the
|
|
68
|
+
`gpio` group with mode `0660`; add the service user to the `gpio` group:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
sudo usermod -aG gpio <service-user>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## D-Bus API
|
|
75
|
+
|
|
76
|
+
Interface `org.pympacds.GPIO` at object path `/org/pympacds/gpio`:
|
|
77
|
+
|
|
78
|
+
| Member | Type | Description |
|
|
79
|
+
|--------|------|-------------|
|
|
80
|
+
| `get_value(line)` | `get_value(s) -> b` | Read a line's value |
|
|
81
|
+
| `set_value(line, value)` | `set_value(sb) -> b` | Set an output line |
|
|
82
|
+
| `get_default_value(line)` | `get_default_value(s) -> b` | Default (reset) value of an output |
|
|
83
|
+
| `set_default_value(line, value)` | `set_default_value(sb) -> b` | Set the default value |
|
|
84
|
+
| `get_lines()` | `get_lines() -> s` | JSON values of all exposed lines |
|
|
85
|
+
| `status()` | `status() -> s` | JSON chip/line configuration + values |
|
|
86
|
+
| `line_changed(name, offset, value)` | signal `(sub)` | Emitted on a watched edge event |
|
|
87
|
+
| `watched_lines` | property `(as)` | List of watched lines |
|
|
88
|
+
|
|
89
|
+
A `line` argument is a decimal offset or a `[gpio.linemap]` name, resolved
|
|
90
|
+
number-first-then-name. An unresolvable line produces a D-Bus error reply.
|
|
91
|
+
|
|
92
|
+
The framework `HealthContract` (at `.../health`) and `ConfigContract`
|
|
93
|
+
(`[dbus] contract_config = true`) are also exported.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pympacds-gpio"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "GPIO server service for pympacds"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name="Oscar Diaz", email="odiaz@ieee.org"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pympacds>=0.2.0",
|
|
17
|
+
"gpiod>=2.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
pympacds-gpio = "pympacds_gpio.service:main"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
asyncio_mode = "auto"
|
|
28
|
+
testpaths = ["tests"]
|
|
29
|
+
pythonpath = ["src", "../../src"]
|
|
30
|
+
addopts = [
|
|
31
|
+
"-v",
|
|
32
|
+
"--tb=short",
|
|
33
|
+
"--cov=pympacds_gpio",
|
|
34
|
+
"--cov-report=term-missing",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 100
|
|
39
|
+
target-version = "py310"
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
43
|
+
|
|
44
|
+
# D-Bus type-signature string annotations ("s", "b", "sub", "as", ...) are the
|
|
45
|
+
# dbus-fast/dbus-next convention, not Python type references.
|
|
46
|
+
[tool.ruff.lint.per-file-ignores]
|
|
47
|
+
"src/pympacds_gpio/contracts.py" = ["F821", "F722"]
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
python_version = "3.10"
|
|
51
|
+
ignore_missing_imports = true
|
|
52
|
+
mypy_path = ["../../src"]
|
|
53
|
+
|
|
54
|
+
# The core framework has its own (pre-existing) typing debt ("object"-typed
|
|
55
|
+
# D-Bus globals, D-Bus signature strings). Treat it as Any rather than
|
|
56
|
+
# re-checking it from the service.
|
|
57
|
+
[[tool.mypy.overrides]]
|
|
58
|
+
module = "pympacds"
|
|
59
|
+
follow_imports = "silent"
|
|
60
|
+
|
|
61
|
+
[[tool.mypy.overrides]]
|
|
62
|
+
module = "pympacds.*"
|
|
63
|
+
follow_imports = "silent"
|
|
64
|
+
|
|
65
|
+
# D-Bus type-signature annotations ("s", "b", "sub", "as", ...) are the
|
|
66
|
+
# dbus-fast/dbus-next convention, not Python types.
|
|
67
|
+
[[tool.mypy.overrides]]
|
|
68
|
+
module = "pympacds_gpio.contracts"
|
|
69
|
+
disable_error_code = ["name-defined", "valid-type"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""GPIO D-Bus contract (REQ-GPIO-005)."""
|
|
2
|
+
|
|
3
|
+
from pympacds import get_dbus_lib
|
|
4
|
+
from pympacds.contracts import ServiceContract, dbus_method, dbus_property, dbus_signal
|
|
5
|
+
|
|
6
|
+
_PropertyAccess = get_dbus_lib().PropertyAccess # type: ignore[attr-defined]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GpioContract(ServiceContract):
|
|
10
|
+
"""GPIO read/write/watch interface."""
|
|
11
|
+
|
|
12
|
+
iface_name = "GPIO"
|
|
13
|
+
iface_version = "1.0.0"
|
|
14
|
+
iface_provides = ["gpio"]
|
|
15
|
+
|
|
16
|
+
def __init__(self, ifname: str, base):
|
|
17
|
+
super().__init__(ifname, base)
|
|
18
|
+
self._require(
|
|
19
|
+
"dbus_gpio_get_value",
|
|
20
|
+
"dbus_gpio_set_value",
|
|
21
|
+
"dbus_gpio_get_default_value",
|
|
22
|
+
"dbus_gpio_set_default_value",
|
|
23
|
+
"dbus_gpio_get_lines",
|
|
24
|
+
"dbus_gpio_status",
|
|
25
|
+
"dbus_gpio_watched_lines",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@dbus_method()
|
|
29
|
+
def get_value(self, line: "s") -> "b":
|
|
30
|
+
return self.base.dbus_gpio_get_value(line)
|
|
31
|
+
|
|
32
|
+
@dbus_method()
|
|
33
|
+
def set_value(self, line: "s", value: "b") -> "b":
|
|
34
|
+
return self.base.dbus_gpio_set_value(line, value)
|
|
35
|
+
|
|
36
|
+
@dbus_method()
|
|
37
|
+
def get_default_value(self, line: "s") -> "b":
|
|
38
|
+
return self.base.dbus_gpio_get_default_value(line)
|
|
39
|
+
|
|
40
|
+
@dbus_method()
|
|
41
|
+
def set_default_value(self, line: "s", value: "b") -> "b":
|
|
42
|
+
return self.base.dbus_gpio_set_default_value(line, value)
|
|
43
|
+
|
|
44
|
+
@dbus_method()
|
|
45
|
+
def get_lines(self) -> "s":
|
|
46
|
+
return self.base.dbus_gpio_get_lines()
|
|
47
|
+
|
|
48
|
+
@dbus_method()
|
|
49
|
+
def status(self) -> "s":
|
|
50
|
+
return self.base.dbus_gpio_status()
|
|
51
|
+
|
|
52
|
+
@dbus_signal()
|
|
53
|
+
def line_changed(self, name: "s", offset: "u", value: "b") -> "sub":
|
|
54
|
+
return [name, offset, value]
|
|
55
|
+
|
|
56
|
+
@dbus_property(access=_PropertyAccess.READ)
|
|
57
|
+
def watched_lines(self) -> "as":
|
|
58
|
+
return self.base.dbus_gpio_watched_lines()
|