improvements

This commit is contained in:
zzstoatzz 2025-04-12 23:40:22 -05:00
commit 35ca7c65d3

View file

@ -101,36 +101,95 @@ def list_groups() -> list[str]:
@lights_mcp.tool()
def list_scenes() -> list[str]:
"""Lists the names of all available Hue scenes."""
def list_scenes() -> dict[str, list[str]] | list[str]:
"""Lists Hue scenes, grouped by the light group they belong to.
Returns:
dict[str, list[str]]: A dictionary mapping group names to a list of scene names within that group.
list[str]: An error message list if the bridge connection fails or an error occurs.
"""
if not (bridge := _get_bridge()):
return ["Error: Bridge not connected"]
try:
# phue2 get_scene() returns a dict {id: {details}} including name
scenes = bridge.get_scene()
return [scene_details["name"] for scene_details in scenes.values()]
scenes_data = bridge.get_scene() # Returns dict {scene_id: {details...}}
groups_data = bridge.get_group() # Returns dict {group_id: {details...}}
# Create a lookup for group name by group ID
group_id_to_name = {gid: ginfo["name"] for gid, ginfo in groups_data.items()}
scenes_by_group: dict[str, list[str]] = {}
for scene_id, scene_details in scenes_data.items():
scene_name = scene_details.get("name")
# Scenes might be associated with a group via 'group' key or lights
# Using 'group' key if available is more direct for group scenes
group_id = scene_details.get("group")
if scene_name and group_id and group_id in group_id_to_name:
group_name = group_id_to_name[group_id]
if group_name not in scenes_by_group:
scenes_by_group[group_name] = []
# Avoid duplicate scene names within a group listing (though unlikely)
if scene_name not in scenes_by_group[group_name]:
scenes_by_group[group_name].append(scene_name)
# Sort scenes within each group for consistent output
for group_name in scenes_by_group:
scenes_by_group[group_name].sort()
return scenes_by_group
except (PhueException, Exception) as e:
return [f"Error listing scenes: {e}"]
# Return error as list to match other list-returning tools on error
return [f"Error listing scenes by group: {e}"]
@lights_mcp.tool()
def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
"""Activates a specific scene within a specified light group."""
"""Activates a specific scene within a specified light group, verifying the scene belongs to the group."""
if not (bridge := _get_bridge()):
return {"error": "Bridge not connected", "success": False}
try:
# Note: phue2 run_scene uses group_name and scene_name directly
# 1. Find the target group ID
groups_data = bridge.get_group()
target_group_id = None
for gid, ginfo in groups_data.items():
if ginfo.get("name") == group_name:
target_group_id = gid
break
if not target_group_id:
return {"error": f"Group '{group_name}' not found", "success": False}
# 2. Find the target scene and check its group association
scenes_data = bridge.get_scene()
scene_found = False
scene_in_correct_group = False
for sid, sinfo in scenes_data.items():
if sinfo.get("name") == scene_name:
scene_found = True
# Check if this scene is associated with the target group ID
if sinfo.get("group") == target_group_id:
scene_in_correct_group = True
break # Found the scene in the correct group
if not scene_found:
return {"error": f"Scene '{scene_name}' not found", "success": False}
if not scene_in_correct_group:
return {
"error": f"Scene '{scene_name}' does not belong to group '{group_name}'",
"success": False,
}
# 3. Activate the scene (now that we've verified it)
result = bridge.run_scene(group_name=group_name, scene_name=scene_name)
# run_scene returns True on success, we'll make the response richer
if result:
return {
"group": group_name,
"activated_scene": scene_name,
"success": True,
"phue2_result": result, # Include the raw True/False
"phue2_result": result,
}
else:
# This case might indicate the scene/group exists but activation failed
# This case might indicate the scene/group exists but activation failed internally
return {
"group": group_name,
"scene": scene_name,
@ -139,7 +198,7 @@ def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
}
except (KeyError, PhueException, Exception) as e:
# KeyError likely means group or scene name is wrong
# Handle potential errors during bridge communication or data parsing
return handle_phue_error(f"{group_name}/{scene_name}", "activate_scene", e)
@ -193,3 +252,41 @@ def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str
}
except (KeyError, PhueException, ValueError, Exception) as e:
return handle_phue_error(group_name, "set_group_attributes", e)
@lights_mcp.tool()
def list_lights_by_group() -> dict[str, list[str]] | list[str]:
"""Lists Hue lights, grouped by the room/group they belong to.
Returns:
dict[str, list[str]]: A dictionary mapping group names to a list of light names within that group.
list[str]: An error message list if the bridge connection fails or an error occurs.
"""
if not (bridge := _get_bridge()):
return ["Error: Bridge not connected"]
try:
groups_data = bridge.get_group() # dict {group_id: {details}}
lights_data = bridge.get_light_objects("id") # dict {light_id: {details}}
lights_by_group: dict[str, list[str]] = {}
for group_id, group_details in groups_data.items():
group_name = group_details.get("name")
light_ids = group_details.get("lights", [])
if group_name and light_ids:
light_names = []
for light_id in light_ids:
# phue uses string IDs for lights in group, but int IDs in get_light_objects
light_id_int = int(light_id)
if light_id_int in lights_data:
light_name = lights_data[light_id_int].name
if light_name:
light_names.append(light_name)
if light_names:
light_names.sort() # Keep light list sorted
lights_by_group[group_name] = light_names
return lights_by_group
except (PhueException, Exception) as e:
# Return error as list
return [f"Error listing lights by group: {e}"]