Skip to content

Commit cd86c78

Browse files
bieniuthecode
andauthored
Control modes for Shelly Cury (home-assistant#155665)
Co-authored-by: Shay Levy <[email protected]>
1 parent a0da295 commit cd86c78

File tree

8 files changed

+343
-37
lines changed

8 files changed

+343
-37
lines changed

homeassistant/components/shelly/icons.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@
7070
}
7171
},
7272
"switch": {
73+
"cury_away_mode": {
74+
"default": "mdi:home-outline",
75+
"state": {
76+
"off": "mdi:home-import-outline",
77+
"on": "mdi:home-export-outline"
78+
}
79+
},
7380
"cury_boost": {
7481
"default": "mdi:rocket-launch"
7582
},

homeassistant/components/shelly/select.py

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from typing import Final
6+
from typing import TYPE_CHECKING, Final
77

88
from aioshelly.const import RPC_GENERATIONS
99

@@ -37,14 +37,92 @@
3737
class RpcSelectDescription(RpcEntityDescription, SelectEntityDescription):
3838
"""Class to describe a RPC select entity."""
3939

40+
method: str
41+
42+
43+
class RpcSelect(ShellyRpcAttributeEntity, SelectEntity):
44+
"""Represent a RPC select entity."""
45+
46+
entity_description: RpcSelectDescription
47+
_id: int
48+
49+
def __init__(
50+
self,
51+
coordinator: ShellyRpcCoordinator,
52+
key: str,
53+
attribute: str,
54+
description: RpcSelectDescription,
55+
) -> None:
56+
"""Initialize select."""
57+
super().__init__(coordinator, key, attribute, description)
58+
59+
if self.option_map:
60+
self._attr_options = list(self.option_map.values())
61+
62+
if hasattr(self, "_attr_name") and description.role != ROLE_GENERIC:
63+
delattr(self, "_attr_name")
64+
65+
@property
66+
def current_option(self) -> str | None:
67+
"""Return the selected entity option to represent the entity state."""
68+
if isinstance(self.attribute_value, str) and self.option_map:
69+
return self.option_map[self.attribute_value]
70+
71+
return None
72+
73+
@rpc_call
74+
async def async_select_option(self, option: str) -> None:
75+
"""Change the value."""
76+
method = getattr(self.coordinator.device, self.entity_description.method)
77+
78+
if TYPE_CHECKING:
79+
assert method is not None
80+
81+
if self.reversed_option_map:
82+
await method(self._id, self.reversed_option_map[option])
83+
else:
84+
await method(self._id, option)
85+
86+
87+
class RpcCuryModeSelect(RpcSelect):
88+
"""Represent a RPC select entity for Cury modes."""
89+
90+
@property
91+
def current_option(self) -> str | None:
92+
"""Return the selected entity option to represent the entity state."""
93+
if self.attribute_value is None:
94+
return "none"
95+
96+
if TYPE_CHECKING:
97+
assert isinstance(self.attribute_value, str)
98+
99+
return self.attribute_value
100+
40101

41102
RPC_SELECT_ENTITIES: Final = {
103+
"cury_mode": RpcSelectDescription(
104+
key="cury",
105+
sub_key="mode",
106+
translation_key="cury_mode",
107+
options=[
108+
"hall",
109+
"bedroom",
110+
"living_room",
111+
"lavatory_room",
112+
"none",
113+
"reception",
114+
"workplace",
115+
],
116+
method="cury_set_mode",
117+
entity_class=RpcCuryModeSelect,
118+
),
42119
"enum_generic": RpcSelectDescription(
43120
key="enum",
44121
sub_key="value",
45122
removal_condition=lambda config, _status, key: not is_view_for_platform(
46123
config, key, SELECT_PLATFORM
47124
),
125+
method="enum_set",
48126
role=ROLE_GENERIC,
49127
),
50128
}
@@ -89,37 +167,3 @@ def _async_setup_rpc_entry(
89167
virtual_text_ids,
90168
"enum",
91169
)
92-
93-
94-
class RpcSelect(ShellyRpcAttributeEntity, SelectEntity):
95-
"""Represent a RPC select entity."""
96-
97-
entity_description: RpcSelectDescription
98-
_id: int
99-
100-
def __init__(
101-
self,
102-
coordinator: ShellyRpcCoordinator,
103-
key: str,
104-
attribute: str,
105-
description: RpcSelectDescription,
106-
) -> None:
107-
"""Initialize select."""
108-
super().__init__(coordinator, key, attribute, description)
109-
110-
self._attr_options = list(self.option_map.values())
111-
112-
@property
113-
def current_option(self) -> str | None:
114-
"""Return the selected entity option to represent the entity state."""
115-
if not isinstance(self.attribute_value, str):
116-
return None
117-
118-
return self.option_map[self.attribute_value]
119-
120-
@rpc_call
121-
async def async_select_option(self, option: str) -> None:
122-
"""Change the value."""
123-
await self.coordinator.device.enum_set(
124-
self._id, self.reversed_option_map[option]
125-
)

homeassistant/components/shelly/strings.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@
162162
}
163163
}
164164
},
165+
"select": {
166+
"cury_mode": {
167+
"name": "Mode",
168+
"state": {
169+
"bedroom": "Bedroom",
170+
"hall": "Hall",
171+
"lavatory_room": "Lavatory room",
172+
"living_room": "Living room",
173+
"none": "None",
174+
"reception": "Reception",
175+
"workplace": "Workplace"
176+
}
177+
}
178+
},
165179
"sensor": {
166180
"adc": {
167181
"name": "ADC"

homeassistant/components/shelly/switch.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ class RpcSwitchDescription(RpcEntityDescription, SwitchEntityDescription):
290290
available=lambda status: (right := status["right"]) is not None
291291
and right.get("vial", {}).get("level", -1) != -1,
292292
),
293+
"cury_away_mode": RpcSwitchDescription(
294+
key="cury",
295+
sub_key="away_mode",
296+
name="Away mode",
297+
translation_key="cury_away_mode",
298+
is_on=lambda status: status["away_mode"],
299+
method_on="cury_set_away_mode",
300+
method_off="cury_set_away_mode",
301+
method_params_fn=lambda id, value: (id, value),
302+
),
293303
}
294304

