Skip to content

Commit 53c644a

Browse files
authored
add light platform to fressnapf_tracker (home-assistant#157865)
1 parent 5e9107e commit 53c644a

File tree

6 files changed

+374
-25
lines changed

6 files changed

+374
-25
lines changed

homeassistant/components/fressnapf_tracker/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
PLATFORMS: list[Platform] = [
1616
Platform.BINARY_SENSOR,
1717
Platform.DEVICE_TRACKER,
18+
Platform.LIGHT,
1819
Platform.SENSOR,
1920
]
2021

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Light platform for fressnapf_tracker."""
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
from homeassistant.components.light import (
6+
ATTR_BRIGHTNESS,
7+
ColorMode,
8+
LightEntity,
9+
LightEntityDescription,
10+
)
11+
from homeassistant.const import EntityCategory
12+
from homeassistant.core import HomeAssistant
13+
from homeassistant.exceptions import HomeAssistantError
14+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
15+
16+
from . import FressnapfTrackerConfigEntry
17+
from .const import DOMAIN
18+
from .entity import FressnapfTrackerEntity
19+
20+
LIGHT_ENTITY_DESCRIPTION = LightEntityDescription(
21+
translation_key="led",
22+
entity_category=EntityCategory.CONFIG,
23+
key="led_brightness_value",
24+
)
25+
26+
27+
async def async_setup_entry(
28+
hass: HomeAssistant,
29+
entry: FressnapfTrackerConfigEntry,
30+
async_add_entities: AddConfigEntryEntitiesCallback,
31+
) -> None:
32+
"""Set up the Fressnapf Tracker lights."""
33+
34+
async_add_entities(
35+
FressnapfTrackerLight(coordinator, LIGHT_ENTITY_DESCRIPTION)
36+
for coordinator in entry.runtime_data
37+
if coordinator.data.led_activatable is not None
38+
and coordinator.data.led_activatable.has_led
39+
and coordinator.data.tracker_settings.features.flash_light
40+
)
41+
42+
43+
class FressnapfTrackerLight(FressnapfTrackerEntity, LightEntity):
44+
"""Fressnapf Tracker light."""
45+
46+
_attr_color_mode: ColorMode = ColorMode.BRIGHTNESS
47+
_attr_supported_color_modes: set[ColorMode] = {ColorMode.BRIGHTNESS}
48+
49+
@property
50+
def brightness(self) -> int:
51+
"""Return the brightness of this light between 0..255."""
52+
if TYPE_CHECKING:
53+
# The entity is not created if led_brightness_value is None
54+
assert self.coordinator.data.led_brightness_value is not None
55+
return int(round((self.coordinator.data.led_brightness_value / 100) * 255))
56+
57+
async def async_turn_on(self, **kwargs: Any) -> None:
58+
"""Turn on the device."""
59+
self.raise_if_not_activatable()
60+
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
61+
brightness = int((brightness / 255) * 100)
62+
await self.coordinator.client.set_led_brightness(brightness)
63+
await self.coordinator.async_request_refresh()
64+
65+
async def async_turn_off(self, **kwargs: Any) -> None:
66+
"""Turn off the device."""
67+
await self.coordinator.client.set_led_brightness(0)
68+
await self.coordinator.async_request_refresh()
69+
70+
def raise_if_not_activatable(self) -> None:
71+
"""Raise error with reasoning if light is not activatable."""
72+
if TYPE_CHECKING:
73+
# The entity is not created if led_activatable is None
74+
assert self.coordinator.data.led_activatable is not None
75+
error_type: str | None = None
76+
if not self.coordinator.data.led_activatable.seen_recently:
77+
error_type = "not_seen_recently"
78+
elif not self.coordinator.data.led_activatable.not_charging:
79+
error_type = "charging"
80+
elif not self.coordinator.data.led_activatable.nonempty_battery:
81+
error_type = "low_battery"
82+
if error_type is not None:
83+
raise HomeAssistantError(
84+
translation_domain=DOMAIN,
85+
translation_key=error_type,
86+
)
87+
88+
@property
89+
def is_on(self) -> bool:
90+
"""Return true if device is on."""
91+
if self.coordinator.data.led_brightness_value is not None:
92+
return self.coordinator.data.led_brightness_value > 0
93+
return False

homeassistant/components/fressnapf_tracker/strings.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,23 @@
4545
}
4646
}
4747
}
48+
},
49+
"entity": {
50+
"light": {
51+
"led": {
52+
"name": "Flashlight"
53+
}
54+
}
55+
},
56+
"exceptions": {
57+
"charging": {
58+
"message": "The flashlight cannot be activated while charging."
59+
},
60+
"low_battery": {
61+
"message": "The flashlight cannot be activated due to low battery."
62+
},
63+
"not_seen_recently": {
64+
"message": "The flashlight cannot be activated when the tracker has not moved recently."
65+
}
4866
}
4967
}

tests/components/fressnapf_tracker/conftest.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from fressnapftracker import (
77
Device,
8+
LedActivatable,
9+
LedBrightness,
810
PhoneVerificationResponse,
911
Position,
1012
SmsCodeResponse,
@@ -30,6 +32,30 @@
3032
MOCK_ACCESS_TOKEN = "mock_access_token"
3133
MOCK_SERIAL_NUMBER = "ABC123456"
3234
MOCK_DEVICE_TOKEN = "mock_device_token"
35+
MOCK_TRACKER = Tracker(
36+
name="Fluffy",
37+
battery=85,
38+
charging=False,
39+
position=Position(
40+
lat=52.520008,
41+
lng=13.404954,
42+
accuracy=10,
43+
timestamp="2024-01-15T12:00:00Z",
44+
),
45+
tracker_settings=TrackerSettings(
46+
generation="GPS Tracker 2.0",
47+
features=TrackerFeatures(flash_light=True, live_tracking=True),
48+
),
49+
led_brightness=LedBrightness(value=50),
50+
deep_sleep=None,
51+
led_activatable=LedActivatable(
52+
has_led=True,
53+
seen_recently=True,
54+
nonempty_battery=True,
55+
not_charging=True,
56+
overall=True,
57+
),
58+
)
3359

3460

3561
@pytest.fixture
@@ -42,29 +68,6 @@ def mock_setup_entry() -> Generator[AsyncMock]:
4268
yield mock_setup_entry
4369

4470

45-
@pytest.fixture
46-
def mock_tracker() -> Tracker:
47-
"""Create a mock Tracker object."""
48-
return Tracker(
49-
name="Fluffy",
50-
battery=85,
51-
charging=False,
52-
position=Position(
53-
lat=52.520008,
54-
lng=13.404954,
55-
accuracy=10,
56-
timestamp="2024-01-15T12:00:00Z",
57-
),
58-
tracker_settings=TrackerSettings(
59-
generation="GPS Tracker 2.0",
60-
features=TrackerFeatures(live_tracking=True),
61-
),
62-
led_brightness=None,
63-
deep_sleep=None,
64-
led_activatable=None,
65-
)
66-
67-
6871
@pytest.fixture
6972
def mock_tracker_no_position() -> Tracker:
7073
"""Create a mock Tracker object without position."""
@@ -122,13 +125,14 @@ def mock_auth_client(mock_device: Device) -> Generator[MagicMock]:
122125

123126

124127
@pytest.fixture
125-
def mock_api_client(mock_tracker: Tracker) -> Generator[MagicMock]:
128+
def mock_api_client() -> Generator[MagicMock]:
126129
"""Mock the ApiClient."""
127130
with patch(
128131
"homeassistant.components.fressnapf_tracker.coordinator.ApiClient"
129132
) as mock_api_client:
130133
client = mock_api_client.return_value
131-
client.get_tracker = AsyncMock(return_value=mock_tracker)
134+
client.get_tracker = AsyncMock(return_value=MOCK_TRACKER)
135+
client.set_led_brightness = AsyncMock(return_value=None)
132136
yield client
133137

134138

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# serializer version: 1
2+
# name: test_state_entity_device_snapshots[light.fluffy_flashlight-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': dict({
8+
'supported_color_modes': list([
9+
<ColorMode.BRIGHTNESS: 'brightness'>,
10+
]),
11+
}),
12+
'config_entry_id': <ANY>,
13+
'config_subentry_id': <ANY>,
14+
'device_class': None,
15+
'device_id': <ANY>,
16+
'disabled_by': None,
17+
'domain': 'light',
18+
'entity_category': <EntityCategory.CONFIG: 'config'>,
19+
'entity_id': 'light.fluffy_flashlight',
20+
'has_entity_name': True,
21+
'hidden_by': None,
22+
'icon': None,
23+
'id': <ANY>,
24+
'labels': set({
25+
}),
26+
'name': None,
27+
'options': dict({
28+
}),
29+
'original_device_class': None,
30+
'original_icon': None,
31+
'original_name': 'Flashlight',
32+
'platform': 'fressnapf_tracker',
33+
'previous_unique_id': None,
34+
'suggested_object_id': None,
35+
'supported_features': 0,
36+
'translation_key': 'led',
37+
'unique_id': 'ABC123456_led_brightness_value',
38+
'unit_of_measurement': None,
39+
})
40+
# ---
41+
# name: test_state_entity_device_snapshots[light.fluffy_flashlight-state]
42+
StateSnapshot({
43+
'attributes': ReadOnlyDict({
44+
'brightness': 128,
45+
'color_mode': <ColorMode.BRIGHTNESS: 'brightness'>,
46+
'friendly_name': 'Fluffy Flashlight',
47+
'supported_color_modes': list([
48+
<ColorMode.BRIGHTNESS: 'brightness'>,
49+
]),
50+
'supported_features': <LightEntityFeature: 0>,
51+
}),
52+
'context': <ANY>,
53+
'entity_id': 'light.fluffy_flashlight',
54+
'last_changed': <ANY>,
55+
'last_reported': <ANY>,
56+
'last_updated': <ANY>,
57+
'state': 'on',
58+
})
59+
# ---

0 commit comments

Comments
 (0)