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
|
| 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
|
| 1231 |
|
| 1232 |
-
# Camera
|
| 1233 |
-
def
|
| 1234 |
-
"""Get camera
|
| 1235 |
if self.camera_server:
|
| 1236 |
-
|
| 1237 |
-
|
| 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 |
-
|
|
|
|
| 1243 |
server=self,
|
| 1244 |
-
key=self._get_entity_key("
|
| 1245 |
-
name="Camera
|
| 1246 |
-
object_id="
|
| 1247 |
icon="mdi:camera",
|
| 1248 |
-
|
| 1249 |
)
|
| 1250 |
-
self.state.entities.append(
|
| 1251 |
|
| 1252 |
-
_LOGGER.info("Phase 10 entities registered:
|
| 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)."""
|