8000 Add HomeLink integration by ryanjones-gentex · Pull Request #136460 · home-assistant/core · GitHub
[go: up one dir, main page]

Skip to content

Add HomeLink integration #136460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 71 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
d642abe
Add HomeLink integration
ryanjones-gentex Jan 24, 2025
2314ab7
Use common keys for email, password
ryanjones-gentex Feb 4, 2025
9746a73
Remove update_listener
ryanjones-gentex Feb 18, 2025
ad74414
Fix config_flow formatting
ryanjones-gentex Feb 19, 2025
90ead29
Remove domain handler
ryanjones-gentex Feb 19, 2025
6a23c67
Remove info logging statement
ryanjones-gentex Feb 19, 2025
805057b
Create polling interval constant
ryanjones-gentex Feb 19, 2025
ce12319
Add formatting changes to coordinator
ryanjones-gentex Feb 19, 2025
d9f008f
Add type to config
ryanjones-gentex Feb 19, 2025
7015fbb
Add config to super
ryanjones-gentex Feb 19, 2025
6510ae6
Convert runtime data to dataclass
ryanjones-gentex Feb 19, 2025
0ef03d9
Remove unused logger
ryanjones-gentex Feb 20, 2025
c8d1680
Add return type
ryanjones-gentex Feb 20, 2025
edb774a
Add return type to update method
ryanjones-gentex Feb 20, 2025
de4077b
Move sync above runtime_data assignment
ryanjones-gentex Feb 20, 2025
df7dd42
Fix name casing; add type
ryanjones-gentex Feb 20, 2025
6923d8b
Add has_entity_name for binary sensor
ryanjones-gentex Feb 20, 2025
c847d2a
Implement feedback from ha
niaexa Feb 24, 2025
2bcfa39
Change over to events
niaexa Feb 26, 2025
bc538e9
Use string ref and add comment explaining unused reference
niaexa Feb 26, 2025
32b4e0c
Fix tests
niaexa Feb 27, 2025
a3d3355
PR feedback
niaexa Feb 27, 2025
2c9563b
Change strings to constants
niaexa Feb 28, 2025
5eebf8b
Add button device class
ryanjones-gentex Mar 3, 2025
947fe7e
Add HomeLink integration
ryanjones-gentex Jan 24, 2025
8480288
Use common keys for email, password
ryanjones-gentex Feb 4, 2025
c6b72e4
Remove update_listener
ryanjones-gentex Feb 18, 2025
88baf96
Fix config_flow formatting
ryanjones-gentex Feb 19, 2025
49ea83e
Remove domain handler
ryanjones-gentex Feb 19, 2025
ba7a6ef
Remove info logging statement
ryanjones-gentex Feb 19, 2025
0235c38
Create polling interval constant
ryanjones-gentex Feb 19, 2025
34f51d9
Add formatting changes to coordinator
ryanjones-gentex Feb 19, 2025
9cdf39c
Add type to config
ryanjones-gentex Feb 19, 2025
2585b1c
Add config to super
ryanjones-gentex Feb 19, 2025
d0fbc36
Convert runtime data to dataclass
ryanjones-gentex Feb 19, 2025
0bfc0cb
Remove unused logger
ryanjones-gentex Feb 20, 2025
89543ba
Add return type
ryanjones-gentex Feb 20, 2025
37af61e
Add return type to update method
ryanjones-gentex Feb 20, 2025
9fbd1c9
Move sync above runtime_data assignment
ryanjones-gentex Feb 20, 2025
ba6220c
Fix name casing; add type
ryanjones-gentex Feb 20, 2025
acdc5c7
Add has_entity_name for binary sensor
ryanjones-gentex Feb 20, 2025
091b737
Implement feedback from ha
niaexa Feb 24, 2025
cc9f5e5
Change over to events
niaexa Feb 26, 2025
44f9b60
Use string ref and add comment explaining unused reference
niaexa Feb 26, 2025
9429ed2
Fix tests
niaexa Feb 27, 2025
0020d57
PR feedback
niaexa Feb 27, 2025
fc5912c
Change strings to constants
niaexa Feb 28, 2025
b49fa33
Attempt to consoldiate auth methods
niaexa Mar 5, 2025
cab9ede
attempt a combined auth flow
niaexa Mar 7, 2025
bfa467b
Merge auth implementations
niaexa Mar 10, 2025
15b2019
Merge branch 'feature/homelink-integration' of https://github.com/Gen…
niaexa Mar 12, 2025
13bb9dd
Merge pull request #2 from niaexa/feature/event
ryanjones-gentex Mar 20, 2025
609ba83
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex Mar 25, 2025
f30edab
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex Apr 4, 2025
a61375d
Add mqtt support
niaexa Apr 4, 2025
f04d5fe
Add support for removing the entry
niaexa Apr 4, 2025
f7999ca
Fix tests
niaexa Apr 8, 2025
db5a5ed
Remove logger
niaexa Apr 8, 2025
2b41a75
Merge pull request #3 from niaexa/feature/pubsub
ryanjones-gentex Apr 8, 2025
3d39521
Checkpoint
niaexa Apr 9, 2025
5a7d846
Make sure updates are handled in HA's loop
niaexa Apr 11, 2025
cd6b920
Fix tests to match new model
niaexa Apr 14, 2025
e3756ee
Misc suggested fixes
niaexa Apr 15, 2025
d7c7388
Types
niaexa Apr 16, 2025
0ae24bf
Merge pull request #4 from niaexa/feature/ha-changes
ryanjones-gentex Apr 21, 2025
539d403
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex Apr 28, 2025
83bf2ce
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex May 1, 2025
b6f0772
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex May 15, 2025
c5851e2
Fix mypy error
niaexa May 16, 2025
4772354
Merge pull request #6 from niaexa/fix/mypy2
ryanjones-gentex May 16, 2025
4a38b3e
Merge branch 'dev' into feature/homelink-integration
ryanjones-gentex May 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add mqtt support
  • Loading branch information
