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

"feat-camera-use-esphome-camera-entity-for-real-preview"

Browse files
reachy_mini_ha_voice/entity.py CHANGED
@@ -172,6 +172,7 @@ class TextSensorEntity(ESPHomeEntity):
172
  name: str,
173
  object_id: str,
174
  icon: str = "",
 
175
  value_getter: Optional[Callable[[], str]] = None,
176
  ) -> None:
177
  ESPHomeEntity.__init__(self, server)
@@ -179,6 +180,7 @@ class TextSensorEntity(ESPHomeEntity):
179
  self.name = name
180
  self.object_id = object_id
181
  self.icon = icon
 
182
  self._value_getter = value_getter
183
  self._value = ""
184
 
@@ -199,6 +201,7 @@ class TextSensorEntity(ESPHomeEntity):
199
  key=self.key,
200
  name=self.name,
201
  icon=self.icon,
 
202
  )
203
  elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
204
  yield self._get_state_message()
 
172
  name: str,
173
  object_id: str,
174
  icon: str = "",
175
+ entity_category: int = 0, # 0 = none, 1 = config, 2 = diagnostic
176
  value_getter: Optional[Callable[[], str]] = None,
177
  ) -> None:
178
  ESPHomeEntity.__init__(self, server)
 
180
  self.name = name
181
  self.object_id = object_id
182
  self.icon = icon
183
+ self.entity_category = entity_category
184
  self._value_getter = value_getter
185
  self._value = ""
186
 
 
201
  key=self.key,
202
  name=self.name,
203
  icon=self.icon,
204
+ entity_category=self.entity_category,
205
  )
206
  elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
207
  yield self._get_state_message()
reachy_mini_ha_voice/entity_extensions.py CHANGED
@@ -49,6 +49,7 @@ class SensorEntity(ESPHomeEntity):
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)
@@ -59,6 +60,7 @@ class SensorEntity(ESPHomeEntity):
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 = {
@@ -94,6 +96,7 @@ class SensorEntity(ESPHomeEntity):
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()
@@ -186,6 +189,7 @@ class SelectEntity(ESPHomeEntity):
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:
@@ -195,6 +199,7 @@ class SelectEntity(ESPHomeEntity):
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 ""
@@ -222,6 +227,7 @@ class SelectEntity(ESPHomeEntity):
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()
@@ -252,6 +258,7 @@ class ButtonEntity(ESPHomeEntity):
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)
@@ -260,6 +267,7 @@ class ButtonEntity(ESPHomeEntity):
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]:
@@ -270,6 +278,7 @@ class ButtonEntity(ESPHomeEntity):
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:
 
49
  accuracy_decimals: int = 2,
50
  device_class: str = "",
51
  state_class: int = SensorStateClass.NONE,
52
+ entity_category: int = 0, # 0 = none, 1 = config, 2 = diagnostic
53
  value_getter: Optional[Callable[[], float]] = None,
54
  ) -> None:
55
  ESPHomeEntity.__init__(self, server)
 
60
  self.unit_of_measurement = unit_of_measurement
61
  self.accuracy_decimals = accuracy_decimals
62
  self.device_class = device_class
63
+ self.entity_category = entity_category
64
  # Convert string state_class to int if needed (for backward compatibility)
65
  if isinstance(state_class, str):
66
  state_class_map = {
 
96
  accuracy_decimals=self.accuracy_decimals,
97
  device_class=self.device_class,
98
  state_class=self.state_class,
99
+ entity_category=self.entity_category,
100
  )
101
  elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
102
  yield self._get_state_message()
 
189
  object_id: str,
190
  options: List[str],
191
  icon: str = "",
192
+ entity_category: int = 0, # 0 = none, 1 = config, 2 = diagnostic
193
  value_getter: Optional[Callable[[], str]] = None,
194
  value_setter: Optional[Callable[[str], None]] = None,
195
  ) -> None:
 
199
  self.object_id = object_id
200
  self.options = options
201
  self.icon = icon
202
+ self.entity_category = entity_category
203
  self._value_getter = value_getter
204
  self._value_setter = value_setter
205
  self._value = options[0] if options else ""
 
227
  name=self.name,
228
  icon=self.icon,
229
  options=self.options,
230
+ entity_category=self.entity_category,
231
  )
232
  elif isinstance(msg, (SubscribeHomeAssistantStatesRequest, SubscribeStatesRequest)):
