Skip to content

Commit c866dc9

Browse files
eifingerjoostlek
andauthored
Add sensor platform to fressnapf_tracker (home-assistant#157658)
Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent e2acf30 commit c866dc9

File tree

10 files changed

+232
-48
lines changed

10 files changed

+232
-48
lines changed

homeassistant/components/fressnapf_tracker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
FressnapfTrackerDataUpdateCoordinator,
1313
)
1414

15-
PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER]
15+
PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER, Platform.SENSOR]
1616

1717

1818
async def async_setup_entry(

homeassistant/components/fressnapf_tracker/entity.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""fressnapf_tracker class."""
22

33
from homeassistant.helpers.device_registry import DeviceInfo
4+
from homeassistant.helpers.entity import EntityDescription
45
from homeassistant.helpers.update_coordinator import CoordinatorEntity
56

67
from . import FressnapfTrackerDataUpdateCoordinator
@@ -25,3 +26,17 @@ def __init__(self, coordinator: FressnapfTrackerDataUpdateCoordinator) -> None:
2526
manufacturer="Fressnapf",
2627
serial_number=str(self.id),
2728
)
29+
30+
31+
class FressnapfTrackerEntity(FressnapfTrackerBaseEntity):
32+
"""Entity for fressnapf_tracker."""
33+
34+
def __init__(
35+
self,
36+
coordinator: FressnapfTrackerDataUpdateCoordinator,
37+
entity_description: EntityDescription,
38+
) -> None:
39+
"""Initialize the entity."""
40+
super().__init__(coordinator)
41+
self.entity_description = entity_description
42+
self._attr_unique_id = f"{self.id}_{entity_description.key}"

homeassistant/components/fressnapf_tracker/quality_scale.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ rules:
5353
entity-category: todo
5454
entity-device-class: todo
5555
entity-disabled-by-default: todo
56-
entity-translations:
57-
status: exempt
58-
comment: No entities to translate
56+
entity-translations: done
5957
exception-translations: todo
6058
icon-translations: todo
6159
reconfiguration-flow: done
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Sensor platform for fressnapf_tracker."""
2+
3+
from collections.abc import Callable
4+
from dataclasses import dataclass
5+
6+
from fressnapftracker import Tracker
7+
8+
from homeassistant.components.sensor import (
9+
SensorDeviceClass,
10+
SensorEntity,
11+
SensorEntityDescription,
12+
SensorStateClass,
13+
)
14+
from homeassistant.const import PERCENTAGE, EntityCategory
15+
from homeassistant.core import HomeAssistant
16+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
17+
18+
from . import FressnapfTrackerConfigEntry
19+
from .entity import FressnapfTrackerEntity
20+
21+
22+
@dataclass(frozen=True, kw_only=True)
23+
class FressnapfTrackerSensorDescription(SensorEntityDescription):
24+
"""Class describing Fressnapf Tracker sensor entities."""
25+
26+
value_fn: Callable[[Tracker], int]
27+
28+
29+
SENSOR_ENTITY_DESCRIPTIONS: tuple[FressnapfTrackerSensorDescription, ...] = (
30+
FressnapfTrackerSensorDescription(
31+
key="battery",
32+
state_class=SensorStateClass.MEASUREMENT,
33+
device_class=SensorDeviceClass.BATTERY,
34+
native_unit_of_measurement=PERCENTAGE,
35+
entity_category=EntityCategory.DIAGNOSTIC,
36+
value_fn=lambda data: data.battery,
37+
),
38+
)
39+
40+
41+
async def async_setup_entry(
42+
hass: HomeAssistant,
43+
entry: FressnapfTrackerConfigEntry,
44+
async_add_entities: AddConfigEntryEntitiesCallback,
45+
) -> None:
46+
"""Set up the Fressnapf Tracker sensors."""
47+
48+
async_add_entities(
49+
FressnapfTrackerSensor(coordinator, sensor_description)
50+
for sensor_description in SENSOR_ENTITY_DESCRIPTIONS
51+
for coordinator in entry.runtime_data
52+
)
53+
54+
55+
class FressnapfTrackerSensor(FressnapfTrackerEntity, SensorEntity):
56+
"""fressnapf_tracker sensor for general information."""
57+
58+
entity_description: FressnapfTrackerSensorDescription
59+
60+
@property
61+
def native_value(self) -> int:
62+
"""Return the state of the resources if it has been received yet."""
63+
return self.entity_description.value_fn(self.coordinator.data)

tests/components/fressnapf_tracker/snapshots/test_device_tracker.ambr

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,4 @@
11
# serializer version: 1
2-
# name: test_state_entity_device_snapshots[Fluffy-entry]
3-
DeviceRegistryEntrySnapshot({
4-
'area_id': None,
5-
'config_entries': <ANY>,
6-
'config_entries_subentries': <ANY>,
7-
'configuration_url': None,
8-
'connections': set({
9-
}),
10-
'disabled_by': None,
11-
'entry_type': None,
12-
'hw_version': None,
13-
'id': <ANY>,
14-
'identifiers': set({
15-
tuple(
16-
'fressnapf_tracker',
17-
'ABC123456',
18-
),
19-
}),
20-
'labels': set({
21-
}),
22-
'manufacturer': 'Fressnapf',
23-
'model': 'GPS Tracker 2.0',
24-
'model_id': None,
25-
'name': 'Fluffy',
26-
'name_by_user': None,
27-
'primary_config_entry': <ANY>,
28-
'serial_number': 'ABC123456',
29-
'sw_version': None,
30-
'via_device_id': None,
31-
})
32-
# ---
332
# name: test_state_entity_device_snapshots[device_tracker.fluffy-entry]
343
EntityRegistryEntrySnapshot({
354
'aliases': set({
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# serializer version: 1
2+
# name: test_state_entity_device_snapshots[Fluffy-entry]
3+
DeviceRegistryEntrySnapshot({
4+
'area_id': None,
5+
'config_entries': <ANY>,
6+
'config_entries_subentries': <ANY>,
7+
'configuration_url': None,
8+
'connections': set({
9+
}),
10+
'disabled_by': None,
11+
'entry_type': None,
12+
'hw_version': None,
13+
'id': <ANY>,
14+
'identifiers': set({
15+
tuple(
16+
'fressnapf_tracker',
17+
'ABC123456',
18+
),
19+
}),
20+
'labels': set({
21+
}),
22+
'manufacturer': 'Fressnapf',
23+
'model': 'GPS Tracker 2.0',
24+
'model_id': None,
25+
'name': 'Fluffy',
26+
'name_by_user': None,
27+
'primary_config_entry': <ANY>,
28+
'serial_number': 'ABC123456',
29+
'sw_version': None,
30+
'via_device_id': None,
31+
})
32+
# ---
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# serializer version: 1
2+
# name: test_state_entity_device_snapshots[sensor.fluffy_battery-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': dict({
8+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
9+
}),
10+
'config_entry_id': <ANY>,
11+
'config_subentry_id': <ANY>,
12+
'device_class': None,
13+
'device_id': <ANY>,
14+
'disabled_by': None,
15+
'domain': 'sensor',
16+
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
17+
'entity_id': 'sensor.fluffy_battery',
18+
'has_entity_name': True,
19+
'hidden_by': None,
20+
'icon': None,
21+
'id': <ANY>,
22+
'labels': set({
23+
}),
24+
'name': None,
25+
'options': dict({
26+
}),
27+
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
28+
'original_icon': None,
29+
'original_name': 'Battery',
30+
'platform': 'fressnapf_tracker',
31+
'previous_unique_id': None,
32+
'suggested_object_id': None,
33+
'supported_features': 0,
34+
'translation_key': None,
35+
'unique_id': 'ABC123456_battery',
36+
'unit_of_measurement': '%',
37+
})
38+
# ---
39+
# name: test_state_entity_device_snapshots[sensor.fluffy_battery-state]
40+
StateSnapshot({
41+
'attributes': ReadOnlyDict({
42+
'device_class': 'battery',
43+
'friendly_name': 'Fluffy Battery',
44+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
45+
'unit_of_measurement': '%',
46+
}),
47+
'context': <ANY>,
48+
'entity_id': 'sensor.fluffy_battery',
49+
'last_changed': <ANY>,
50+
'last_reported': <ANY>,
51+
'last_updated': <ANY>,
52+
'state': '85',
53+
})
54+
# ---

tests/components/fressnapf_tracker/test_device_tracker.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,39 @@
11
"""Test the Fressnapf Tracker device tracker platform."""
22

3-
from unittest.mock import AsyncMock, MagicMock
3+
from collections.abc import AsyncGenerator
4+
from unittest.mock import AsyncMock, MagicMock, patch
45

56
from fressnapftracker import Tracker
67
import pytest
78
from syrupy.assertion import SnapshotAssertion
89

9-
from homeassistant.const import STATE_UNAVAILABLE
10+
from homeassistant.const import STATE_UNAVAILABLE, Platform
1011
from homeassistant.core import HomeAssistant
11-
from homeassistant.helpers import device_registry as dr, entity_registry as er
12+
from homeassistant.helpers import entity_registry as er
1213

1314
from tests.common import MockConfigEntry, snapshot_platform
1415

1516

17+
@pytest.fixture(autouse=True)
18+
async def platforms() -> AsyncGenerator[None]:
19+
"""Return the platforms to be loaded for this test."""
20+
with patch(
21+
"homeassistant.components.fressnapf_tracker.PLATFORMS",
22+
[Platform.DEVICE_TRACKER],
23+
):
24+
yield
25+
26+
1627
@pytest.mark.usefixtures("init_integration")
1728
async def test_state_entity_device_snapshots(
1829
hass: HomeAssistant,
1930
entity_registry: er.EntityRegistry,
20-
device_registry: dr.DeviceRegistry,
2131
mock_config_entry: MockConfigEntry,
2232
snapshot: SnapshotAssertion,
2333
) -> None:
2434
"""Test device tracker entity is created correctly."""
2535
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
2636

27-
device_entries = dr.async_entries_for_config_entry(
28-
device_registry, mock_config_entry.entry_id
29-
)
30-
assert device_entries
31-
for device_entry in device_entries:
32-
assert device_entry == snapshot(name=f"{device_entry.name}-entry"), (
33-
f"device entry snapshot failed for {device_entry.name}"
34-
)
35-
3637

3738
@pytest.mark.usefixtures("mock_auth_client")
3839
async def test_device_tracker_no_position(

tests/components/fressnapf_tracker/test_init.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from unittest.mock import AsyncMock, MagicMock
44

55
import pytest
6+
from syrupy.assertion import SnapshotAssertion
67

78
from homeassistant.config_entries import ConfigEntryState
89
from homeassistant.core import HomeAssistant
10+
from homeassistant.helpers import device_registry as dr
911

1012
from tests.common import MockConfigEntry
1113

@@ -59,3 +61,20 @@ async def test_setup_entry_api_error(
5961
await hass.async_block_till_done()
6062

6163
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
64+
65+
66+
@pytest.mark.usefixtures("init_integration")
67+
async def test_state_entity_device_snapshots(
68+
device_registry: dr.DeviceRegistry,
69+
mock_config_entry: MockConfigEntry,
70+
snapshot: SnapshotAssertion,
71+
) -> None:
72+
"""Test sensor entity is created correctly."""
73+
device_entries = dr.async_entries_for_config_entry(
74+
device_registry, mock_config_entry.entry_id
75+
)
76+
assert device_entries
77+
for device_entry in device_entries:
78+
assert device_entry == snapshot(name=f"{device_entry.name}-entry"), (
79+
f"device entry snapshot failed for {device_entry.name}"
80+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Test the Fressnapf Tracker sensor platform."""
2+
3+
from collections.abc import AsyncGenerator
4+
from unittest.mock import patch
5+
6+
import pytest
7+
from syrupy.assertion import SnapshotAssertion
8+
9+
from homeassistant.const import Platform
10+
from homeassistant.core import HomeAssistant
11+
from homeassistant.helpers import entity_registry as er
12+
13+
from tests.common import MockConfigEntry, snapshot_platform
14+
15+
16+
@pytest.fixture(autouse=True)
17+
async def platforms() -> AsyncGenerator[None]:
18+
"""Return the platforms to be loaded for this test."""
19+
with patch(
20+
"homeassistant.components.fressnapf_tracker.PLATFORMS", [Platform.SENSOR]
21+
):
22+
yield
23+
24+
25+
@pytest.mark.usefixtures("init_integration")
26+
async def test_state_entity_device_snapshots(
27+
hass: HomeAssistant,
28+
entity_registry: er.EntityRegistry,
29+
mock_config_entry: MockConfigEntry,
30+
snapshot: SnapshotAssertion,
31+
) -> None:
32+
"""Test sensor entity is created correctly."""
33+
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

0 commit comments

Comments
 (0)