|
| 1 | +""" |
| 2 | +Configuration management tools for Home Assistant automations. |
| 3 | +
|
| 4 | +This module provides tools for retrieving, creating, updating, and removing |
| 5 | +Home Assistant automation configurations. |
| 6 | +""" |
| 7 | + |
| 8 | +import logging |
| 9 | +from typing import Annotated, Any, cast |
| 10 | + |
| 11 | +from pydantic import Field |
| 12 | + |
| 13 | +from .helpers import log_tool_usage |
| 14 | +from .util_helpers import parse_json_param |
| 15 | + |
| 16 | +logger = logging.getLogger(__name__) |
| 17 | + |
| 18 | + |
| 19 | +def register_config_automation_tools(mcp: Any, client: Any, **kwargs: Any) -> None: |
| 20 | + """Register Home Assistant automation configuration tools.""" |
| 21 | + |
| 22 | + @mcp.tool |
| 23 | + @log_tool_usage |
| 24 | + async def ha_config_get_automation( |
| 25 | + identifier: Annotated[ |
| 26 | + str, |
| 27 | + Field( |
| 28 | + description="Automation entity_id (e.g., 'automation.morning_routine') or unique_id" |
| 29 | + ), |
| 30 | + ], |
| 31 | + ) -> dict[str, Any]: |
| 32 | + """ |
| 33 | + Retrieve Home Assistant automation configuration. |
| 34 | +
|
| 35 | + Returns the complete configuration including triggers, conditions, actions, and mode settings. |
| 36 | +
|
| 37 | + EXAMPLES: |
| 38 | + - Get automation: ha_config_get_automation("automation.morning_routine") |
| 39 | + - Get by unique_id: ha_config_get_automation("my_unique_automation_id") |
| 40 | +
|
| 41 | + For comprehensive automation documentation, use: ha_get_domain_docs("automation") |
| 42 | + """ |
| 43 | + try: |
| 44 | + config_result = await client.get_automation_config(identifier) |
| 45 | + return { |
| 46 | + "success": True, |
| 47 | + "action": "get", |
| 48 | + "identifier": identifier, |
| 49 | + "config": config_result, |
| 50 | + } |
| 51 | + except Exception as e: |
| 52 | + # Handle 404 errors gracefully (often used to verify deletion) |
| 53 | + error_str = str(e) |
| 54 | + if ( |
| 55 | + "404" in error_str |
| 56 | + or "not found" in error_str.lower() |
| 57 | + or "entity not found" in error_str.lower() |
| 58 | + ): |
| 59 | + logger.debug( |
| 60 | + f"Automation {identifier} not found (expected for deletion verification)" |
| 61 | + ) |
| 62 | + return { |
| 63 | + "success": False, |
| 64 | + "action": "get", |
| 65 | + "identifier": identifier, |
| 66 | + "error": f"Automation {identifier} does not exist", |
| 67 | + "reason": "not_found", |
| 68 | + } |
| 69 | + |
| 70 | + logger.error(f"Error getting automation: {e}") |
| 71 | + return { |
| 72 | + "success": False, |
| 73 | + "action": "get", |
| 74 | + "identifier": identifier, |
| 75 | + "error": str(e), |
| 76 | + "suggestions": [ |
| 77 | + "Verify automation exists using ha_search_entities(domain_filter='automation')", |
| 78 | + "Check Home Assistant connection", |
| 79 | + "Use ha_get_domain_docs('automation') for configuration help", |
| 80 | + ], |
| 81 | + } |
| 82 | + |
| 83 | + @mcp.tool |
| 84 | + @log_tool_usage |
| 85 | + async def ha_config_set_automation( |
| 86 | + config: Annotated[ |
| 87 | + str | dict[str, Any], |
| 88 | + Field( |
| 89 | + description="Complete automation configuration with required fields: 'alias', 'trigger', 'action'. Optional: 'description', 'condition', 'mode', 'max', 'initial_state', 'variables'" |
| 90 | + ), |
| 91 | + ], |
| 92 | + identifier: Annotated[ |
| 93 | + str | None, |
| 94 | + Field( |
| 95 | + description="Automation entity_id or unique_id for updates. Omit to create new automation with generated unique_id.", |
| 96 | + default=None, |
| 97 | + ), |
| 98 | + ] = None, |
| 99 | + ) -> dict[str, Any]: |
| 100 | + """ |
| 101 | + Create or update a Home Assistant automation. |
| 102 | +
|
| 103 | + Creates a new automation (if identifier omitted) or updates existing automation with provided configuration. |
| 104 | +
|
| 105 | + REQUIRED CONFIG FIELDS: |
| 106 | + - alias: Human-readable automation name |
| 107 | + - trigger: List of trigger conditions (time, state, event, etc.) |
| 108 | + - action: List of actions to execute |
| 109 | +
|
| 110 | + OPTIONAL CONFIG FIELDS: |
| 111 | + - description: Detailed description |
| 112 | + - condition: Additional conditions that must be met |
| 113 | + - mode: 'single' (default), 'restart', 'queued', 'parallel' |
| 114 | + - max: Maximum concurrent executions (for queued/parallel modes) |
| 115 | + - initial_state: Whether automation starts enabled (true/false) |
| 116 | + - variables: Variables for use in automation |
| 117 | +
|
| 118 | + BASIC EXAMPLES: |
| 119 | +
|
| 120 | + Simple time-based automation: |
| 121 | + ha_config_set_automation({ |
| 122 | + "alias": "Morning Lights", |
| 123 | + "trigger": [{"platform": "time", "at": "07:00:00"}], |
| 124 | + "action": [{"service": "light.turn_on", "target": {"area_id": "bedroom"}}] |
| 125 | + }) |
| 126 | +
|
| 127 | + Motion-activated lighting with condition: |
| 128 | + ha_config_set_automation({ |
| 129 | + "alias": "Motion Light", |
| 130 | + "trigger": [{"platform": "state", "entity_id": "binary_sensor.motion", "to": "on"}], |
| 131 | + "condition": [{"condition": "sun", "after": "sunset"}], |
| 132 | + "action": [ |
| 133 | + {"service": "light.turn_on", "target": {"entity_id": "light.hallway"}}, |
| 134 | + {"delay": {"minutes": 5}}, |
| 135 | + {"service": "light.turn_off", "target": {"entity_id": "light.hallway"}} |
| 136 | + ], |
| 137 | + "mode": "restart" |
| 138 | + }) |
| 139 | +
|
| 140 | + Update existing automation: |
| 141 | + ha_config_set_automation( |
| 142 | + identifier="automation.morning_routine", |
| 143 | + config={ |
| 144 | + "alias": "Updated Morning Routine", |
| 145 | + "trigger": [{"platform": "time", "at": "06:30:00"}], |
| 146 | + "action": [ |
| 147 | + {"service": "light.turn_on", "target": {"area_id": "bedroom"}}, |
| 148 | + {"service": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} |
| 149 | + ] |
| 150 | + } |
| 151 | + ) |
| 152 | +
|
| 153 | + TRIGGER TYPES: time, time_pattern, sun, state, numeric_state, event, device, zone, template, and more |
| 154 | + CONDITION TYPES: state, numeric_state, time, sun, template, device, zone, and more |
| 155 | + ACTION TYPES: service calls, delays, wait_for_trigger, wait_template, if/then/else, choose, repeat, parallel |
| 156 | +
|
| 157 | + For comprehensive automation documentation with all trigger/condition/action types and advanced examples: |
| 158 | + - Use: ha_get_domain_docs("automation") |
| 159 | + - Or visit: https://www.home-assistant.io/docs/automation/ |
| 160 | +
|
| 161 | + TROUBLESHOOTING: |
| 162 | + - Use ha_get_state() to verify entity_ids exist |
| 163 | + - Use ha_search_entities() to find correct entity_ids |
| 164 | + - Use ha_eval_template() to test Jinja2 templates before using in automations |
| 165 | + - Use ha_search_entities(domain_filter='automation') to find existing automations |
| 166 | + """ |
| 167 | + try: |
| 168 | + # Parse JSON config if provided as string |
| 169 | + try: |
| 170 | + parsed_config = parse_json_param(config, "config") |
| 171 | + except ValueError as e: |
| 172 | + return { |
| 173 | + "success": False, |
| 174 | + "error": f"Invalid config parameter: {e}", |
| 175 | + "provided_config_type": type(config).__name__, |
| 176 | + } |
| 177 | + |
| 178 | + # Ensure config is a dict |
| 179 | + if parsed_config is None or not isinstance(parsed_config, dict): |
| 180 | + return { |
| 181 | + "success": False, |
| 182 | + "error": "Config parameter must be a JSON object", |
| 183 | + "provided_type": type(parsed_config).__name__, |
| 184 | + } |
| 185 | + |
| 186 | + config_dict = cast(dict[str, Any], parsed_config) |
| 187 | + |
| 188 | + # Validate required fields |
| 189 | + required_fields = ["alias", "trigger", "action"] |
| 190 | + missing_fields = [f for f in required_fields if f not in config_dict] |
| 191 | + if missing_fields: |
| 192 | + return { |
| 193 | + "success": False, |
| 194 | + "error": f"Missing required fields: {', '.join(missing_fields)}", |
| 195 | + "required_fields": required_fields, |
| 196 | + "missing_fields": missing_fields, |
| 197 | + } |
| 198 | + |
| 199 | + result = await client.upsert_automation_config( |
| 200 | + config_dict, identifier |
| 201 | + ) |
| 202 | + return { |
| 203 | + "success": True, |
| 204 | + **result, |
| 205 | + "config_provided": config_dict, |
| 206 | + } |
| 207 | + |
| 208 | + except Exception as e: |
| 209 | + logger.error(f"Error upserting automation: {e}") |
| 210 | + return { |
| 211 | + "success": False, |
| 212 | + "identifier": identifier, |
| 213 | + "error": str(e), |
| 214 | + "suggestions": [ |
| 215 | + "Check automation configuration format", |
| 216 | + "Ensure required fields: alias, trigger, action", |
| 217 | + "Use entity_id format: automation.morning_routine or unique_id", |
| 218 | + "Use ha_search_entities(domain_filter='automation') to find automations", |
| 219 | + "Use ha_get_domain_docs('automation') for comprehensive configuration help", |
| 220 | + ], |
| 221 | + } |
| 222 | + |
| 223 | + @mcp.tool |
| 224 | + @log_tool_usage |
| 225 | + async def ha_config_remove_automation( |
| 226 | + identifier: Annotated[ |
| 227 | + str, |
| 228 | + Field( |
| 229 | + description="Automation entity_id (e.g., 'automation.old_automation') or unique_id to delete" |
| 230 | + ), |
| 231 | + ], |
| 232 | + ) -> dict[str, Any]: |
| 233 | + """ |
| 234 | + Delete a Home Assistant automation. |
| 235 | +
|
| 236 | + EXAMPLES: |
| 237 | + - Delete automation: ha_config_remove_automation("automation.old_automation") |
| 238 | + - Delete by unique_id: ha_config_remove_automation("my_unique_id") |
| 239 | +
|
| 240 | + **WARNING:** Deleting an automation removes it permanently from your Home Assistant configuration. |
| 241 | + """ |
| 242 | + try: |
| 243 | + result = await client.delete_automation_config(identifier) |
| 244 | + return {"success": True, "action": "delete", **result} |
| 245 | + except Exception as e: |
| 246 | + logger.error(f"Error deleting automation: {e}") |
| 247 | + return { |
| 248 | + "success": False, |
| 249 | + "action": "delete", |
| 250 | + "identifier": identifier, |
| 251 | + "error": str(e), |
| 252 | + "suggestions": [ |
| 253 | + "Verify automation exists using ha_search_entities(domain_filter='automation')", |
| 254 | + "Use entity_id format: automation.morning_routine or unique_id", |
| 255 | + "Check Home Assistant connection", |
| 256 | + ], |
| 257 | + } |
0 commit comments