1
mirror of https://github.com/home-assistant/core synced 2024-09-06 10:29:55 +02:00

Add Starlink device tracker (#91445)

* Fetch location data and redact in diagnostics

* Implement device tracker

* Fix failing tests

* Update starlink-grpc-core

* Update coveragerc

* Hardcode GPS source type

* Use translations

* Move DEVICE_TRACKERS a little higher in the file

* Separate status and location check try/catches

* Revert "Separate status and location check try/catches"

This reverts commit 7628ec62f6.
This commit is contained in:
Jack Boswell 2023-08-19 21:36:23 +12:00 committed by GitHub
parent c526d23686
commit 8a6bde1191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 109 additions and 8 deletions

View File

@ -1187,6 +1187,7 @@ omit =
homeassistant/components/starlink/binary_sensor.py
homeassistant/components/starlink/button.py
homeassistant/components/starlink/coordinator.py
homeassistant/components/starlink/device_tracker.py
homeassistant/components/starlink/sensor.py
homeassistant/components/starlink/switch.py
homeassistant/components/starline/__init__.py

View File

@ -11,6 +11,7 @@ from .coordinator import StarlinkUpdateCoordinator
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.DEVICE_TRACKER,
Platform.SENSOR,
Platform.SWITCH,
]

View File

@ -10,8 +10,10 @@ from starlink_grpc import (
AlertDict,
ChannelContext,
GrpcError,
LocationDict,
ObstructionDict,
StatusDict,
location_data,
reboot,
set_stow_state,
status_data,
@ -28,6 +30,7 @@ _LOGGER = logging.getLogger(__name__)
class StarlinkData:
"""Contains data pulled from the Starlink system."""
location: LocationDict
status: StatusDict
obstruction: ObstructionDict
alert: AlertDict
@ -53,7 +56,10 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]):
status = await self.hass.async_add_executor_job(
status_data, self.channel_context
)
return StarlinkData(*status)
location = await self.hass.async_add_executor_job(
location_data, self.channel_context
)
return StarlinkData(location, *status)
except GrpcError as exc:
raise UpdateFailed from exc

View File

@ -0,0 +1,73 @@
"""Contains device trackers exposed by the Starlink integration."""
from collections.abc import Callable
from dataclasses import dataclass
from homeassistant.components.device_tracker import SourceType, TrackerEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import StarlinkData
from .entity import StarlinkEntity
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up all binary sensors for this entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
StarlinkDeviceTrackerEntity(coordinator, description)
for description in DEVICE_TRACKERS
)
@dataclass
class StarlinkDeviceTrackerEntityDescriptionMixin:
"""Describes a Starlink device tracker."""
latitude_fn: Callable[[StarlinkData], float]
longitude_fn: Callable[[StarlinkData], float]
@dataclass
class StarlinkDeviceTrackerEntityDescription(
EntityDescription, StarlinkDeviceTrackerEntityDescriptionMixin
):
"""Describes a Starlink button entity."""
DEVICE_TRACKERS = [
StarlinkDeviceTrackerEntityDescription(
key="device_location",
translation_key="device_location",
entity_registry_enabled_default=False,
latitude_fn=lambda data: data.location["latitude"],
longitude_fn=lambda data: data.location["longitude"],
),
]
class StarlinkDeviceTrackerEntity(StarlinkEntity, TrackerEntity):
"""A TrackerEntity for Starlink devices. Handles creating unique IDs."""
entity_description: StarlinkDeviceTrackerEntityDescription
@property
def source_type(self) -> SourceType | str:
"""Return the source type, eg gps or router, of the device."""
return SourceType.GPS
@property
def latitude(self) -> float | None:
"""Return latitude value of the device."""
return self.entity_description.latitude_fn(self.coordinator.data)
@property
def longitude(self) -> float | None:
"""Return longitude value of the device."""
return self.entity_description.longitude_fn(self.coordinator.data)

View File

@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import StarlinkUpdateCoordinator
TO_REDACT = {"id"}
TO_REDACT = {"id", "latitude", "longitude", "altitude"}
async def async_get_config_entry_diagnostics(

View File

@ -44,6 +44,11 @@
"name": "Unexpected location"
}
},
"device_tracker": {
"device_location": {
"name": "Device location"
}
},
"sensor": {
"ping": {
"name": "Ping"

View File

@ -0,0 +1,5 @@
{
"latitude": 37.422,
"longitude": -122.084,
"altitude": 100
}

View File

@ -8,11 +8,16 @@ SETUP_ENTRY_PATCHER = patch(
"homeassistant.components.starlink.async_setup_entry", return_value=True
)
COORDINATOR_SUCCESS_PATCHER = patch(
STATUS_DATA_SUCCESS_PATCHER = patch(
"homeassistant.components.starlink.coordinator.status_data",
return_value=json.loads(load_fixture("status_data_success.json", "starlink")),
)
LOCATION_DATA_SUCCESS_PATCHER = patch(
"homeassistant.components.starlink.coordinator.location_data",
return_value=json.loads(load_fixture("location_data_success.json", "starlink")),
)
DEVICE_FOUND_PATCHER = patch(
"homeassistant.components.starlink.config_flow.get_id", return_value="some-valid-id"
)

View File

@ -16,6 +16,11 @@
'alert_thermal_throttle': False,
'alert_unexpected_location': False,
}),
'location': dict({
'altitude': '**REDACTED**',
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
}),
'obstruction': dict({
'raw_wedges_fraction_obstructed[]': list([
None,

View File

@ -5,7 +5,7 @@ from homeassistant.components.starlink.const import DOMAIN
from homeassistant.const import CONF_IP_ADDRESS
from homeassistant.core import HomeAssistant
from .patchers import COORDINATOR_SUCCESS_PATCHER
from .patchers import LOCATION_DATA_SUCCESS_PATCHER, STATUS_DATA_SUCCESS_PATCHER
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
@ -23,7 +23,7 @@ async def test_diagnostics(
data={CONF_IP_ADDRESS: "1.2.3.4:0000"},
)
with COORDINATOR_SUCCESS_PATCHER:
with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER:
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)

View File

@ -4,7 +4,7 @@ from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_IP_ADDRESS
from homeassistant.core import HomeAssistant
from .patchers import COORDINATOR_SUCCESS_PATCHER
from .patchers import LOCATION_DATA_SUCCESS_PATCHER, STATUS_DATA_SUCCESS_PATCHER
from tests.common import MockConfigEntry
@ -16,7 +16,7 @@ async def test_successful_entry(hass: HomeAssistant) -> None:
data={CONF_IP_ADDRESS: "1.2.3.4:0000"},
)
with COORDINATOR_SUCCESS_PATCHER:
with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER:
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
@ -33,7 +33,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
data={CONF_IP_ADDRESS: "1.2.3.4:0000"},
)
with COORDINATOR_SUCCESS_PATCHER:
with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER:
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)