Compare commits

...

38 Commits

Author SHA1 Message Date
dependabot[bot]
e38a45ade9
Bump mypy from 1.16.1 to 1.17.0 (#376)
Bumps [mypy](https://github.com/python/mypy) from 1.16.1 to 1.17.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.16.1...v1.17.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 09:55:53 +03:00
dependabot[bot]
0383c19d77
Bump flake8 from 7.2.0 to 7.3.0 (#374)
Bumps [flake8](https://github.com/pycqa/flake8) from 7.2.0 to 7.3.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-29 10:42:24 +03:00
dependabot[bot]
f9aa9b0485
Bump mypy from 1.16.0 to 1.16.1 (#371)
Bumps [mypy](https://github.com/python/mypy) from 1.16.0 to 1.16.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.16.0...v1.16.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-17 00:49:20 +03:00
dependabot[bot]
49b06ffa84
Bump mypy from 1.15.0 to 1.16.0 (#368)
Bumps [mypy](https://github.com/python/mypy) from 1.15.0 to 1.16.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 21:55:34 +03:00
dependabot[bot]
94f5e1ba18
Bump pylint from 3.3.6 to 3.3.7 (#366)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.6 to 3.3.7.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.6...v3.3.7)

---
updated-dependencies:
- dependency-name: pylint
  dependency-version: 3.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 21:22:28 +03:00
dependabot[bot]
a9f904da77
Bump actions/setup-python from 5.5.0 to 5.6.0 (#365)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.5.0...v5.6.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 21:23:18 +03:00
dependabot[bot]
7449569925
Bump flake8 from 7.1.2 to 7.2.0 (#364)
Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.2 to 7.2.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 22:59:47 +03:00
dependabot[bot]
81c89d0385
Bump actions/setup-python from 5.4.0 to 5.5.0 (#363)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.4.0...v5.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 20:49:35 +02:00
dependabot[bot]
e5a86e2593
Bump pylint from 3.3.4 to 3.3.6 (#362)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.4 to 3.3.6.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.4...v3.3.6)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 21:23:10 +02:00
dependabot[bot]
7a07346934
Bump pre-commit from 4.1.0 to 4.2.0 (#361)
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.1.0...v4.2.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 21:18:42 +02:00
dependabot[bot]
2890971f59
Bump isort from 6.0.0 to 6.0.1 (#359)
Bumps [isort](https://github.com/PyCQA/isort) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/PyCQA/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PyCQA/isort/compare/6.0.0...6.0.1)

---
updated-dependencies:
- dependency-name: isort
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 20:50:19 +02:00
dependabot[bot]
831af93917
Bump flake8 from 7.1.1 to 7.1.2 (#358)
Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.1 to 7.1.2.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.1...7.1.2)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 21:51:51 +02:00
Tomer
fedab2a3c7
Bump version to 2025.2.1 (#357) 2025-02-14 21:26:11 +02:00
Tomer
89eb52ee56
Skip failed entities instead of stop loading all of them (#356)
change async_setup_platform to just skip entities with HW issues instead of throwing and skipping the rest of the entities.
2025-02-14 21:22:43 +02:00
dependabot[bot]
003993a3e5
Bump mypy from 1.14.1 to 1.15.0 (#355)
Bumps [mypy](https://github.com/python/mypy) from 1.14.1 to 1.15.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.14.1...v1.15.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 21:16:57 +02:00
dependabot[bot]
797a4522e4
Bump isort from 5.13.2 to 6.0.0 (#352)
Bumps [isort](https://github.com/pycqa/isort) from 5.13.2 to 6.0.0.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.13.2...6.0.0)

---
updated-dependencies:
- dependency-name: isort
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 20:34:36 +02:00
dependabot[bot]
df4788feba
Bump pylint from 3.3.3 to 3.3.4 (#353)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.3 to 3.3.4.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.3...v3.3.4)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 20:30:52 +02:00
dependabot[bot]
974a66e7c5
Bump black from 24.10.0 to 25.1.0 (#351)
Bumps [black](https://github.com/psf/black) from 24.10.0 to 25.1.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.10.0...25.1.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 20:29:46 +02:00
Tomer
1bbefe1f7b
Bump version to 2025.2.0 (#350) 2025-02-01 13:45:27 +02:00
Tomer
3400619946
Minor error handling and tracing improvement (#348)
* 1. Better exceptions for ports that are used by us or other components.
2. Improve tracing to understand failures better.

* Use the right exception class
2025-01-31 22:51:20 +02:00
dependabot[bot]
bdfac26fae
Bump actions/stale from 9.0.0 to 9.1.0 (#344)
Bumps [actions/stale](https://github.com/actions/stale) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-30 11:00:54 +02:00
dependabot[bot]
fa42ae379e
Bump actions/setup-python from 5.3.0 to 5.4.0 (#347)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5.3.0...v5.4.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-28 20:57:21 +02:00
dependabot[bot]
7c0d121839
Bump pre-commit from 4.0.1 to 4.1.0 (#346)
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.0.1 to 4.1.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.0.1...v4.1.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 20:22:16 +02:00
Tomer
c894d86b8a
Require configuration.yaml section for bug reports (#341)
* Require configuration.yaml section for bug reports 

Specifically state we should get the configuration yaml section as part of bug reports.

* Update README.md

Co-authored-by: Shay Levy <levyshay1@gmail.com>

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-01-06 13:25:52 +02:00
dependabot[bot]
88fc778a13
Bump pylint from 3.3.2 to 3.3.3 (#339)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 21:08:04 +02:00
dependabot[bot]
242bad8b82
Bump mypy from 1.14.0 to 1.14.1 (#338)
Bumps [mypy](https://github.com/python/mypy) from 1.14.0 to 1.14.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.14.0...v1.14.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 20:47:06 +02:00
Tomer
58c5d678ee
Bump version to 2024.12.2 (#336) 2024-12-27 23:23:48 +02:00
Tomer
aeb27b45d6
Hub refactor (#332)
* Hub refactor:
As discussed in: https://github.com/thecode/ha-rpi_gpio/discussions/325 :
1. Each entity will do its own registration.
2. State can not be changed before we register for fd changes.
3. No need to call get_line_value from events.
4. Cover was changed to use the async APIs

* Additional changes:
1. Add validationt to switch __init__ so if the line is busy it will fail early and not in async_added_to_hass
2. Remove the need to send to hub the HA entities. This makes the hub more generic.
3. Fix cover initial state to be read from the binary sensor instead of assuming the cover is closed.
2024-12-27 22:48:36 +02:00
dependabot[bot]
6243c48ad1
Bump mypy from 1.13.0 to 1.14.0 (#334)
Bumps [mypy](https://github.com/python/mypy) from 1.13.0 to 1.14.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-23 21:44:51 +02:00
Shay Levy
7e4eda3636
Remove wrong comment about cover (#329) 2024-12-03 09:51:25 +02:00
dependabot[bot]
9d6dea6175
Bump pylint from 3.3.1 to 3.3.2 (#324)
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.1...v3.3.2)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 08:26:19 +02:00
Tomer
82afeeaf69
Bump version to 2024.12.1 (#328) 2024-12-03 08:22:54 +02:00
Tomer
aa563dfd45
Fix regressions in cover (#327) 2024-12-03 08:14:57 +02:00
Tomer
902c9808a2
Bump version to 2024.12.0 (#323) 2024-12-02 15:14:19 +02:00
Tomer
3e4867c3ff
Fix wrong sensor value when pull_mode=UP (#322) 2024-12-02 15:00:41 +02:00
n0n0
77c33715a8
Update README.md (#321)
Line 197, persistent key documented in double with line 194
2024-11-28 23:11:56 +02:00
Tomer
91ab3b2a3e
Bump version to 2024.11.0 (#318) 2024-11-10 18:17:04 +02:00
Tomer
769706d0ad
Fix cover config with no ID (#317)
* Fix cover config with no ID
2024-11-10 18:01:05 +02:00
9 changed files with 162 additions and 185 deletions

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚀 Run stale - name: 🚀 Run stale
uses: actions/stale@v9.0.0 uses: actions/stale@v9.1.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 14 days-before-stale: 14

View File

@ -23,7 +23,7 @@ jobs:
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.6.0
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies

View File

@ -194,7 +194,6 @@ switch:
| `persistent` | no | `false` | boolean | If true, the switch state will be persistent in HA and will be restored if HA restart / crash | | `persistent` | no | `false` | boolean | If true, the switch state will be persistent in HA and will be restored if HA restart / crash |
| `pull_mode` | no | `AS_IS` | string | Type of internal pull resistor to use: `UP` - pull-up resistor, `DOWN` - pull-down resistor, `AS-IS` no change | | `pull_mode` | no | `AS_IS` | string | Type of internal pull resistor to use: `UP` - pull-up resistor, `DOWN` - pull-down resistor, `AS-IS` no change |
| `drive` |no | `PUSH_PULL`|string | control drive configuration of the GPIO, determines how the line behaves when it is set to output mode; `PUSH_PULL`, GPIO line can both source and sink current, can actively drive the line to both high and low states. `OPEN-DRAIN`, GPPIO can only sink current (drive the line to low) and is otherwise left floating, and `OPEN-SOURCE` the reverse. | `drive` |no | `PUSH_PULL`|string | control drive configuration of the GPIO, determines how the line behaves when it is set to output mode; `PUSH_PULL`, GPIO line can both source and sink current, can actively drive the line to both high and low states. `OPEN-DRAIN`, GPPIO can only sink current (drive the line to low) and is otherwise left floating, and `OPEN-SOURCE` the reverse.
|`persistent` | no | `false` | boolean | If true, the switch state will be persistent in HA and will be restored if HA restart / crash. |
For more details about the GPIO layout, visit the Wikipedia [article](https://en.wikipedia.org/wiki/Raspberry_Pi#General_purpose_input-output_(GPIO)_connector) about the Raspberry Pi. For more details about the GPIO layout, visit the Wikipedia [article](https://en.wikipedia.org/wiki/Raspberry_Pi#General_purpose_input-output_(GPIO)_connector) about the Raspberry Pi.
@ -211,4 +210,4 @@ switch:
``` ```
# Reporting issues # Reporting issues
*Before* reporting issues please enable debug logging as described [here](https://www.home-assistant.io/docs/configuration/troubleshooting/#enabling-debug-logging), check logs and report issue attaching the log file. *Before* reporting issues please enable debug logging as described [here](https://www.home-assistant.io/docs/configuration/troubleshooting/#enabling-debug-logging), check logs and report issue attaching the log file and the relevant YAML section.

View File

@ -12,6 +12,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_SENSORS, CONF_NAME, CONF_PORT, CONF_UNIQUE_ID from homeassistant.const import CONF_SENSORS, CONF_NAME, CONF_PORT, CONF_UNIQUE_ID
from .hub import BIAS from .hub import BIAS
CONF_INVERT_LOGIC = "invert_logic" CONF_INVERT_LOGIC = "invert_logic"
DEFAULT_INVERT_LOGIC = False DEFAULT_INVERT_LOGIC = False
CONF_BOUNCETIME = "bouncetime" CONF_BOUNCETIME = "bouncetime"
@ -51,17 +52,20 @@ async def async_setup_platform(
sensors = [] sensors = []
for sensor in config.get(CONF_SENSORS): for sensor in config.get(CONF_SENSORS):
sensors.append( try:
GPIODBinarySensor( sensors.append(
hub, GPIODBinarySensor(
sensor[CONF_NAME], hub,
sensor[CONF_PORT], sensor[CONF_NAME],
sensor.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{sensor[CONF_PORT]}_{sensor[CONF_NAME].lower().replace(' ', '_')}", sensor[CONF_PORT],
sensor.get(CONF_INVERT_LOGIC), sensor.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{sensor[CONF_PORT]}_{sensor[CONF_NAME].lower().replace(' ', '_')}",
sensor.get(CONF_PULL_MODE), sensor.get(CONF_INVERT_LOGIC),
sensor.get(CONF_BOUNCETIME) sensor.get(CONF_PULL_MODE),
sensor.get(CONF_BOUNCETIME)
)
) )
) except Exception as e:
_LOGGER.error(f"Failed to add binary sensor {sensor[CONF_NAME]} for port {sensor[CONF_PORT]}: {e}")
async_add_entities(sensors) async_add_entities(sensors)
@ -78,12 +82,22 @@ class GPIODBinarySensor(BinarySensorEntity):
self._active_low = active_low self._active_low = active_low
self._bias = bias self._bias = bias
self._debounce = debounce self._debounce = debounce
self._line, current_is_on = self._hub.add_sensor(self._port, self._active_low, self._bias, self._debounce)
self._attr_is_on = current_is_on
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
await super().async_added_to_hass() await super().async_added_to_hass()
self._hub.add_sensor(self, self._port, self._active_low, self._bias, self._debounce) _LOGGER.debug(f"GPIODBinarySensor async_added_to_hass: Adding fd:{self._line.fd}")
self.async_write_ha_state() self._hub._hass.loop.add_reader(self._line.fd, self.handle_event)
async def async_will_remove_from_hass(self) -> None:
await super().async_will_remove_from_hass()
_LOGGER.debug(f"GPIODBinarySensor async_will_remove_from_hass: Removing fd:{self._line.fd}")
self._hub._hass.loop.remove_reader(self._line.fd)
self._line.release()
def handle_event(self): def handle_event(self):
self._attr_is_on = self._hub.get_line_value(self._port) for event in self._line.read_edge_events():
self._attr_is_on = True if event.event_type is event.Type.RISING_EDGE else False
_LOGGER.debug(f"Event: {event}. New line value: {self._attr_is_on}")
self.schedule_update_ha_state(False) self.schedule_update_ha_state(False)

View File

@ -17,6 +17,7 @@ from homeassistant.const import CONF_COVERS, CONF_NAME, CONF_UNIQUE_ID
from .hub import BIAS, DRIVE from .hub import BIAS, DRIVE
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import voluptuous as vol import voluptuous as vol
import asyncio
CONF_RELAY_PIN = "relay_pin" CONF_RELAY_PIN = "relay_pin"
CONF_RELAY_TIME = "relay_time" CONF_RELAY_TIME = "relay_time"
@ -70,21 +71,24 @@ async def async_setup_platform(
invert_relay = config[CONF_INVERT_RELAY] invert_relay = config[CONF_INVERT_RELAY]
covers = [] covers = []
for cover in config.get(CONF_COVERS): for cover in config.get(CONF_COVERS):
covers.append( try:
GPIODCover( covers.append(
hub, GPIODCover(
cover[CONF_NAME], hub,
cover.get(CONF_RELAY_PIN), cover[CONF_NAME],
relay_time, cover.get(CONF_RELAY_PIN),
invert_relay, relay_time,
"AS_IS", invert_relay,
"PUSH_PULL", "AS_IS",
cover.get(CONF_STATE_PIN), "PUSH_PULL",
state_pull_mode, cover.get(CONF_STATE_PIN),
invert_state, state_pull_mode,
cover.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{cover.get(CONF_RELAY_PORT) or cover.get("relay_pin")}_{cover[CONF_NAME].lower().replace(' ', '_')}", invert_state,
cover.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{cover.get(CONF_RELAY_PIN)}_{cover[CONF_NAME].lower().replace(' ', '_')}",
)
) )
) except Exception as e:
_LOGGER.error(f"Failed to add cover {cover[CONF_NAME]} for port {cover.get(CONF_RELAY_PIN)}:{cover.get(CONF_STATE_PIN)}: {e}")
async_add_entities(covers) async_add_entities(covers)
@ -105,51 +109,66 @@ class GPIODCover(CoverEntity):
self._state_port = state_port self._state_port = state_port
self._state_bias = state_bias self._state_bias = state_bias
self._state_active_low = state_active_low self._state_active_low = state_active_low
self._attr_is_closed = False != state_active_low self._relay_line, self._state_line, current_is_on = self._hub.add_cover(
self._relay_port, self._relay_active_low, self._relay_bias, self._relay_drive,
self._state_port, self._state_bias, self._state_active_low)
self._attr_is_closed = current_is_on
self.is_on = current_is_on
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
await super().async_added_to_hass() await super().async_added_to_hass()
self._hub.add_cover(self, self._relay_port, self._relay_active_low, self._relay_bias, _LOGGER.debug(f"GPIODCover async_added_to_hass: Adding fd:{self._state_line.fd}")
self._relay_drive, self._state_port, self._state_bias, self._state_active_low) self._hub._hass.loop.add_reader(self._state_line.fd, self.handle_event)
self.async_write_ha_state()
async def async_will_remove_from_hass(self) -> None:
await super().async_will_remove_from_hass()
_LOGGER.debug(f"GPIODCover async_will_remove_from_hass: Removing fd:{self._state_line.fd}")
self._hub._hass.loop.remove_reader(self._state_line.fd)
self._relay_line.release()
self._state_line.release()
def handle_event(self): def handle_event(self):
self._attr_is_closed = self._hub.get_line_value(self._state_port) for event in self._state_line.read_edge_events():
self._attr_is_closed = True if event.event_type is event.Type.RISING_EDGE else False
_LOGGER.debug(f"Event: {event}. New _attr_is_closed value: {self._attr_is_closed}")
self.schedule_update_ha_state(False) self.schedule_update_ha_state(False)
def close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
_LOGGER.debug(f"GPIODCover async_close_cover: is_closed: {self.is_closed}. is_closing: {self.is_closing}, is_opening: {self.is_opening}")
if self.is_closed: if self.is_closed:
return return
self._hub.turn_on(self._relay_port) self._hub.turn_on(self._relay_line, self._relay_port)
self._attr_is_closing = True self._attr_is_closing = True
self.schedule_update_ha_state(False) self.async_write_ha_state()
sleep(self._relay_time) await asyncio.sleep(self._relay_time)
if not self.is_closing: if not self.is_closing:
# closing stopped # closing stopped
return return
self._hub.turn_off(self._relay_port) self._hub.turn_off(self._relay_line, self._relay_port)
self._attr_is_closing = False self._attr_is_closing = False
self.handle_event() self.async_write_ha_state()
def open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
_LOGGER.debug(f"GPIODCover async_open_cover: is_closed: {self.is_closed}. is_closing: {self.is_closing}, is_opening: {self.is_opening}")
if not self.is_closed: if not self.is_closed:
return return
self._hub.turn_on(self._relay_port) self._hub.turn_on(self._relay_line, self._relay_port)
self._attr_is_opening = True self._attr_is_opening = True
self.schedule_update_ha_state(False) self.async_write_ha_state()
sleep(self._relay_time) await asyncio.sleep(self._relay_time)
if not self.is_opening: if not self.is_opening:
# opening stopped # opening stopped
return return
self._hub.turn_off(self._relay_port) self._hub.turn_off(self._relay_line, self._relay_port)
self._attr_is_opening = False self._attr_is_opening = False
self.handle_event() self.async_write_ha_state()
def stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs):
_LOGGER.debug(f"GPIODCover async_stop_cover: is_closed: {self.is_closed}. is_closing: {self.is_closing}, is_opening: {self.is_opening}")
if not (self.is_closing or self.is_opening): if not (self.is_closing or self.is_opening):
return return
self._hub.turn_off(self._relay_port) self._hub.turn_off(self._relay_line, self._relay_port)
self._attr_is_opening = False self._attr_is_opening = False
self._attr_is_closing = False self._attr_is_closing = False
self.schedule_update_ha_state(False) self.async_write_ha_state()

View File

@ -7,7 +7,7 @@ _LOGGER = logging.getLogger(__name__)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START from homeassistant.const import EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError,ServiceValidationError
from typing import Dict from typing import Dict
from datetime import timedelta from datetime import timedelta
@ -39,10 +39,6 @@ class Hub:
self._id = path self._id = path
self._hass = hass self._hass = hass
self._online = False self._online = False
self._lines : gpiod.LineRequest = None
self._config : Dict[int, gpiod.LineSettings] = {}
self._edge_events = False
self._entities = {}
if path: if path:
# use config # use config
@ -62,13 +58,8 @@ class Hub:
break break
self.verify_online() self.verify_online()
_LOGGER.debug(f"using gpio_device: {self._path}") _LOGGER.debug(f"using gpio_device: {self._path}")
# startup and shutdown triggers of hass
self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, self.startup)
self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.cleanup)
def verify_online(self): def verify_online(self):
if not self._online: if not self._online:
_LOGGER.error("No gpio device detected, bailing out") _LOGGER.error("No gpio device detected, bailing out")
@ -82,6 +73,7 @@ class Hub:
_LOGGER.debug(f"verify_gpiochip: {path} is a gpiochip_device") _LOGGER.debug(f"verify_gpiochip: {path} is a gpiochip_device")
self._chip = gpiod.Chip(path) self._chip = gpiod.Chip(path)
info = self._chip.get_info() info = self._chip.get_info()
_LOGGER.debug(f"verify_gpiochip: {path} info is: {info}")
if not "pinctrl" in info.label: if not "pinctrl" in info.label:
_LOGGER.debug(f"verify_gpiochip: {path} no pinctrl {info.label}") _LOGGER.debug(f"verify_gpiochip: {path} no pinctrl {info.label}")
return False return False
@ -91,121 +83,67 @@ class Hub:
def verify_port_ready(self, port: int): def verify_port_ready(self, port: int):
info = self._chip.get_line_info(port) info = self._chip.get_line_info(port)
_LOGGER.debug(f"original port info: {info}") _LOGGER.debug(f"original port {port} info: {info}")
if info.used and info.consumer != DOMAIN: if info.used:
_LOGGER.error(f"Port {port} already in use by {info.consumer}") if info.consumer != DOMAIN:
raise HomeAssistantError(f"Port {port} already in use by {info.consumer}") raise HomeAssistantError(f"Port {port} already in use by {info.consumer}")
else:
raise HomeAssistantError(f"Port {port} already in use by another entity, check your config for duplicates port usage")
async def startup(self, _):
"""Stuff to do after starting."""
_LOGGER.debug(f"startup {DOMAIN} hub")
if not self._online:
_LOGGER.debug(f"integration is not online")
return
if not self._config:
_LOGGER.debug(f"gpiod config is empty")
return
# setup lines
try:
self.update_lines()
except Exception as e:
_LOGGER.error(f"Failed to update lines: {e}")
return
if not self._edge_events:
return
_LOGGER.debug("Start listener")
self._hass.loop.add_reader(self._lines.fd, self.handle_events)
def cleanup(self, _):
"""Stuff to do before stopping."""
_LOGGER.debug(f"cleanup {DOMAIN} hub")
if self._config:
self._config.clear()
if self._lines:
self._lines.release()
if self._chip:
self._chip.close()
self._online = False
@property @property
def hub_id(self) -> str: def hub_id(self) -> str:
"""ID for hub""" """ID for hub"""
return self._id return self._id
def update_lines(self) -> None: def add_switch(self, port, active_low, bias, drive_mode, init_state) -> gpiod.LineRequest:
if self._lines: _LOGGER.debug(f"add_switch - port: {port}, active_low: {active_low}, bias: {bias}, drive_mode: {drive_mode}, init_state: {init_state}")
self._lines.release()
_LOGGER.debug(f"updating lines: {self._config}")
self._lines = self._chip.request_lines(
consumer = DOMAIN,
config = self._config
)
_LOGGER.debug(f"update_lines new lines: {self._lines}")
def handle_events(self):
for event in self._lines.read_edge_events():
_LOGGER.debug(f"Event: {event}")
self._entities[event.line_offset].handle_event()
def add_switch(self, entity, port, active_low, bias, drive_mode, init_output_value = True) -> None:
_LOGGER.debug(f"in add_switch {port}")
self.verify_online() self.verify_online()
self.verify_port_ready(port) self.verify_port_ready(port)
self._entities[port] = entity line_request = self._chip.request_lines(
self._config[port] = gpiod.LineSettings( consumer=DOMAIN,
direction = Direction.OUTPUT, config={port: gpiod.LineSettings(
bias = BIAS[bias], direction = Direction.OUTPUT,
drive = DRIVE[drive_mode], bias = BIAS[bias],
active_low = active_low, drive = DRIVE[drive_mode],
output_value = Value.ACTIVE if init_output_value and entity.is_on else Value.INACTIVE active_low = active_low,
) output_value = Value.ACTIVE if init_state is not None and init_state else Value.INACTIVE)})
_LOGGER.debug(f"add_switch line_request: {line_request}")
return line_request
def turn_on(self, port) -> None: def turn_on(self, line, port) -> None:
_LOGGER.debug(f"in turn_on {port}") _LOGGER.debug(f"in turn_on {port}")
self.verify_online() self.verify_online()
self._lines.set_value(port, Value.ACTIVE) line.set_value(port, Value.ACTIVE)
def turn_off(self, port) -> None: def turn_off(self, line, port) -> None:
_LOGGER.debug(f"in turn_off {port}") _LOGGER.debug(f"in turn_off {port}")
self.verify_online() self.verify_online()
self._lines.set_value(port, Value.INACTIVE) line.set_value(port, Value.INACTIVE)
def add_sensor(self, entity, port, active_low, bias, debounce) -> None: def add_sensor(self, port, active_low, bias, debounce) -> gpiod.LineRequest:
_LOGGER.debug(f"in add_sensor {port}") _LOGGER.debug(f"add_sensor - port: {port}, active_low: {active_low}, bias: {bias}, debounce: {debounce}")
self.verify_online() self.verify_online()
self.verify_port_ready(port) self.verify_port_ready(port)
# read current status of the sensor line_request = self._chip.request_lines(
line = self._chip.request_lines({ port: {} }) consumer=DOMAIN,
value = True if line.get_value(port) == Value.ACTIVE else False config={port: gpiod.LineSettings(
entity.is_on = True if value ^ active_low else False direction = Direction.INPUT,
line.release() edge_detection = Edge.BOTH,
_LOGGER.debug(f"current value for port {port}: {entity.is_on}") bias = BIAS[bias],
active_low = active_low,
debounce_period = timedelta(milliseconds=debounce),
event_clock = Clock.REALTIME)})
_LOGGER.debug(f"add_sensor line_request: {line_request}")
current_is_on = True if line_request.get_value(port) == Value.ACTIVE else False
_LOGGER.debug(f"add_sensor current state: {current_is_on}")
return line_request, current_is_on
self._entities[port] = entity def add_cover(self, relay_port, relay_active_low, relay_bias, relay_drive,
self._config[port] = gpiod.LineSettings( state_port, state_bias, state_active_low):
direction = Direction.INPUT, _LOGGER.debug(f"add_cover - relay_port: {relay_port}, state_port: {state_port}")
edge_detection = Edge.BOTH, relay_line = self.add_switch(relay_port, relay_active_low, relay_bias, relay_drive, False)
bias = BIAS[bias], state_line, current_is_on = self.add_sensor(state_port, state_active_low, state_bias, 50)
active_low = active_low, return relay_line, state_line, current_is_on
debounce_period = timedelta(milliseconds=debounce),
event_clock = Clock.REALTIME,
output_value = Value.ACTIVE if entity.is_on else Value.INACTIVE,
)
self._edge_events = True
def get_line_value(self, port, **kwargs):
return self._lines.get_value(port) == Value.ACTIVE
def add_cover(self, entity, relay_port, relay_active_low, relay_bias, relay_drive,
state_port, state_bias, state_active_low) -> None:
_LOGGER.debug(f"in add_cover {relay_port} {state_port}")
self.add_switch(entity, relay_port, relay_active_low, relay_bias, relay_drive, init_output_value = False)
self.add_sensor(entity, state_port, state_active_low, state_bias, 50)

View File

@ -7,5 +7,5 @@
"iot_class": "local_push", "iot_class": "local_push",
"issue_tracker": "https://github.com/thecode/ha-rpi_gpio/issues", "issue_tracker": "https://github.com/thecode/ha-rpi_gpio/issues",
"requirements": [ "gpiod>=2.2.1" ], "requirements": [ "gpiod>=2.2.1" ],
"version": "2024.10.2" "version": "2025.2.1"
} }

View File

@ -56,18 +56,21 @@ async def async_setup_platform(
switches = [] switches = []
for switch in config.get(CONF_SWITCHES): for switch in config.get(CONF_SWITCHES):
switches.append( try:
GPIODSwitch( switches.append(
hub, GPIODSwitch(
switch[CONF_NAME], hub,
switch[CONF_PORT], switch[CONF_NAME],
switch.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{switch[CONF_PORT]}_{switch[CONF_NAME].lower().replace(' ', '_')}", switch[CONF_PORT],
switch.get(CONF_INVERT_LOGIC), switch.get(CONF_UNIQUE_ID) or f"{DOMAIN}_{switch[CONF_PORT]}_{switch[CONF_NAME].lower().replace(' ', '_')}",
switch.get(CONF_PULL_MODE), switch.get(CONF_INVERT_LOGIC),
switch.get(CONF_DRIVE), switch.get(CONF_PULL_MODE),
switch[CONF_PERSISTENT] switch.get(CONF_DRIVE),
switch[CONF_PERSISTENT]
)
) )
) except Exception as e:
_LOGGER.error(f"Failed to add switch {switch[CONF_NAME]} for port {switch[CONF_PORT]}: {e}")
async_add_entities(switches) async_add_entities(switches)
@ -85,7 +88,9 @@ class GPIODSwitch(SwitchEntity, RestoreEntity):
self._bias = bias self._bias = bias
self._drive_mode = drive self._drive_mode = drive
self._persistent = persistent self._persistent = persistent
self._line = None
self._hub.verify_port_ready(self._port)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Call when the switch is added to hass.""" """Call when the switch is added to hass."""
await super().async_added_to_hass() await super().async_added_to_hass()
@ -95,19 +100,21 @@ class GPIODSwitch(SwitchEntity, RestoreEntity):
else: else:
_LOGGER.debug(f"setting initial persistent state for: {self._port}. state: {state.state}") _LOGGER.debug(f"setting initial persistent state for: {self._port}. state: {state.state}")
self._attr_is_on = True if state.state == STATE_ON else False self._attr_is_on = True if state.state == STATE_ON else False
self._hub.add_switch(self, self._port, self._active_low, self._bias, self._drive_mode) self.async_write_ha_state()
self.async_write_ha_state() self._line = self._hub.add_switch(self._port, self._active_low, self._bias, self._drive_mode, self._attr_is_on)
async def async_will_remove_from_hass(self) -> None:
await super().async_will_remove_from_hass()
_LOGGER.debug(f"GPIODSwitch async_will_remove_from_hass")
if self._line:
self._line.release()
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
self._hub.turn_on(self._port) self._hub.turn_on(self._line, self._port)
self._attr_is_on = True self._attr_is_on = True
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
self._hub.turn_off(self._port) self._hub.turn_off(self._line, self._port)
self._attr_is_on = False self._attr_is_on = False
self.async_write_ha_state() self.async_write_ha_state()
def handle_event(self):
self._attr_is_on = self._hub.get_line_value(self._port)
self.schedule_update_ha_state(False)

View File

@ -1,7 +1,7 @@
black==24.10.0 black==25.1.0
flake8==7.1.1 flake8==7.3.0
isort==5.13.2 isort==6.0.1
mypy==1.13.0 mypy==1.17.0
pre-commit==4.0.1 pre-commit==4.2.0
pydocstyle==6.3.0 pydocstyle==6.3.0
pylint==3.3.1 pylint==3.3.7