233
  yield self._get_state_message()
 
258
  object_id: str,
259
  icon: str = "",
260
  device_class: str = "",
261
+ entity_category: int = 0, # 0 = none, 1 = config, 2 = diagnostic
262
  on_press: Optional[Callable[[], None]] = None,
263
  ) -> None:
264
  ESPHomeEntity.__init__(self, server)
 
267
  self.object_id = object_id
268
  self.icon = icon
269
  self.device_class = device_class
270
+ self.entity_category = entity_category
271
  self._on_press = on_press
272
 
273
  def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
 
278
  name=self.name,
279
  icon=self.icon,
280
  device_class=self.device_class,
281
+ entity_category=self.entity_category,
282
  )
283
  elif isinstance(msg, ButtonCommandRequest) and msg.key == self.key:
284
  if self._on_press:
reachy_mini_ha_voice/satellite.py CHANGED
@@ -111,8 +111,9 @@ class VoiceSatelliteProtocol(APIServer):
111
  "emotion": 800,
112
  # Phase 9: Audio controls
113
  "microphone_volume": 900,
114
- # Phase 10: Camera URL
115
- "camera_url": 1000,
 
116
  # Phase 11: LED control (disabled - not visible)
117
  # "led_brightness": 1100,
118
  # "led_effect": 1101,
@@ -1227,29 +1228,27 @@ class VoiceSatelliteProtocol(APIServer):
1227
  _LOGGER.info("Phase 9 entities registered: microphone_volume")
1228
 
1229
  def _setup_phase10_entities(self) -> None:
1230
- """Setup Phase 10 entities: Camera URL for Generic Camera integration."""
1231
 
1232
- # Camera stream URL - users can use this with Generic Camera in HA
1233
- def get_camera_url() -> str:
1234
- """Get camera stream URL."""
1235
  if self.camera_server:
1236
- # Get WLAN IP from reachy controller
1237
- wlan_ip = self.reachy_controller.get_wlan_ip()
1238
- if wlan_ip and wlan_ip != "N/A":
1239
- return f"http://{wlan_ip}:{self.camera_server.port}/stream"
1240
- return "N/A"
1241
 
1242
- camera_url = TextSensorEntity(
 
1243
  server=self,
1244
- key=self._get_entity_key("camera_url"),
1245
- name="Camera Stream URL",
1246
- object_id="camera_url",
1247
  icon="mdi:camera",
1248
- value_getter=get_camera_url,
1249
  )
1250
- self.state.entities.append(camera_url)
1251
 
1252
- _LOGGER.info("Phase 10 entities registered: camera_url (use with Generic Camera in HA)")
1253
 
1254
  def _setup_phase11_entities(self) -> None:
1255
  """Setup Phase 11 entities: LED control (via local SDK)."""
 
111
  "emotion": 800,
112
  # Phase 9: Audio controls
113
  "microphone_volume": 900,
114
+ # Phase 10: Camera
115
+ "camera_url": 1000, # Keep for backward compatibility
116
+ "camera": 1001, # New camera entity
117
  # Phase 11: LED control (disabled - not visible)
118
  # "led_brightness": 1100,
119
  # "led_effect": 1101,
 
1228
  _LOGGER.info("Phase 9 entities registered: microphone_volume")
1229
 
1230
  def _setup_phase10_entities(self) -> None:
1231
+ """Setup Phase 10 entities: Camera for Home Assistant integration."""
1232
 
1233
+ # Camera image getter - returns JPEG bytes from camera server
1234
+ def get_camera_image() -> Optional[bytes]:
1235
+ """Get camera snapshot as JPEG bytes."""
1236
  if self.camera_server:
1237
+ return self.camera_server.get_snapshot()
1238
+ return None
 
 
 
1239
 
1240
+ # Real camera entity - shows preview in Home Assistant
1241
+ camera_entity = CameraEntity(
1242
  server=self,
1243
+ key=self._get_entity_key("camera"), # Use new camera key
1244
+ name="Camera",
1245
+ object_id="camera",
1246
  icon="mdi:camera",
1247
+ image_getter=get_camera_image,
1248
  )
1249
+ self.state.entities.append(camera_entity)
1250
 
1251
+ _LOGGER.info("Phase 10 entities registered: camera (ESPHome Camera entity)")
1252
 
1253
  def _setup_phase11_entities(self) -> None:
1254
  """Setup Phase 11 entities: LED control (via local SDK)."""