Skip to content

Commit

Permalink
Add config flow to OpenSky (home-assistant#96912)
Browse files Browse the repository at this point in the history
Co-authored-by: Sander <[email protected]>
  • Loading branch information
joostlek and golles authored Jul 25, 2023
1 parent b4200cb commit 585d357
Show file tree
Hide file tree
Showing 15 changed files with 443 additions and 19 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/openhome/ @bazwilliams
/tests/components/openhome/ @bazwilliams
/homeassistant/components/opensky/ @joostlek
/tests/components/opensky/ @joostlek
/homeassistant/components/opentherm_gw/ @mvn23
/tests/components/opentherm_gw/ @mvn23
/homeassistant/components/openuv/ @bachya
Expand Down
26 changes: 26 additions & 0 deletions homeassistant/components/opensky/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
"""The opensky component."""
from __future__ import annotations

from python_opensky import OpenSky

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CLIENT, DOMAIN, PLATFORMS


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up opensky from a config entry."""

client = OpenSky(session=async_get_clientsession(hass))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {CLIENT: client}

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload opensky config entry."""

return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
77 changes: 77 additions & 0 deletions homeassistant/components/opensky/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Config flow for OpenSky integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow
from homeassistant.const import (
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
CONF_RADIUS,
)
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType

from .const import DEFAULT_NAME, DOMAIN
from .sensor import CONF_ALTITUDE, DEFAULT_ALTITUDE


class OpenSkyConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow handler for OpenSky."""

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Initialize user input."""
if user_input is not None:
return self.async_create_entry(
title=DEFAULT_NAME,
data={
CONF_LATITUDE: user_input[CONF_LATITUDE],
CONF_LONGITUDE: user_input[CONF_LONGITUDE],
},
options={
CONF_RADIUS: user_input[CONF_RADIUS],
CONF_ALTITUDE: user_input[CONF_ALTITUDE],
},
)
return self.async_show_form(
step_id="user",
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_RADIUS): vol.Coerce(float),
vol.Required(CONF_LATITUDE): cv.latitude,
vol.Required(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_ALTITUDE): vol.Coerce(float),
}
),
{
CONF_LATITUDE: self.hass.config.latitude,
CONF_LONGITUDE: self.hass.config.longitude,
CONF_ALTITUDE: DEFAULT_ALTITUDE,
},
),
)

async def async_step_import(self, import_config: ConfigType) -> FlowResult:
"""Import config from yaml."""
entry_data = {
CONF_LATITUDE: import_config.get(CONF_LATITUDE, self.hass.config.latitude),
CONF_LONGITUDE: import_config.get(
CONF_LONGITUDE, self.hass.config.longitude
),
}
self._async_abort_entries_match(entry_data)
return self.async_create_entry(
title=import_config.get(CONF_NAME, DEFAULT_NAME),
data=entry_data,
options={
CONF_RADIUS: import_config[CONF_RADIUS] * 1000,
CONF_ALTITUDE: import_config.get(CONF_ALTITUDE, DEFAULT_ALTITUDE),
},
)
4 changes: 4 additions & 0 deletions homeassistant/components/opensky/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""OpenSky constants."""
from homeassistant.const import Platform

PLATFORMS = [Platform.SENSOR]
DEFAULT_NAME = "OpenSky"
DOMAIN = "opensky"
CLIENT = "client"

CONF_ALTITUDE = "altitude"
ATTR_ICAO24 = "icao24"
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/opensky/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"domain": "opensky",
"name": "OpenSky Network",
"codeowners": ["@joostlek"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/opensky",
"iot_class": "cloud_polling",
"requirements": ["python-opensky==0.0.10"]
Expand Down
69 changes: 51 additions & 18 deletions homeassistant/components/opensky/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
Expand All @@ -15,17 +16,18 @@
CONF_NAME,
CONF_RADIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import (
ATTR_ALTITUDE,
ATTR_CALLSIGN,
ATTR_ICAO24,
ATTR_SENSOR,
CLIENT,
CONF_ALTITUDE,
DEFAULT_ALTITUDE,
DOMAIN,
Expand All @@ -36,6 +38,7 @@
# OpenSky free user has 400 credits, with 4 credits per API call. 100/24 = ~4 requests per hour
SCAN_INTERVAL = timedelta(minutes=15)


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_RADIUS): vol.Coerce(float),
Expand All @@ -47,27 +50,57 @@
)


def setup_platform(
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Open Sky platform."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
radius = config.get(CONF_RADIUS, 0)
bounding_box = OpenSky.get_bounding_box(latitude, longitude, radius * 1000)
session = async_get_clientsession(hass)
opensky = OpenSky(session=session)
add_entities(
"""Set up the OpenSky sensor platform from yaml."""

async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.1.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "OpenSky",
},
)

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize the entries."""

