mirror of
https://github.com/home-assistant/core.git
synced 2026-10-07 06:50:41 -04:00
Add Starlink positioning exclusive switch (#184021)
This commit is contained in:
@@ -20,6 +20,7 @@ from starlink_grpc import (
|
||||
history_stats,
|
||||
location_data,
|
||||
reboot,
|
||||
set_gps_config,
|
||||
set_sleep_config,
|
||||
set_stow_state,
|
||||
status_data,
|
||||
@@ -110,6 +111,16 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]):
|
||||
except GrpcError as exc:
|
||||
raise HomeAssistantError from exc
|
||||
|
||||
async def async_set_gps_enabled(self, enable: bool) -> None:
|
||||
"""Set whether Starlink system uses GPS for position data."""
|
||||
async with asyncio.timeout(4):
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
set_gps_config, enable, self.channel_context
|
||||
)
|
||||
except GrpcError as exc:
|
||||
raise HomeAssistantError from exc
|
||||
|
||||
async def async_set_sleep_schedule_enabled(self, sleep_schedule: bool) -> None:
|
||||
"""Set whether Starlink system uses the configured sleep schedule."""
|
||||
async with asyncio.timeout(4):
|
||||
|
||||
@@ -25,6 +25,14 @@
|
||||
"upload": {
|
||||
"default": "mdi:upload"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"starlink_positioning_exclusive": {
|
||||
"default": "mdi:crosshairs-gps",
|
||||
"state": {
|
||||
"on": "mdi:crosshairs-off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
"sleep_schedule": {
|
||||
"name": "Sleep schedule"
|
||||
},
|
||||
"starlink_positioning_exclusive": {
|
||||
"name": "Use Starlink positioning exclusively"
|
||||
},
|
||||
"stowed": {
|
||||
"name": "Stowed"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ from homeassistant.components.switch import (
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -80,4 +81,16 @@ SWITCHES = [
|
||||
False
|
||||
),
|
||||
),
|
||||
StarlinkSwitchEntityDescription(
|
||||
key="starlink_positioning_exclusive",
|
||||
translation_key="starlink_positioning_exclusive",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda data: (
|
||||
None
|
||||
if (gps_enabled := data.status.get("gps_enabled")) is None
|
||||
else not gps_enabled
|
||||
),
|
||||
turn_on_fn=lambda coordinator: coordinator.async_set_gps_enabled(False),
|
||||
turn_off_fn=lambda coordinator: coordinator.async_set_gps_enabled(True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
"obstruction_interval": null,
|
||||
"direction_azimuth": -179.00344848632812,
|
||||
"direction_elevation": 68.67173767089844,
|
||||
"is_snr_above_noise_floor": true
|
||||
"is_snr_above_noise_floor": true,
|
||||
"gps_ready": true,
|
||||
"gps_enabled": true,
|
||||
"gps_sats": 12
|
||||
},
|
||||
{
|
||||
"wedges_fraction_obstructed[]": [
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
'direction_elevation': 68.67173767089844,
|
||||
'downlink_throughput_bps': 10108.2724609375,
|
||||
'fraction_obstructed': 0.0,
|
||||
'gps_enabled': True,
|
||||
'gps_ready': True,
|
||||
'gps_sats': 12,
|
||||
'hardware_version': 'rev3_proto2',
|
||||
'id': '**REDACTED**',
|
||||
'is_snr_above_noise_floor': True,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Tests Starlink switches."""
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from starlink_grpc import GrpcError
|
||||
|
||||
from homeassistant.components.starlink.const import DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util.json import JsonArrayType
|
||||
|
||||
from .patchers import (
|
||||
HISTORY_STATS_SUCCESS_PATCHER,
|
||||
LOCATION_DATA_SUCCESS_PATCHER,
|
||||
SLEEP_DATA_SUCCESS_PATCHER,
|
||||
STATUS_DATA_FIXTURE,
|
||||
STATUS_DATA_TARGET,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
ENTITY_ID = "switch.starlink_use_starlink_positioning_exclusively"
|
||||
SET_GPS_CONFIG_TARGET = "homeassistant.components.starlink.coordinator.set_gps_config"
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant, status_data: JsonArrayType
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Starlink integration with the given status data."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4:0000"})
|
||||
|
||||
with (
|
||||
LOCATION_DATA_SUCCESS_PATCHER,
|
||||
SLEEP_DATA_SUCCESS_PATCHER,
|
||||
HISTORY_STATS_SUCCESS_PATCHER,
|
||||
patch(STATUS_DATA_TARGET, return_value=status_data),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("gps_enabled", "expected_state"),
|
||||
[
|
||||
(True, STATE_OFF),
|
||||
(False, STATE_ON),
|
||||
(None, STATE_UNKNOWN),
|
||||
],
|
||||
)
|
||||
async def test_starlink_positioning_exclusive_state(
|
||||
hass: HomeAssistant, gps_enabled: bool | None, expected_state: str
|
||||
) -> None:
|
||||
"""Test the switch is on when the dish does not use GPS."""
|
||||
status_data = deepcopy(STATUS_DATA_FIXTURE)
|
||||
status_data[0]["gps_enabled"] = gps_enabled
|
||||
|
||||
await setup_integration(hass, status_data)
|
||||
|
||||
assert hass.states.get(ENTITY_ID).state == expected_state
|
||||
|
||||
|
||||
async def test_starlink_positioning_exclusive_not_reported(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test the switch is unknown when the dish omits the GPS state."""
|
||||
status_data = deepcopy(STATUS_DATA_FIXTURE)
|
||||
del status_data[0]["gps_enabled"]
|
||||
|
||||
await setup_integration(hass, status_data)
|
||||
|
||||
assert hass.states.get(ENTITY_ID).state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "gps_enabled"),
|
||||
[
|
||||
(SERVICE_TURN_ON, False),
|
||||
(SERVICE_TURN_OFF, True),
|
||||
],
|
||||
)
|
||||
async def test_starlink_positioning_exclusive_set(
|
||||
hass: HomeAssistant, service: str, gps_enabled: bool
|
||||
) -> None:
|
||||
"""Test turning the switch on disables GPS and turning it off enables it."""
|
||||
entry = await setup_integration(hass, deepcopy(STATUS_DATA_FIXTURE))
|
||||
|
||||
with patch(SET_GPS_CONFIG_TARGET) as mock_set_gps_config:
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_set_gps_config.assert_called_once_with(
|
||||
gps_enabled, entry.runtime_data.channel_context
|
||||
)
|
||||
|
||||
|
||||
async def test_starlink_positioning_exclusive_error(hass: HomeAssistant) -> None:
|
||||
"""Test a communication error is raised as HomeAssistantError."""
|
||||
await setup_integration(hass, deepcopy(STATUS_DATA_FIXTURE))
|
||||
|
||||
with (
|
||||
patch(SET_GPS_CONFIG_TARGET, side_effect=GrpcError("error")),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
Reference in New Issue
Block a user