forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upgrade thethingsnetwork to v3 (home-assistant#113375)
* thethingsnetwork upgrade to v3 * add en translations and requirements_all * fix most of the findings * hassfest * use ttn_client v0.0.3 * reduce content of initial release * remove features that trigger errors * remove unneeded * add initial testcases * Exception warning * add strict type checking * add strict type checking * full coverage * rename to conftest * review changes * avoid using private attributes - use protected instead * simplify config_flow * remove unused options * review changes * upgrade client * add types client library - no need to cast * use add_suggested_values_to_schema * add ruff fix * review changes * remove unneeded comment * use typevar for TTN value * use typevar for TTN value * review * ruff error not detected in local * test review * re-order fixture * fix test * reviews * fix case * split testcases * review feedback * Update homeassistant/components/thethingsnetwork/__init__.py Co-authored-by: Joost Lekkerkerker <[email protected]> * Update homeassistant/components/thethingsnetwork/__init__.py Co-authored-by: Joost Lekkerkerker <[email protected]> * Update tests/components/thethingsnetwork/test_config_flow.py Co-authored-by: Joost Lekkerkerker <[email protected]> * Remove deprecated var * Update tests/components/thethingsnetwork/test_config_flow.py Co-authored-by: Joost Lekkerkerker <[email protected]> * Remove unused import * fix ruff warning --------- Co-authored-by: Joost Lekkerkerker <[email protected]>
- Loading branch information
Showing
21 changed files
with
724 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
homeassistant/components/thethingsnetwork/config_flow.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
"""The Things Network's integration config flow.""" | ||
|
||
from collections.abc import Mapping | ||
import logging | ||
from typing import Any | ||
|
||
from ttn_client import TTNAuthError, TTNClient | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import CONF_API_KEY, CONF_HOST | ||
from homeassistant.helpers.selector import ( | ||
TextSelector, | ||
TextSelectorConfig, | ||
TextSelectorType, | ||
) | ||
|
||
from .const import CONF_APP_ID, DOMAIN, TTN_API_HOST | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class TTNFlowHandler(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow.""" | ||
|
||
VERSION = 1 | ||
|
||
_reauth_entry: ConfigEntry | None = None | ||
|
||
async def async_step_user( | ||
self, user_input: Mapping[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""User initiated config flow.""" | ||
|
||
errors = {} | ||
if user_input is not None: | ||
client = TTNClient( | ||
user_input[CONF_HOST], | ||
user_input[CONF_APP_ID], | ||
user_input[CONF_API_KEY], | ||
0, | ||
) | ||
try: | ||
await client.fetch_data() | ||
except TTNAuthError: | ||
_LOGGER.exception("Error authenticating with The Things Network") | ||
errors["base"] = "invalid_auth" | ||
except Exception: | ||
_LOGGER.exception("Unknown error occurred") | ||
errors["base"] = "unknown" | ||
|
||
if not errors: | ||
# Create entry | ||
if self._reauth_entry: | ||
return self.async_update_reload_and_abort( | ||
self._reauth_entry, | ||
data=user_input, | ||
reason="reauth_successful", | ||
) | ||
await self.async_set_unique_id(user_input[CONF_APP_ID]) | ||
self._abort_if_unique_id_configured() | ||
|
||
return self.async_create_entry( | ||
title=str(user_input[CONF_APP_ID]), | ||
data=user_input, | ||
) | ||
|
||
# Show form for user to provide settings | ||
if not user_input: | ||
if self._reauth_entry: | ||
user_input = self._reauth_entry.data | ||
else: | ||
user_input = {CONF_HOST: TTN_API_HOST} | ||
|
||
schema = self.add_suggested_values_to_schema( | ||
vol.Schema( | ||
{ | ||
vol.Required(CONF_HOST): str, | ||
vol.Required(CONF_APP_ID): str, | ||
vol.Required(CONF_API_KEY): TextSelector( | ||
TextSelectorConfig( | ||
type=TextSelectorType.PASSWORD, autocomplete="api_key" | ||
) | ||
), | ||
} | ||
), | ||
user_input, | ||
) | ||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors) | ||
|
||
async def async_step_reauth( | ||
self, user_input: Mapping[str, Any] | ||
) -> ConfigFlowResult: | ||
"""Handle a flow initialized by a reauth event.""" | ||
|
||
self._reauth_entry = self.hass.config_entries.async_get_entry( | ||
self.context["entry_id"] | ||
) | ||
|
||
return await self.async_step_reauth_confirm() | ||
|
||
async def async_step_reauth_confirm( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Dialog that informs the user that reauth is required.""" | ||
if user_input is None: | ||
return self.async_show_form(step_id="reauth_confirm") | ||
return await self.async_step_user() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"""The Things Network's integration constants.""" | ||
|
||
from homeassistant.const import Platform | ||
|
||
DOMAIN = "thethingsnetwork" | ||
TTN_API_HOST = "eu1.cloud.thethings.network" | ||
|
||
PLATFORMS = [Platform.SENSOR] | ||
|
||
CONF_APP_ID = "app_id" | ||
|
||
POLLING_PERIOD_S = 60 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
"""The Things Network's integration DataUpdateCoordinator.""" | ||
|
||
from datetime import timedelta | ||
import logging | ||
|
||
from ttn_client import TTNAuthError, TTNClient | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_API_KEY, CONF_HOST | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryAuthFailed | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
from .const import CONF_APP_ID, POLLING_PERIOD_S | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class TTNCoordinator(DataUpdateCoordinator[TTNClient.DATA_TYPE]): | ||
"""TTN coordinator.""" | ||
|
||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: | ||
"""Initialize my coordinator.""" | ||
super().__init__( | ||
hass, | ||
_LOGGER, | ||
# Name of the data. For logging purposes. | ||
name=f"TheThingsNetwork_{entry.data[CONF_APP_ID]}", | ||
# Polling interval. Will only be polled if there are subscribers. | ||
update_interval=timedelta( | ||
seconds=POLLING_PERIOD_S, | ||
), | ||
) | ||
|
||
self._client = TTNClient( | ||
entry.data[CONF_HOST], | ||
entry.data[CONF_APP_ID], | ||
entry.data[CONF_API_KEY], | ||
push_callback=self._push_callback, | ||
) | ||
|
||
async def _async_update_data(self) -> TTNClient.DATA_TYPE: | ||
"""Fetch data from API endpoint. | ||
This is the place to pre-process the data to lookup tables | ||
so entities can quickly look up their data. | ||
""" | ||
try: | ||
# Note: asyncio.TimeoutError and aiohttp.ClientError are already | ||
# handled by the data update coordinator. | ||
measurements = await self._client.fetch_data() | ||
except TTNAuthError as err: | ||
# Raising ConfigEntryAuthFailed will cancel future updates | ||
# and start a config flow with SOURCE_REAUTH (async_step_reauth) | ||
_LOGGER.error("TTNAuthError") | ||
raise ConfigEntryAuthFailed from err | ||
else: | ||
# Return measurements | ||
_LOGGER.debug("fetched data: %s", measurements) | ||
return measurements | ||
|
||
async def _push_callback(self, data: TTNClient.DATA_TYPE) -> None: | ||
_LOGGER.debug("pushed data: %s", data) | ||
|
||
# Push data to entities | ||
self.async_set_updated_data(data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"""Support for The Things Network entities.""" | ||
|
||
import logging | ||
|
||
from ttn_client import TTNBaseValue | ||
|
||
from homeassistant.core import callback | ||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import DOMAIN | ||
from .coordinator import TTNCoordinator | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class TTNEntity(CoordinatorEntity[TTNCoordinator]): | ||
"""Representation of a The Things Network Data Storage sensor.""" | ||
|
||
_attr_has_entity_name = True | ||
_ttn_value: TTNBaseValue | ||
|
||
def __init__( | ||
self, | ||
coordinator: TTNCoordinator, | ||
app_id: str, | ||
ttn_value: TTNBaseValue, | ||
) -> None: | ||
"""Initialize a The Things Network Data Storage sensor.""" | ||
|
||
# Pass coordinator to CoordinatorEntity | ||
super().__init__(coordinator) | ||
|
||
self._ttn_value = ttn_value | ||
|
||
self._attr_unique_id = f"{self.device_id}_{self.field_id}" | ||
self._attr_name = self.field_id | ||
|
||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, f"{app_id}_{self.device_id}")}, | ||
name=self.device_id, | ||
) | ||
|
||
@callback | ||
def _handle_coordinator_update(self) -> None: | ||
"""Handle updated data from the coordinator.""" | ||
|
||
my_entity_update = self.coordinator.data.get(self.device_id, {}).get( | ||
self.field_id | ||
) | ||
if ( | ||
my_entity_update | ||
and my_entity_update.received_at > self._ttn_value.received_at | ||
): | ||
_LOGGER.debug( | ||
"Received update for %s: %s", self.unique_id, my_entity_update | ||
) | ||
# Check that the type of an entity has not changed since the creation | ||
assert isinstance(my_entity_update, type(self._ttn_value)) | ||
self._ttn_value = my_entity_update | ||
self.async_write_ha_state() | ||
|
||
@property | ||
def device_id(self) -> str: | ||
"""Return device_id.""" | ||
return str(self._ttn_value.device_id) | ||
|
||
@property | ||
def field_id(self) -> str: | ||
"""Return field_id.""" | ||
return str(self._ttn_value.field_id) |
Oops, something went wrong.