opensky = hass.data[DOMAIN][entry.entry_id][CLIENT]
bounding_box = OpenSky.get_bounding_box(
entry.data[CONF_LATITUDE],
entry.data[CONF_LONGITUDE],
entry.options[CONF_RADIUS],
)
async_add_entities(
[
OpenSkySensor(
hass,
config.get(CONF_NAME, DOMAIN),
entry.title,
opensky,
bounding_box,
config[CONF_ALTITUDE],
entry.options.get(CONF_ALTITUDE, DEFAULT_ALTITUDE),
entry.entry_id,
)
],
True,
Expand All @@ -83,20 +116,20 @@ class OpenSkySensor(SensorEntity):

def __init__(
self,
hass: HomeAssistant,
name: str,
opensky: OpenSky,
bounding_box: BoundingBox,
altitude: float,
entry_id: str,
) -> None:
"""Initialize the sensor."""
self._altitude = altitude
self._state = 0
self._hass = hass
self._name = name
self._previously_tracked: set[str] = set()
self._opensky = opensky
self._bounding_box = bounding_box
self._attr_unique_id = f"{entry_id}_opensky"

@property
def name(self) -> str:
Expand Down Expand Up @@ -133,7 +166,7 @@ def _handle_boundary(
ATTR_LATITUDE: latitude,
ATTR_ICAO24: icao24,
}
self._hass.bus.fire(event, data)
self.hass.bus.fire(event, data)

async def async_update(self) -> None:
"""Update device state."""
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/opensky/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config": {
"step": {
"user": {
"description": "Fill in the location to track.",
"data": {
"name": "[%key:common::config_flow::data::api_key%]",
"radius": "Radius",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"altitude": "Altitude"
}
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@
"openexchangerates",
"opengarage",
"openhome",
"opensky",
"opentherm_gw",
"openuv",
"openweathermap",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -3976,7 +3976,7 @@
"opensky": {
"name": "OpenSky Network",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"opentherm_gw": {
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,9 @@ python-miio==0.5.12
# homeassistant.components.mystrom
python-mystrom==2.2.0

# homeassistant.components.opensky
python-opensky==0.0.10

# homeassistant.components.otbr
# homeassistant.components.thread
python-otbr-api==2.3.0
Expand Down
9 changes: 9 additions & 0 deletions tests/components/opensky/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Opensky tests."""
from unittest.mock import patch


def patch_setup_entry() -> bool:
"""Patch interface."""
return patch(
"homeassistant.components.opensky.async_setup_entry", return_value=True
)
50 changes: 50 additions & 0 deletions tests/components/opensky/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Configure tests for the OpenSky integration."""
from collections.abc import Awaitable, Callable
from unittest.mock import patch

import pytest
from python_opensky import StatesResponse

from homeassistant.components.opensky.const import CONF_ALTITUDE, DOMAIN
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from tests.common import MockConfigEntry

ComponentSetup = Callable[[MockConfigEntry], Awaitable[None]]


@pytest.fixture(name="config_entry")
def mock_config_entry() -> MockConfigEntry:
"""Create OpenSky entry in Home Assistant."""
return MockConfigEntry(
domain=DOMAIN,
title="OpenSky",
data={
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
},
options={
CONF_RADIUS: 10.0,
CONF_ALTITUDE: 0.0,
},
)


@pytest.fixture(name="setup_integration")
async def mock_setup_integration(
hass: HomeAssistant,
) -> Callable[[MockConfigEntry], Awaitable[None]]:
"""Fixture for setting up the component."""

async def func(mock_config_entry: MockConfigEntry) -> None:
mock_config_entry.add_to_hass(hass)
with patch(
"python_opensky.OpenSky.get_states",
return_value=StatesResponse(states=[], time=0),
):
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()

return func
Loading

0 comments on commit 585d357

Please sign in to comment.