Skip to content

Commit d861e5b

Browse files
committed
Re-organize HA discovery messages
1 parent 0ba21e0 commit d861e5b

File tree

5 files changed

+764
-662
lines changed

5 files changed

+764
-662
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
if TYPE_CHECKING:
6+
from collections.abc import Collection
7+
8+
9+
class HaCustomAvailabilityConfig:
10+
def __init__(
11+
self,
12+
*,
13+
rules: list[HaCustomAvailabilityEntry],
14+
mode: str = "all",
15+
) -> None:
16+
self.__rules = rules
17+
self.__mode = mode
18+
19+
def to_dict(self) -> dict[str, Any]:
20+
return {
21+
"availability": [r.to_dict() for r in set(self.__rules)],
22+
"availability_mode": self.__mode,
23+
}
24+
25+
26+
class HaCustomAvailabilityEntry:
27+
def __init__(
28+
self,
29+
*,
30+
topic: str,
31+
template: str | None = None,
32+
payload_available: str = "online",
33+
payload_not_available: str = "offline",
34+
) -> None:
35+
self.__topic = topic
36+
self.__template = template
37+
self.__payload_available = payload_available
38+
self.__payload_not_available = payload_not_available
39+
40+
def to_dict(self) -> dict[str, Any]:
41+
result = {
42+
"topic": self.__topic,
43+
"payload_available": self.__payload_available,
44+
"payload_not_available": self.__payload_not_available,
45+
}
46+
if self.__template:
47+
result.update({"value_template": self.__template})
48+
return result
49+
50+
def __key(self) -> Collection[str | None]:
51+
return (
52+
self.__topic,
53+
self.__template,
54+
self.__payload_available,
55+
self.__payload_not_available,
56+
)
57+
58+
def __hash__(self) -> int:
59+
return hash(self.__key())
60+
61+
def __eq__(self, other: object) -> bool:
62+
if isinstance(other, HaCustomAvailabilityEntry):
63+
return self.__key() == other.__key()
64+
return NotImplemented
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
from __future__ import annotations
2+
3+
import abc
4+
from abc import abstractmethod
5+
from typing import TYPE_CHECKING, Any
6+
7+
if TYPE_CHECKING:
8+
from integrations.home_assistant.availability import HaCustomAvailabilityConfig
9+
10+
11+
class HomeAssistantDiscoveryBase(metaclass=abc.ABCMeta):
12+
@abstractmethod
13+
def _get_state_topic(self, raw_topic: str) -> str:
14+
raise NotImplementedError
15+
16+
@abstractmethod
17+
def _get_command_topic(self, raw_topic: str) -> str:
18+
raise NotImplementedError
19+
20+
@abstractmethod
21+
def _publish_ha_discovery_message(
22+
self,
23+
sensor_type: str,
24+
sensor_name: str,
25+
payload: dict[str, Any],
26+
custom_availability: HaCustomAvailabilityConfig | None = None,
27+
) -> str:
28+
raise NotImplementedError
29+
30+
def _publish_select(
31+
self,
32+
topic: str,
33+
name: str,
34+
options: list[str],
35+
*,
36+
entity_category: str | None = None,
37+
enabled: bool = True,
38+
value_template: str = "{{ value }}",
39+
command_template: str = "{{ value }}",
40+
icon: str | None = None,
41+
custom_availability: HaCustomAvailabilityConfig | None = None,
42+
) -> str:
43+
payload = {
44+
"state_topic": self._get_state_topic(topic),
45+
"command_topic": self._get_command_topic(topic),
46+
"value_template": value_template,
47+
"command_template": command_template,
48+
"options": options,
49+
"enabled_by_default": enabled,
50+
}
51+
if entity_category is not None:
52+
payload["entity_category"] = entity_category
53+
if icon is not None:
54+
payload["icon"] = icon
55+
56+
return self._publish_ha_discovery_message(
57+
"select", name, payload, custom_availability
58+
)
59+
60+
def _publish_text(
61+
self,
62+
topic: str,
63+
name: str,
64+
enabled: bool = True,
65+
icon: str | None = None,
66+
value_template: str = "{{ value }}",
67+
command_template: str = "{{ value }}",
68+
retain: bool = False,
69+
min_value: int | None = None,
70+
max_value: int | None = None,
71+
pattern: str | None = None,
72+
custom_availability: HaCustomAvailabilityConfig | None = None,
73+
) -> str:
74+
payload: dict[str, Any] = {
75+
"state_topic": self._get_state_topic(topic),
76+
"command_topic": self._get_command_topic(topic),
77+
"value_template": value_template,
78+
"command_template": command_template,
79+
"retain": str(retain).lower(),
80+
"enabled_by_default": enabled,
81+
}
82+
if min_value is not None:
83+
payload["min"] = min_value
84+
if max_value is not None:
85+
payload["max"] = max_value
86+
if pattern is not None:
87+
payload["pattern"] = pattern
88+
if icon is not None:
89+
payload["icon"] = icon
90+
91+
return self._publish_ha_discovery_message(
92+
"text", name, payload, custom_availability
93+
)
94+
95+
def _publish_binary_sensor(
96+
self,
97+
topic: str,
98+
name: str,
99+
enabled: bool = True,
100+
device_class: str | None = None,
101+
value_template: str = "{{ value }}",
102+
payload_on: str = "True",
103+
payload_off: str = "False",
104+
icon: str | None = None,
105+
custom_availability: HaCustomAvailabilityConfig | None = None,
106+
) -> str:
107+
payload = {
108+
"state_topic": self._get_state_topic(topic),
109+
"value_template": value_template,
110+
"payload_on": payload_on,
111+
"payload_off": payload_off,
112+
"enabled_by_default": enabled,
113+
}
114+
if device_class is not None:
115+
payload["device_class"] = device_class
116+
if icon is not None:
117+
payload["icon"] = icon
118+
119+
return self._publish_ha_discovery_message(
120+
"binary_sensor", name, payload, custom_availability
121+
)
122+
123+
def _publish_number(
124+
self,
125+
topic: str,
126+
name: str,
127+
enabled: bool = True,
128+
entity_category: str | None = None,
129+
device_class: str | None = None,
130+
state_class: str | None = None,
131+
unit_of_measurement: str | None = None,
132+
icon: str | None = None,
133+
value_template: str = "{{ value }}",
134+
retain: bool = False,
135+
mode: str = "auto",
136+
min_value: float = 1.0,
137+
max_value: float = 100.0,
138+
step: float = 1.0,
139+
custom_availability: HaCustomAvailabilityConfig | None = None,
140+
) -> str:
141+
payload = {
142+
"state_topic": self._get_state_topic(topic),
143+
"command_topic": self._get_command_topic(topic),
144+
"value_template": value_template,
145+
"retain": str(retain).lower(),
146+
"mode": mode,
147+
"min": min_value,
148+
"max": max_value,
149+
"step": step,
150+
"enabled_by_default": enabled,
151+
}
152+
if entity_category is not None:
153+
payload["entity_category"] = entity_category
154+
if device_class is not None:
155+
payload["device_class"] = device_class
156+
if state_class is not None:
157+
payload["state_class"] = state_class
158+
if unit_of_measurement is not None:
159+
payload["unit_of_measurement"] = unit_of_measurement
160+
if icon is not None:
161+
payload["icon"] = icon
162+
163+
return self._publish_ha_discovery_message(
164+
"number", name, payload, custom_availability
165+
)
166+
167+
def _publish_switch(
168+
self,
169+
topic: str,
170+
name: str,
171+
*,
172+
enabled: bool = True,
173+
icon: str | None = None,
174+
value_template: str = "{{ value }}",
175+
payload_on: str = "True",
176+
payload_off: str = "False",
177+
custom_availability: HaCustomAvailabilityConfig | None = None,
178+
) -> str:
179+
payload = {
180+
"state_topic": self._get_state_topic(topic),
181+
"command_topic": self._get_command_topic(topic),
182+
"value_template": value_template,
183+
"payload_on": payload_on,
184+
"payload_off": payload_off,
185+
"optimistic": False,
186+
"qos": 0,
187+
"enabled_by_default": enabled,
188+
}
189+
if icon is not None:
190+
payload["icon"] = icon
191+
return self._publish_ha_discovery_message(
192+
"switch", name, payload, custom_availability
193+
)
194+
195+
def _publish_lock(
196+
self,
197+
topic: str,
198+
name: str,
199+
enabled: bool = True,
200+
icon: str | None = None,
201+
payload_lock: str = "True",
202+
payload_unlock: str = "False",
203+
state_locked: str = "True",
204+
state_unlocked: str = "False",
205+
custom_availability: HaCustomAvailabilityConfig | None = None,
206+
) -> str:
207+
payload = {
208+
"state_topic": self._get_state_topic(topic),
209+
"command_topic": self._get_command_topic(topic),
210+
"payload_lock": payload_lock,
211+
"payload_unlock": payload_unlock,
212+
"state_locked": state_locked,
213+
"state_unlocked": state_unlocked,
214+
"optimistic": False,
215+
"qos": 0,
216+
"enabled_by_default": enabled,
217+
}
218+
if icon is not None:
219+
payload["icon"] = icon
220+
return self._publish_ha_discovery_message(
221+
"lock", name, payload, custom_availability
222+
)
223+
224+
def _publish_sensor(
225+
self,
226+
topic: str,
227+
name: str,
228+
enabled: bool = True,
229+
entity_category: str | None = None,
230+
device_class: str | None = None,
231+
state_class: str | None = None,
232+
unit_of_measurement: str | None = None,
233+
icon: str | None = None,
234+
value_template: str = "{{ value }}",
235+
custom_availability: HaCustomAvailabilityConfig | None = None,
236+
) -> str:
237+
payload = {
238+
"state_topic": self._get_state_topic(topic),
239+
"value_template": value_template,
240+
"enabled_by_default": enabled,
241+
}
242+
if entity_category is not None:
243+
payload["entity_category"] = entity_category
244+
if device_class is not None:
245+
payload["device_class"] = device_class
246+
if state_class is not None:
247+
payload["state_class"] = state_class
248+
if unit_of_measurement is not None:
249+
payload["unit_of_measurement"] = unit_of_measurement
250+
if icon is not None:
251+
payload["icon"] = icon
252+
253+
return self._publish_ha_discovery_message(
254+
"sensor", name, payload, custom_availability
255+
)

0 commit comments

Comments
 (0)