add switch platform to fressnapf_tracker (#157971)

This commit is contained in:
Kevin Stillhammer
2025-12-04 16:44:53 +01:00
committed by GitHub
parent 4805b33a27
commit 0ccfd77fef
7 changed files with 243 additions and 2 deletions
@@ -17,6 +17,7 @@ PLATFORMS: list[Platform] = [
Platform.DEVICE_TRACKER,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]
@@ -4,6 +4,14 @@
"pet": {
"default": "mdi:paw"
}
},
"switch": {
"energy_saving": {
"default": "mdi:sleep",
"state": {
"off": "mdi:sleep-off"
}
}
}
}
}
@@ -51,6 +51,11 @@
"led": {
"name": "Flashlight"
}
},
"switch": {
"energy_saving": {
"name": "Sleep mode"
}
}
},
"exceptions": {
@@ -0,0 +1,58 @@
"""Switch platform for Fressnapf Tracker."""
from typing import TYPE_CHECKING, Any
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import FressnapfTrackerConfigEntry
from .entity import FressnapfTrackerEntity
SWITCH_ENTITY_DESCRIPTION = SwitchEntityDescription(
translation_key="energy_saving",
entity_category=EntityCategory.CONFIG,
device_class=SwitchDeviceClass.SWITCH,
key="energy_saving",
)
async def async_setup_entry(
hass: HomeAssistant,
entry: FressnapfTrackerConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Fressnapf Tracker switches."""
async_add_entities(
FressnapfTrackerSwitch(coordinator, SWITCH_ENTITY_DESCRIPTION)
for coordinator in entry.runtime_data
if coordinator.data.tracker_settings.features.energy_saving_mode
)
class FressnapfTrackerSwitch(FressnapfTrackerEntity, SwitchEntity):
"""Fressnapf Tracker switch."""
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the device."""
await self.coordinator.client.set_energy_saving(True)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the device."""
await self.coordinator.client.set_energy_saving(False)
await self.coordinator.async_request_refresh()
@property
def is_on(self) -> bool:
"""Return true if device is on."""
if TYPE_CHECKING:
# The entity is not created if energy_saving is None
assert self.coordinator.data.energy_saving is not None
return self.coordinator.data.energy_saving.value == 1
@@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from fressnapftracker import (
Device,
EnergySaving,
LedActivatable,
LedBrightness,
PhoneVerificationResponse,
@@ -44,9 +45,12 @@ MOCK_TRACKER = Tracker(
),
tracker_settings=TrackerSettings(
generation="GPS Tracker 2.0",
features=TrackerFeatures(flash_light=True, live_tracking=True),
features=TrackerFeatures(
flash_light=True, energy_saving_mode=True, live_tracking=True
),
),
led_brightness=LedBrightness(value=50),
led_brightness=LedBrightness(status="ok", value=50),
energy_saving=EnergySaving(status="ok", value=1),
deep_sleep=None,
led_activatable=LedActivatable(
has_led=True,
@@ -133,6 +137,7 @@ def mock_api_client() -> Generator[MagicMock]:
client = mock_api_client.return_value
client.get_tracker = AsyncMock(return_value=MOCK_TRACKER)
client.set_led_brightness = AsyncMock(return_value=None)
client.set_energy_saving = AsyncMock(return_value=None)
yield client
@@ -0,0 +1,50 @@
# serializer version: 1
# name: test_state_entity_device_snapshots[switch.fluffy_sleep_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.fluffy_sleep_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SwitchDeviceClass.SWITCH: 'switch'>,
'original_icon': None,
'original_name': 'Sleep mode',
'platform': 'fressnapf_tracker',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'energy_saving',
'unique_id': 'ABC123456_energy_saving',
'unit_of_measurement': None,
})
# ---
# name: test_state_entity_device_snapshots[switch.fluffy_sleep_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'switch',
'friendly_name': 'Fluffy Sleep mode',
}),
'context': <ANY>,
'entity_id': 'switch.fluffy_sleep_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
@@ -0,0 +1,114 @@
"""Test the Fressnapf Tracker switch platform."""
from collections.abc import AsyncGenerator
from unittest.mock import MagicMock, patch
from fressnapftracker import Tracker, TrackerFeatures, TrackerSettings
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
TRACKER_NO_ENERGY_SAVING_MODE = Tracker(
name="Fluffy",
battery=0,
charging=False,
position=None,
tracker_settings=TrackerSettings(
generation="GPS Tracker 2.0",
features=TrackerFeatures(energy_saving_mode=False),
),
)
@pytest.fixture(autouse=True)
async def platforms() -> AsyncGenerator[None]:
"""Return the platforms to be loaded for this test."""
with patch(
"homeassistant.components.fressnapf_tracker.PLATFORMS", [Platform.SWITCH]
):
yield
@pytest.mark.usefixtures("init_integration")
async def test_state_entity_device_snapshots(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test switch entity is created correctly."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("mock_auth_client")
async def test_not_added_when_no_energy_saving_mode(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_api_client: MagicMock,
) -> None:
"""Test switch entity is created correctly."""
mock_api_client.get_tracker.return_value = TRACKER_NO_ENERGY_SAVING_MODE
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity_entries = er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
)
assert len(entity_entries) == 0
@pytest.mark.usefixtures("init_integration")
async def test_turn_on(
hass: HomeAssistant,
mock_api_client: MagicMock,
) -> None:
"""Test turning the switch on."""
entity_id = "switch.fluffy_sleep_mode"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_api_client.set_energy_saving.assert_called_once_with(True)
@pytest.mark.usefixtures("init_integration")
async def test_turn_off(
hass: HomeAssistant,
mock_api_client: MagicMock,
) -> None:
"""Test turning the switch off."""
entity_id = "switch.fluffy_sleep_mode"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_api_client.set_energy_saving.assert_called_once_with(False)