Desmond-Dong commited on
Commit
424cea2
·
1 Parent(s): 3825bae

"fix-camera-add-CameraImageRequest-to-message-handler"

Browse files
old_entity_extensions.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Extended ESPHome entity types for Reachy Mini control."""
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Callable, List, Optional
5
+ import logging
6
+
7
+ from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
8
+ ListEntitiesButtonResponse,
9
+ ListEntitiesRequest,
10
+ ListEntitiesSelectResponse,
11
+ ListEntitiesSensorResponse,
12
+ ListEntitiesSwitchResponse,
13
+ ButtonCommandRequest,
14
+ SelectCommandRequest,
15
+ SelectStateResponse,
16
+ SensorStateResponse,
17
+ SubscribeHomeAssistantStatesRequest,
18
+ SubscribeStatesRequest,
19
+ SwitchCommandRequest,
20
+ SwitchStateResponse,
21
+ )
22
+ from google.protobuf import message
23
+
24
+ from .api_server import APIServer
25
+ from .entity import ESPHomeEntity
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class SensorStateClass:
31
+ """ESPHome SensorStateClass enum values."""
32
+ NONE = 0
33
+ MEASUREMENT = 1
34
+ TOTAL_INCREASING = 2
35
+ TOTAL = 3
36
+
37
+
38
+ class SensorEntity(ESPHomeEntity):
39
+ """Sensor entity for ESPHome (read-only numeric values)."""
40
+
41
+ def __init__(
42
+ self,
43
+ server: APIServer,
44
+ key: int,
45
+ name: str,
46
+ object_id: str,
47
+ icon: str = "",
48
+ unit_of_measurement: str = "",
49
+ accuracy_decimals: int = 2,
50
+ device_class: str = "",
51
+ state_class: int = SensorStateClass.NONE,
52
+ value_getter: Optional[Callable[[], float]] = None,
53
+ ) -> None:
54
+ ESPHomeEntity.__init__(self, server)
55
+ self.key = key
56
+ self.name = name
57
+ self.object_id = object_id
58
+ self.icon = icon
59
+ self.unit_of_measurement = unit_of_measurement
60
+ self.accuracy_decimals = accuracy_decimals
61
+ self.device_class = device_class
62
+ # Convert string state_class to int if needed (for backward compatibility)
63
+ if isinstance(state_class, str):
64
+ state_class_map = {
65
+ "": SensorStateClass.NONE,
66
+ "measurement": SensorStateClass.MEASUREMENT,
67
+ "total_increasing": SensorStateClass.TOTAL_INCREASING,
68
+ "total": SensorStateClass.TOTAL,
69
+ }
70
+ self.state_class = state_class_map.get(state_class.lower(), SensorStateClass.NONE)
71
+ else:
72
+ self.state_class = state_class
73
+ self._value_getter = value_getter
74
+ self._value = 0.0
75
+
76
+ @property
77
+ def value(self) -> float:
78
+ if self._value_getter:
79
+ return self._value_getter()
80
+ return self._value
81
+
82
+ @value.setter
83
+ def value(self, new_value: float) -> None:
84
+ self._value = new_value
85
+
86
+ def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
87
+ if isinstance(msg, ListEntitiesRequest):
88
+ yield ListEntitiesSensorResponse(
89
+ object_id=self.object_id,
90
+ key=self.key,
91
+ name=self.name,
92
+ icon=self.icon,
93
+ unit_of_measurement=self.unit_of_measurement,
94
+ accuracy_decimals=self.accuracy_decimals,
95
+ device_class=self.device_class,
96
+ state_class=self.state_class,
97
+ )
98
+ elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
99
+ yield self._get_state_message()
100
+
101
+ def _get_state_message(self) -> SensorStateResponse:
102
+ return SensorStateResponse(
103
+ key=self.key,
104
+ state=self.value,
105
+ missing_state=False,
106
+ )
107
+
108
+ def update_state(self) -> None:
109
+ """Send state update to Home Assistant."""
110
+ self.server.send_messages([self._get_state_message()])
111
+
112
+
113
+ class SwitchEntity(ESPHomeEntity):
114
+ """Switch entity for ESPHome (read-write boolean values)."""
115
+
116
+ def __init__(
117
+ self,
118
+ server: APIServer,
119
+ key: int,
120
+ name: str,
121
+ object_id: str,
122
+ icon: str = "",
123
+ device_class: str = "",
124
+ entity_category: int = 0, # 0 = none, 1 = config, 2 = diagnostic
125
+ value_getter: Optional[Callable[[], bool]] = None,
126
+ value_setter: Optional[Callable[[bool], None]] = None,
127
+ ) -> None:
128
+ ESPHomeEntity.__init__(self, server)
129
+ self.key = key
130
+ self.name = name
131
+ self.object_id = object_id
132
+ self.icon = icon
133
+ self.device_class = device_class
134
+ self.entity_category = entity_category
135
+ self._value_getter = value_getter
136
+ self._value_setter = value_setter
137
+ self._value = False
138
+
139
+ @property
140
+ def value(self) -> bool:
141
+ if self._value_getter:
142
+ return self._value_getter()
143
+ return self._value
144
+
145
+ @value.setter
146
+ def value(self, new_value: bool) -> None:
147
+ if self._value_setter:
148
+ self._value_setter(new_value)
149
+ self._value = new_value
150
+
151
+ def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
152
+ if isinstance(msg, ListEntitiesRequest):
153
+ yield ListEntitiesSwitchResponse(
154
+ object_id=self.object_id,
155
+ key=self.key,
156
+ name=self.name,
157
+ icon=self.icon,
158
+ device_class=self.device_class,
159
+ entity_category=self.entity_category,
160
+ )
161
+ elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
162
+ yield self._get_state_message()
163
+ elif isinstance(msg, SwitchCommandRequest) and msg.key == self.key:
164
+ self.value = msg.state
165
+ yield self._get_state_message()
166
+
167
+ def _get_state_message(self) -> SwitchStateResponse:
168
+ return SwitchStateResponse(
169
+ key=self.key,
170
+ state=self.value,
171
+ )
172
+
173
+ def update_state(self) -> None:
174
+ """Send state update to Home Assistant."""
175
+ self.server.send_messages([self._get_state_message()])
176
+
177
+
178
+ class SelectEntity(ESPHomeEntity):
179
+ """Select entity for ESPHome (read-write string selection)."""
180
+
181
+ def __init__(
182
+ self,
183
+ server: APIServer,
184
+ key: int,
185
+ name: str,
186
+ object_id: str,
187
+ options: List[str],
188
+ icon: str = "",
189
+ value_getter: Optional[Callable[[], str]] = None,
190
+ value_setter: Optional[Callable[[str], None]] = None,
191
+ ) -> None:
192
+ ESPHomeEntity.__init__(self, server)
193
+ self.key = key
194
+ self.name = name
195
+ self.object_id = object_id
196
+ self.options = options
197
+ self.icon = icon
198
+ self._value_getter = value_getter
199
+ self._value_setter = value_setter
200
+ self._value = options[0] if options else ""
201
+
202
+ @property
203
+ def value(self) -> str:
204
+ if self._value_getter:
205
+ return self._value_getter()
206
+ return self._value
207
+
208
+ @value.setter
209
+ def value(self, new_value: str) -> None:
210
+ if new_value in self.options:
211
+ if self._value_setter:
212
+ self._value_setter(new_value)
213
+ self._value = new_value
214
+ else:
215
+ logger.warning(f"Invalid option '{new_value}' for {self.name}")
216
+
217
+ def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
218
+ if isinstance(msg, ListEntitiesRequest):
219
+ yield ListEntitiesSelectResponse(
220
+ object_id=self.object_id,
221
+ key=self.key,
222
+ name=self.name,
223
+ icon=self.icon,
224
+ options=self.options,
225
+ )
226
+ elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
227
+ yield self._get_state_message()
228
+ elif isinstance(msg, SelectCommandRequest) and msg.key == self.key:
229
+ self.value = msg.state
230
+ yield self._get_state_message()
231
+
232
+ def _get_state_message(self) -> SelectStateResponse:
233
+ return SelectStateResponse(
234
+ key=self.key,
235
+ state=self.value,
236
+ missing_state=False,
237
+ )
238
+
239
+ def update_state(self) -> None:
240
+ """Send state update to Home Assistant."""
241
+ self.server.send_messages([self._get_state_message()])
242
+
243
+
244
+ class ButtonEntity(ESPHomeEntity):
245
+ """Button entity for ESPHome (trigger actions)."""
246
+
247
+ def __init__(
248
+ self,
249
+ server: APIServer,
250
+ key: int,
251
+ name: str,
252
+ object_id: str,
253
+ icon: str = "",
254
+ device_class: str = "",
255
+ on_press: Optional[Callable[[], None]] = None,
256
+ ) -> None:
257
+ ESPHomeEntity.__init__(self, server)
258
+ self.key = key
259
+ self.name = name
260
+ self.object_id = object_id
261
+ self.icon = icon
262
+ self.device_class = device_class
263
+ self._on_press = on_press
264
+
265
+ def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
266
+ if isinstance(msg, ListEntitiesRequest):
267
+ yield ListEntitiesButtonResponse(
268
+ object_id=self.object_id,
269
+ key=self.key,
270
+ name=self.name,
271
+ icon=self.icon,
272
+ device_class=self.device_class,
273
+ )
274
+ elif isinstance(msg, ButtonCommandRequest) and msg.key == self.key:
275
+ if self._on_press:
276
+ try:
277
+ self._on_press()
278
+ logger.info(f"Button '{self.name}' pressed")
279
+ except Exception as e:
280
+ logger.error(f"Error executing button '{self.name}': {e}")
281
+ # Buttons don't have state responses
282
+ return
283
+ yield # Make this a generator
reachy_mini_ha_voice/satellite.py CHANGED
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
  # pylint: disable=no-name-in-module
17
  from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
18
  ButtonCommandRequest,
 
19
  DeviceInfoRequest,
20
  DeviceInfoResponse,
21
  ListEntitiesDoneResponse,
@@ -294,6 +295,7 @@ class VoiceSatelliteProtocol(APIServer):
294
  SwitchCommandRequest,
295
  SelectCommandRequest,
296
  ButtonCommandRequest,
 
297
  ),
298
  ):
299
  for entity in self.state.entities:
 
16
  # pylint: disable=no-name-in-module
17
  from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
18
  ButtonCommandRequest,
19
+ CameraImageRequest,
20
  DeviceInfoRequest,
21
  DeviceInfoResponse,
22
  ListEntitiesDoneResponse,
 
295
  SwitchCommandRequest,
296
  SelectCommandRequest,
297
  ButtonCommandRequest,
298
+ CameraImageRequest,
299
  ),
300
  ):
301
  for entity in self.state.entities: