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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """pympacds-gpio — GPIO server service for pympacds."""
2
+
3
+ __version__ = "0.1.0"
@@ -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()