295305

tests/components/shelly/snapshots/test_devices.ambr

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,73 @@
262262
'state': '45',
263263
})
264264
# ---
265+
# name: test_device[cury_gen4][select.test_name_mode-entry]
266+
EntityRegistryEntrySnapshot({
267+
'aliases': set({
268+
}),
269+
'area_id': None,
270+
'capabilities': dict({
271+
'options': list([
272+
'hall',
273+
'bedroom',
274+
'living_room',
275+
'lavatory_room',
276+
'none',
277+
'reception',
278+
'workplace',
279+
]),
280+
}),
281+
'config_entry_id': <ANY>,
282+
'config_subentry_id': <ANY>,
283+
'device_class': None,
284+
'device_id': <ANY>,
285+
'disabled_by': None,
286+
'domain': 'select',
287+
'entity_category': None,
288+
'entity_id': 'select.test_name_mode',
289+
'has_entity_name': True,
290+
'hidden_by': None,
291+
'icon': None,
292+
'id': <ANY>,
293+
'labels': set({
294+
}),
295+
'name': None,
296+
'options': dict({
297+
}),
298+
'original_device_class': None,
299+
'original_icon': None,
300+
'original_name': 'Mode',
301+
'platform': 'shelly',
302+
'previous_unique_id': None,
303+
'suggested_object_id': None,
304+
'supported_features': 0,
305+
'translation_key': 'cury_mode',
306+
'unique_id': '123456789ABC-cury:0-cury_mode',
307+
'unit_of_measurement': None,
308+
})
309+
# ---
310+
# name: test_device[cury_gen4][select.test_name_mode-state]
311+
StateSnapshot({
312+
'attributes': ReadOnlyDict({
313+
'friendly_name': 'Test name Mode',
314+
'options': list([
315+
'hall',
316+
'bedroom',
317+
'living_room',
318+
'lavatory_room',
319+
'none',
320+
'reception',
321+
'workplace',
322+
]),
323+
}),
324+
'context': <ANY>,
325+
'entity_id': 'select.test_name_mode',
326+
'last_changed': <ANY>,
327+
'last_reported': <ANY>,
328+
'last_updated': <ANY>,
329+
'state': 'living_room',
330+
})
331+
# ---
265332
# name: test_device[cury_gen4][sensor.test_name_last_restart-entry]
266333
EntityRegistryEntrySnapshot({
267334
'aliases': set({
@@ -564,6 +631,54 @@
564631
'state': '-49',
565632
})
566633
# ---
634+
# name: test_device[cury_gen4][switch.test_name_away_mode-entry]
635+
EntityRegistryEntrySnapshot({
636+
'aliases': set({
637+
}),
638+
'area_id': None,
639+
'capabilities': None,
640+
'config_entry_id': <ANY>,
641+
'config_subentry_id': <ANY>,
642+
'device_class': None,
643+
'device_id': <ANY>,
644+
'disabled_by': None,
645+
'domain': 'switch',
646+
'entity_category': None,
647+
'entity_id': 'switch.test_name_away_mode',
648+
'has_entity_name': True,
649+
'hidden_by': None,
650+
'icon': None,
651+
'id': <ANY>,
652+
'labels': set({
653+
}),
654+
'name': None,
655+
'options': dict({
656+
}),
657+
'original_device_class': None,
658+
'original_icon': None,
659+
'original_name': 'Away mode',
660+
'platform': 'shelly',
661+
'previous_unique_id': None,
662+
'suggested_object_id': None,
663+
'supported_features': 0,
664+
'translation_key': 'cury_away_mode',
665+
'unique_id': '123456789ABC-cury:0-cury_away_mode',
666+
'unit_of_measurement': None,
667+
})
668+
# ---
669+
# name: test_device[cury_gen4][switch.test_name_away_mode-state]
670+
StateSnapshot({
671+
'attributes': ReadOnlyDict({
672+
'friendly_name': 'Test name Away mode',
673+
}),
674+
'context': <ANY>,
675+
'entity_id': 'switch.test_name_away_mode',
676+
'last_changed': <ANY>,
677+
'last_reported': <ANY>,
678+
'last_updated': <ANY>,
679+
'state': 'off',
680+
})
681+
# ---
567682
# name: test_device[cury_gen4][switch.test_name_left_slot-entry]
568683
EntityRegistryEntrySnapshot({
569684
'aliases': set({

tests/components/shelly/snapshots/test_switch.ambr

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,52 @@
11
# serializer version: 1
2+
# name: test_cury_switch_entity[switch.test_name_away_mode-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': None,
8+
'config_entry_id': <ANY>,
9+
'config_subentry_id': <ANY>,
10+
'device_class': None,
11+
'device_id': <ANY>,
12+
'disabled_by': None,
13+
'domain': 'switch',
14+
'entity_category': None,
15+
'entity_id': 'switch.test_name_away_mode',
16+
'has_entity_name': True,
17+
'hidden_by': None,
18+
'icon': None,
19+
'id': <ANY>,
20+
'labels': set({
21+
}),
22+
'name': None,
23+
'options': dict({
24+
}),
25+
'original_device_class': None,
26+
'original_icon': None,
27+
'original_name': 'Away mode',
28+
'platform': 'shelly',
29+
'previous_unique_id': None,
30+
'suggested_object_id': None,
31+
'supported_features': 0,
32+
'translation_key': 'cury_away_mode',
33+
'unique_id': '123456789ABC-cury:0-cury_away_mode',
34+
'unit_of_measurement': None,
35+
})
36+
# ---
37+
# name: test_cury_switch_entity[switch.test_name_away_mode-state]
38+
StateSnapshot({
39+
'attributes': ReadOnlyDict({
40+
'friendly_name': 'Test name Away mode',
41+
}),
42+
'context': <ANY>,
43+
'entity_id': 'switch.test_name_away_mode',
44+
'last_changed': <ANY>,
45+
'last_reported': <ANY>,
46+
'last_updated': <ANY>,
47+
'state': 'off',
48+
})
49+
# ---
250
# name: test_cury_switch_entity[switch.test_name_left_slot-entry]
351
EntityRegistryEntrySnapshot({
452
'aliases': set({

0 commit comments

Comments
 (0)