niaexa committed Apr 4, 2025
commit a61375d205d16a65f688dfd6783fa48fa379717c
4 changes: 2 additions & 2 deletions homeassistant/components/gentex_homelink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging

from homelink.provider import Provider
from homelink.mqtt_provider import MQTTProvider

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
Expand Down Expand Up @@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeLinkConfigEntry) ->
aiohttp_client.async_get_clientsession(hass), session
)

provider = Provider(authenticated_session)
provider = MQTTProvider(authenticated_session)
coordinator = HomeLinkCoordinator(hass, provider, entry)

await coordinator.async_config_entry_first_refresh()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/gentex_homelink/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ async def async_step_user(
"""Ask for username and password."""
errors: dict[str, str] = {}
if user_input is not None:
_LOGGER.info(user_input)
self._async_abort_entries_match({CONF_EMAIL: user_input[CONF_EMAIL]})

srp_auth = SRPAuth()
Expand All @@ -53,6 +52,7 @@ async def async_step_user(
user_input[CONF_PASSWORD],
)
except botocore.exceptions.ClientError:
_LOGGER.exception("Error authenticating homelink account")
errors["base"] = "Error authenticating HomeLink account"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is an auth error we should catch something more specific otherwise it's probably unknown

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added specific error handling with default handling using _LOGGER.exception

except Exception:
_LOGGER.exception("An unexpected error occurred")
Expand Down
75 changes: 32 additions & 43 deletions homeassistant/components/gentex_homelink/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
"""Makes requests to the state server and stores the resulting data so that the buttons can access it."""

import asyncio
from dataclasses import dataclass
from datetime import timedelta
import functools
import logging
from typing import Any

from homelink.provider import Provider
from homelink.mqtt_provider import MQTTProvider

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util.ssl import get_default_context

from .const import POLLING_INTERVAL
from .event import HomeLinkEventEntity

_LOGGER = logging.getLogger(__name__)
Expand All @@ -23,7 +22,7 @@
class HomeLinkData:
"""Class for HomeLink integration runtime data."""

provider: Provider
provider: MQTTProvider
coordinator: DataUpdateCoordinator
last_update_id: str | None

Expand All @@ -34,7 +33,7 @@ class HomeLinkCoordinator(DataUpdateCoordinator[dict[str, Any]]):
def __init__(
self,
hass: HomeAssistant,
provider: Provider,
provider: MQTTProvider,
config_entry: ConfigEntry[HomeLinkData],
) -> None:
"""Initialize my coordinator."""
Expand All @@ -43,8 +42,6 @@ def __init__(
_LOGGER,
# Name of the data. For logging purposes.
name="HomeLink Coordinator",
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=POLLING_INTERVAL),
config_entry=config_entry,
)
self.provider = provider
Expand All @@ -54,8 +51,14 @@ def __init__(
self.buttons: list[HomeLinkEventEntity] = []

async def _async_setup(self):
await self.provider.enable()
await self.provider.enable(get_default_context())

await self.discover_devices()
callback = functools.partial(on_message, self)
self.provider.listen(callback)

async def discover_devices(self):
"""Discover devices and build the Entities."""
device_data = await self.provider.discover()

for device in device_data:
Expand All @@ -72,37 +75,23 @@ async def _async_setup(self):

async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from API endpoint."""
if self.config_entry is None:
_LOGGER.error("Config entry is empty, unable to update data")
return {}

# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with asyncio.timeout(10):
# Grab active context variables to limit data required to be fetched from API
# Note: using context is not required if there is no need or ability to limit
# data retrieved from API.
try:
should_sync, devices = await self.provider.get_state()
except Exception:
_LOGGER.exception(
"An unexpected error occurred retrieving HomeLink device state"
)
return {}
else:
if (
should_sync
and should_sync["requestId"] != self.last_sync_id
and self.config_entry.data["last_update_id"]
!= should_sync["requestId"]
and should_sync["timestamp"] != self.last_sync_timestamp
):
config_data = self.config_entry.data.copy()
config_data["last_update_id"] = should_sync["requestId"]

self.hass.config_entries.async_update_entry(
self.config_entry, data=config_data
)
self.last_sync_id = should_sync["requestId"]
self.last_sync_timestamp = should_sync["timestamp"]
return devices
return {}

async def async_update_devices(self, message):
"""Update the devices from the server."""
config_data = self.config_entry.data.copy()
config_data["last_update_id"] = message["requestId"]
await self.discover_devices()

self.hass.config_entries.async_update_entry(self.config_entry, data=config_data)
self.last_sync_id = message["requestId"]
self.last_sync_timestamp = message["timestamp"]


def on_message(coordinator: HomeLinkCoordinator, _topic, message):
"MQTT Callback function."

if message["type"] == "state":
coordinator.hass.add_job(coordinator.async_set_updated_data, message["data"])
if message["type"] == "requestSync":
coordinator.hass.add_job(coordinator.async_set_updated_data, message["data"])
42 changes: 31 additions & 11 deletions homeassistant/components/gentex_homelink/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

from __future__ import annotations

import time
from typing import TYPE_CHECKING
import asyncio
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
# Import keeps mypy happy but is a circular reference otherwise
from .coordinator import HomeLinkCoordinator # noqa: F401

import logging

from homeassistant.components.event import EventDeviceClass, EventEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
Expand All @@ -20,6 +22,8 @@

from .const import DOMAIN, EVENT_OFF, EVENT_PRESSED, EVENT_TIMEOUT

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -60,25 +64,41 @@ def __init__(self, id, param_name, device_id, device_name, coordinator) -> None:
self.name = name
self.unique_id = id
self.last_request_id = None
self.button_off_task: asyncio.Task[Any] | None = None

@callback
def _handle_coordinator_update(self) -> None:
"""Update this button."""
if not self.coordinator.data or self.id not in self.coordinator.data:
if self.state_attributes["event_type"] == EVENT_PRESSED:
self._trigger_event(EVENT_OFF)
self.async_write_ha_state()

if self.id not in self.coordinator.data:
# Not for us
return

# Handles debounce case - if we're pressed again, and we have an active stop timer, cancel the previous timer and restart it
if (
self.state_attributes["event_type"] == EVENT_PRESSED
and self.button_off_task
):
self.button_off_task.cancel()
self.button_off_task = asyncio.create_task(self.button_off())
return

# If this is the first press, set to off before setting pressed
if self.last_request_id is None:
self._trigger_event(EVENT_OFF)

latest_update = self.coordinator.data[self.id]
# Set button to pressed and then schedule the turnoff
if latest_update["requestId"] != self.last_request_id:
self._trigger_event(EVENT_PRESSED)
self.last_request_id = latest_update["requestId"]
elif (
self.state_attributes["event_type"] == EVENT_PRESSED
and time.time() - latest_update["timestamp"] < EVENT_TIMEOUT
):
self._trigger_event(EVENT_OFF)
self.button_off_task = asyncio.create_task(self.button_off())

self.async_write_ha_state()

async def button_off(self):
"""Wait for the specific time then turn the pressed button off."""
await asyncio.sleep(EVENT_TIMEOUT)
self._trigger_event(EVENT_OFF)
self.button_off_task = None
self.async_write_ha_state()
2 changes: 1 addition & 1 deletion homeassistant/components/gentex_homelink/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/gentex_homelink",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["homelink-home-assistant==0.0.6"]
"requirements": ["homelink-home-assistant==0.0.7"]
}
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0