Commit ·
a88f884
1
Parent(s): cd59e9e
v0.2.17: Remove head_joints/passive_joints, move error_message to diagnostic
Browse files- Remove Phase 13-14 entities (head_joints, passive_joints)
- Remove get_head_joints_json() and get_passive_joints_json() from reachy_controller
- Move error_message from Phase 1 to Phase 6 (Diagnostic)
- Add entity_category=2 (diagnostic) to all Phase 6 entities
pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "reachy_mini_ha_voice"
|
| 7 |
-
version = "0.2.
|
| 8 |
description = "Home Assistant Voice Assistant for Reachy Mini"
|
| 9 |
readme = "README.md"
|
| 10 |
requires-python = ">=3.10"
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "reachy_mini_ha_voice"
|
| 7 |
+
version = "0.2.17"
|
| 8 |
description = "Home Assistant Voice Assistant for Reachy Mini"
|
| 9 |
readme = "README.md"
|
| 10 |
requires-python = ">=3.10"
|
reachy_mini_ha_voice/__init__.py
CHANGED
|
@@ -11,7 +11,7 @@ Key features:
|
|
| 11 |
- Reachy Mini motion control integration
|
| 12 |
"""
|
| 13 |
|
| 14 |
-
__version__ = "0.2.
|
| 15 |
__author__ = "Desmond Dong"
|
| 16 |
|
| 17 |
# Don't import main module here to avoid runpy warning
|
|
|
|
| 11 |
- Reachy Mini motion control integration
|
| 12 |
"""
|
| 13 |
|
| 14 |
+
__version__ = "0.2.17"
|
| 15 |
__author__ = "Desmond Dong"
|
| 16 |
|
| 17 |
# Don't import main module here to avoid runpy warning
|
reachy_mini_ha_voice/entity_registry.py
CHANGED
|
@@ -25,7 +25,6 @@ ENTITY_KEYS: Dict[str, int] = {
|
|
| 25 |
# Phase 1: Basic status and volume
|
| 26 |
"daemon_state": 100,
|
| 27 |
"backend_ready": 101,
|
| 28 |
-
"error_message": 102,
|
| 29 |
"speaker_volume": 103,
|
| 30 |
# Phase 2: Motor control
|
| 31 |
"motors_enabled": 200,
|
|
@@ -53,6 +52,7 @@ ENTITY_KEYS: Dict[str, int] = {
|
|
| 53 |
"wireless_version": 603,
|
| 54 |
"simulation_mode": 604,
|
| 55 |
"wlan_ip": 605,
|
|
|
|
| 56 |
# Phase 7: IMU sensors
|
| 57 |
"imu_accel_x": 700,
|
| 58 |
"imu_accel_y": 701,
|
|
@@ -79,10 +79,6 @@ ENTITY_KEYS: Dict[str, int] = {
|
|
| 79 |
"agc_max_gain": 1201,
|
| 80 |
"noise_suppression": 1202,
|
| 81 |
"echo_cancellation_converged": 1203,
|
| 82 |
-
# Phase 13: Robot joints (single JSON sensor)
|
| 83 |
-
"head_joints": 1300,
|
| 84 |
-
# Phase 14: Passive joints for 3D visualization
|
| 85 |
-
"passive_joints": 1400,
|
| 86 |
}
|
| 87 |
|
| 88 |
|
|
@@ -148,7 +144,7 @@ class EntityRegistry:
|
|
| 148 |
self._setup_phase10_entities(entities)
|
| 149 |
# Phase 11 (LED control) disabled - LEDs are inside the robot and not visible
|
| 150 |
self._setup_phase12_entities(entities)
|
| 151 |
-
|
| 152 |
|
| 153 |
_LOGGER.info("All entities registered: %d total", len(entities))
|
| 154 |
|
|
@@ -175,15 +171,6 @@ class EntityRegistry:
|
|
| 175 |
value_getter=rc.get_backend_ready,
|
| 176 |
))
|
| 177 |
|
| 178 |
-
entities.append(TextSensorEntity(
|
| 179 |
-
server=self.server,
|
| 180 |
-
key=get_entity_key("error_message"),
|
| 181 |
-
name="Error Message",
|
| 182 |
-
object_id="error_message",
|
| 183 |
-
icon="mdi:alert-circle",
|
| 184 |
-
value_getter=rc.get_error_message,
|
| 185 |
-
))
|
| 186 |
-
|
| 187 |
entities.append(NumberEntity(
|
| 188 |
server=self.server,
|
| 189 |
key=get_entity_key("speaker_volume"),
|
|
@@ -200,7 +187,7 @@ class EntityRegistry:
|
|
| 200 |
value_setter=rc.set_speaker_volume,
|
| 201 |
))
|
| 202 |
|
| 203 |
-
_LOGGER.debug("Phase 1 entities registered: daemon_state, backend_ready,
|
| 204 |
|
| 205 |
def _setup_phase2_entities(self, entities: List) -> None:
|
| 206 |
"""Setup Phase 2 entities: Motor control."""
|
|
@@ -448,6 +435,7 @@ class EntityRegistry:
|
|
| 448 |
unit_of_measurement="Hz",
|
| 449 |
accuracy_decimals=1,
|
| 450 |
state_class="measurement",
|
|
|
|
| 451 |
value_getter=rc.get_control_loop_frequency,
|
| 452 |
))
|
| 453 |
|
|
@@ -457,6 +445,7 @@ class EntityRegistry:
|
|
| 457 |
name="SDK Version",
|
| 458 |
object_id="sdk_version",
|
| 459 |
icon="mdi:information",
|
|
|
|
| 460 |
value_getter=rc.get_sdk_version,
|
| 461 |
))
|
| 462 |
|
|
@@ -466,6 +455,7 @@ class EntityRegistry:
|
|
| 466 |
name="Robot Name",
|
| 467 |
object_id="robot_name",
|
| 468 |
icon="mdi:robot",
|
|
|
|
| 469 |
value_getter=rc.get_robot_name,
|
| 470 |
))
|
| 471 |
|
|
@@ -476,6 +466,7 @@ class EntityRegistry:
|
|
| 476 |
object_id="wireless_version",
|
| 477 |
icon="mdi:wifi",
|
| 478 |
device_class="connectivity",
|
|
|
|
| 479 |
value_getter=rc.get_wireless_version,
|
| 480 |
))
|
| 481 |
|
|
@@ -485,6 +476,7 @@ class EntityRegistry:
|
|
| 485 |
name="Simulation Mode",
|
| 486 |
object_id="simulation_mode",
|
| 487 |
icon="mdi:virtual-reality",
|
|
|
|
| 488 |
value_getter=rc.get_simulation_mode,
|
| 489 |
))
|
| 490 |
|
|
@@ -494,10 +486,21 @@ class EntityRegistry:
|
|
| 494 |
name="WLAN IP",
|
| 495 |
object_id="wlan_ip",
|
| 496 |
icon="mdi:ip-network",
|
|
|
|
| 497 |
value_getter=rc.get_wlan_ip,
|
| 498 |
))
|
| 499 |
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
|
| 502 |
def _setup_phase7_entities(self, entities: List) -> None:
|
| 503 |
"""Setup Phase 7 entities: IMU sensors (wireless only)."""
|
|
@@ -723,32 +726,6 @@ class EntityRegistry:
|
|
| 723 |
|
| 724 |
_LOGGER.debug("Phase 12 entities registered: agc_enabled, agc_max_gain, noise_suppression, echo_cancellation_converged")
|
| 725 |
|
| 726 |
-
def _setup_phase13_entities(self, entities: List) -> None:
|
| 727 |
-
"""Setup Phase 13 entities: Robot joints as JSON sensor."""
|
| 728 |
-
rc = self.reachy_controller
|
| 729 |
-
|
| 730 |
-
entities.append(TextSensorEntity(
|
| 731 |
-
server=self.server,
|
| 732 |
-
key=get_entity_key("head_joints"),
|
| 733 |
-
name="Head Joints",
|
| 734 |
-
object_id="head_joints",
|
| 735 |
-
icon="mdi:robot",
|
| 736 |
-
value_getter=rc.get_head_joints_json,
|
| 737 |
-
))
|
| 738 |
-
|
| 739 |
-
_LOGGER.debug("Phase 13 entities registered: head_joints")
|
| 740 |
-
|
| 741 |
-
# Phase 14: Passive joints for 3D visualization
|
| 742 |
-
entities.append(TextSensorEntity(
|
| 743 |
-
server=self.server,
|
| 744 |
-
key=get_entity_key("passive_joints"),
|
| 745 |
-
name="Passive Joints",
|
| 746 |
-
object_id="passive_joints",
|
| 747 |
-
value_getter=rc.get_passive_joints_json,
|
| 748 |
-
))
|
| 749 |
-
|
| 750 |
-
_LOGGER.debug("Phase 14 entities registered: passive_joints")
|
| 751 |
-
|
| 752 |
def find_entity_references(self, entities: List) -> None:
|
| 753 |
"""Find and store references to special entities from existing list.
|
| 754 |
|
|
|
|
| 25 |
# Phase 1: Basic status and volume
|
| 26 |
"daemon_state": 100,
|
| 27 |
"backend_ready": 101,
|
|
|
|
| 28 |
"speaker_volume": 103,
|
| 29 |
# Phase 2: Motor control
|
| 30 |
"motors_enabled": 200,
|
|
|
|
| 52 |
"wireless_version": 603,
|
| 53 |
"simulation_mode": 604,
|
| 54 |
"wlan_ip": 605,
|
| 55 |
+
"error_message": 606, # Moved to diagnostic
|
| 56 |
# Phase 7: IMU sensors
|
| 57 |
"imu_accel_x": 700,
|
| 58 |
"imu_accel_y": 701,
|
|
|
|
| 79 |
"agc_max_gain": 1201,
|
| 80 |
"noise_suppression": 1202,
|
| 81 |
"echo_cancellation_converged": 1203,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
|
|
|
|
| 144 |
self._setup_phase10_entities(entities)
|
| 145 |
# Phase 11 (LED control) disabled - LEDs are inside the robot and not visible
|
| 146 |
self._setup_phase12_entities(entities)
|
| 147 |
+
# Phase 13-14 (head_joints, passive_joints) removed - not needed
|
| 148 |
|
| 149 |
_LOGGER.info("All entities registered: %d total", len(entities))
|
| 150 |
|
|
|
|
| 171 |
value_getter=rc.get_backend_ready,
|
| 172 |
))
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
entities.append(NumberEntity(
|
| 175 |
server=self.server,
|
| 176 |
key=get_entity_key("speaker_volume"),
|
|
|
|
| 187 |
value_setter=rc.set_speaker_volume,
|
| 188 |
))
|
| 189 |
|
| 190 |
+
_LOGGER.debug("Phase 1 entities registered: daemon_state, backend_ready, speaker_volume")
|
| 191 |
|
| 192 |
def _setup_phase2_entities(self, entities: List) -> None:
|
| 193 |
"""Setup Phase 2 entities: Motor control."""
|
|
|
|
| 435 |
unit_of_measurement="Hz",
|
| 436 |
accuracy_decimals=1,
|
| 437 |
state_class="measurement",
|
| 438 |
+
entity_category=2, # diagnostic
|
| 439 |
value_getter=rc.get_control_loop_frequency,
|
| 440 |
))
|
| 441 |
|
|
|
|
| 445 |
name="SDK Version",
|
| 446 |
object_id="sdk_version",
|
| 447 |
icon="mdi:information",
|
| 448 |
+
entity_category=2, # diagnostic
|
| 449 |
value_getter=rc.get_sdk_version,
|
| 450 |
))
|
| 451 |
|
|
|
|
| 455 |
name="Robot Name",
|
| 456 |
object_id="robot_name",
|
| 457 |
icon="mdi:robot",
|
| 458 |
+
entity_category=2, # diagnostic
|
| 459 |
value_getter=rc.get_robot_name,
|
| 460 |
))
|
| 461 |
|
|
|
|
| 466 |
object_id="wireless_version",
|
| 467 |
icon="mdi:wifi",
|
| 468 |
device_class="connectivity",
|
| 469 |
+
entity_category=2, # diagnostic
|
| 470 |
value_getter=rc.get_wireless_version,
|
| 471 |
))
|
| 472 |
|
|
|
|
| 476 |
name="Simulation Mode",
|
| 477 |
object_id="simulation_mode",
|
| 478 |
icon="mdi:virtual-reality",
|
| 479 |
+
entity_category=2, # diagnostic
|
| 480 |
value_getter=rc.get_simulation_mode,
|
| 481 |
))
|
| 482 |
|
|
|
|
| 486 |
name="WLAN IP",
|
| 487 |
object_id="wlan_ip",
|
| 488 |
icon="mdi:ip-network",
|
| 489 |
+
entity_category=2, # diagnostic
|
| 490 |
value_getter=rc.get_wlan_ip,
|
| 491 |
))
|
| 492 |
|
| 493 |
+
entities.append(TextSensorEntity(
|
| 494 |
+
server=self.server,
|
| 495 |
+
key=get_entity_key("error_message"),
|
| 496 |
+
name="Error Message",
|
| 497 |
+
object_id="error_message",
|
| 498 |
+
icon="mdi:alert-circle",
|
| 499 |
+
entity_category=2, # diagnostic
|
| 500 |
+
value_getter=rc.get_error_message,
|
| 501 |
+
))
|
| 502 |
+
|
| 503 |
+
_LOGGER.debug("Phase 6 entities registered: control_loop_frequency, sdk_version, robot_name, wireless_version, simulation_mode, wlan_ip, error_message")
|
| 504 |
|
| 505 |
def _setup_phase7_entities(self, entities: List) -> None:
|
| 506 |
"""Setup Phase 7 entities: IMU sensors (wireless only)."""
|
|
|
|
| 726 |
|
| 727 |
_LOGGER.debug("Phase 12 entities registered: agc_enabled, agc_max_gain, noise_suppression, echo_cancellation_converged")
|
| 728 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
def find_entity_references(self, entities: List) -> None:
|
| 730 |
"""Find and store references to special entities from existing list.
|
| 731 |
|
reachy_mini_ha_voice/reachy_controller.py
CHANGED
|
@@ -1120,57 +1120,3 @@ class ReachyController:
|
|
| 1120 |
except Exception as e:
|
| 1121 |
logger.debug(f"Error getting AEC converged status: {e}")
|
| 1122 |
return False
|
| 1123 |
-
|
| 1124 |
-
# ========== Phase 13: Robot Joints ==========
|
| 1125 |
-
|
| 1126 |
-
def get_head_joints_json(self) -> str:
|
| 1127 |
-
"""
|
| 1128 |
-
Get head joints as JSON string.
|
| 1129 |
-
|
| 1130 |
-
Returns:
|
| 1131 |
-
JSON string: "[yaw_body, stewart_1, stewart_2, stewart_3, stewart_4, stewart_5, stewart_6]"
|
| 1132 |
-
Values in radians
|
| 1133 |
-
"""
|
| 1134 |
-
if not self.is_available:
|
| 1135 |
-
return "[]"
|
| 1136 |
-
try:
|
| 1137 |
-
import json
|
| 1138 |
-
head_joints, _ = self.reachy.get_current_joint_positions()
|
| 1139 |
-
if head_joints and len(head_joints) >= 7:
|
| 1140 |
-
# Convert radians to list
|
| 1141 |
-
joints_list = [float(j) for j in head_joints[:7]]
|
| 1142 |
-
return json.dumps(joints_list)
|
| 1143 |
-
return "[]"
|
| 1144 |
-
except Exception as e:
|
| 1145 |
-
logger.error(f"Error getting head joints JSON: {e}")
|
| 1146 |
-
return "[]"
|
| 1147 |
-
|
| 1148 |
-
def get_passive_joints_json(self) -> str:
|
| 1149 |
-
"""
|
| 1150 |
-
Get passive joints as JSON string with cached status.
|
| 1151 |
-
|
| 1152 |
-
Returns:
|
| 1153 |
-
JSON string: "[passive_1_x, passive_1_y, passive_1_z, ..., passive_7_z]"
|
| 1154 |
-
Values in radians (21 values total)
|
| 1155 |
-
"""
|
| 1156 |
-
if not self.is_available:
|
| 1157 |
-
return "[]"
|
| 1158 |
-
try:
|
| 1159 |
-
import json
|
| 1160 |
-
# Get WLAN IP from cached daemon status
|
| 1161 |
-
status = self._get_cached_status()
|
| 1162 |
-
if status is None:
|
| 1163 |
-
return "[]"
|
| 1164 |
-
wlan_ip = status.get('wlan_ip', 'localhost')
|
| 1165 |
-
# Call the backend API to get passive joints
|
| 1166 |
-
backend_url = f"http://{wlan_ip}:8000/api/state/full?with_passive_joints=true"
|
| 1167 |
-
response = requests.get(backend_url, timeout=0.5)
|
| 1168 |
-
if response.status_code == 200:
|
| 1169 |
-
data = response.json()
|
| 1170 |
-
passive_joints = data.get("passive_joints")
|
| 1171 |
-
if passive_joints and len(passive_joints) >= 21:
|
| 1172 |
-
return json.dumps(passive_joints)
|
| 1173 |
-
return "[]"
|
| 1174 |
-
except Exception as e:
|
| 1175 |
-
logger.error(f"Error getting passive joints JSON: {e}")
|
| 1176 |
-
return "[]"
|
|
|
|
| 1120 |
except Exception as e:
|
| 1121 |
logger.debug(f"Error getting AEC converged status: {e}")
|
| 1122 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|