feat: Add Home Assistant smart device integration (Issue #366)#2117
feat: Add Home Assistant smart device integration (Issue #366)#2117giwaov wants to merge 2 commits intoOpenMind:mainfrom
Conversation
- Add home_assistant action plugin with REST API and MQTT connectors - Add HomeAssistantStateInput for monitoring device states - Support for lights (on/off, brightness, color), switches, thermostats, covers, fans - Comprehensive test suite with 30+ test cases - Documentation with setup guide and configuration examples - Demo configuration for smart home control Closes OpenMind#366
There was a problem hiding this comment.
Pull request overview
This pull request adds comprehensive Home Assistant smart home device integration to OM1, enabling control and monitoring of IoT devices through both REST API and MQTT protocols.
Changes:
- Implements action plugin for controlling lights, switches, thermostats, covers, and fans with brightness/color/temperature control
- Implements input plugin for real-time device state monitoring with change detection
- Provides dual connectivity options: REST API (primary) and MQTT (alternative)
- Includes 30+ unit tests covering configuration, device control, and state formatting
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 28 comments.
Show a summary per file
| File | Description |
|---|---|
| src/actions/home_assistant/interface.py | Defines action interfaces with device types (light, switch, climate, cover, fan, media_player) and input/output dataclasses |
| src/actions/home_assistant/connector/rest_api.py | REST API connector implementing device control via Home Assistant HTTP API with session management |
| src/actions/home_assistant/connector/mqtt.py | MQTT connector for device control using MQTT protocol with aiomqtt library |
| src/inputs/plugins/home_assistant_state.py | Input plugin for polling and formatting device states for LLM consumption |
| tests/actions/test_home_assistant.py | Comprehensive unit tests for REST API connector covering all device types and error cases |
| tests/inputs/test_home_assistant_state.py | Unit tests for input plugin covering state formatting and polling logic |
| docs/smart_devices/HOME_ASSISTANT_SETUP.md | Complete setup documentation with examples, configuration, and troubleshooting |
| config/home_assistant.json5 | Demo configuration file with examples for both action and input plugins |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async def raw_to_text(self, raw_input: Optional[str]) -> Optional[Message]: | ||
| """ | ||
| Convert raw input to a Message object. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| raw_input : Optional[str] | ||
| Raw state information. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[Message] | ||
| Message object or None. | ||
| """ | ||
| message = await self._raw_to_text(raw_input) | ||
| if message: | ||
| self.messages.append(message) | ||
| return message |
There was a problem hiding this comment.
The raw_to_text method duplicates the _raw_to_text logic unnecessarily. In the codebase convention (see gps.py:93-105, face_presence_input.py:147-164), raw_to_text should call _raw_to_text and then append to messages, not reimplement the logic. The current implementation has both methods doing similar work, which violates DRY principle and the established pattern.
| """ | ||
| Home Assistant Device State Input Plugin for OM1. | ||
|
|
||
| This module provides an input plugin that monitors Home Assistant device states | ||
| and reports them to the OM1 fuser for processing by the LLM. | ||
| """ | ||
|
|
||
| import asyncio | ||
| import logging | ||
| import time | ||
| from dataclasses import dataclass | ||
| from typing import Any, Dict, List, Optional | ||
|
|
||
| import aiohttp | ||
| from pydantic import Field | ||
|
|
||
| from inputs.base import Message, SensorConfig | ||
| from inputs.base.loop import FuserInput |
There was a problem hiding this comment.
Missing IOProvider integration. All other input plugins in the codebase (e.g., gps.py, face_presence_input.py, battery_turtlebot4.py) import and use IOProvider to log input data in formatted_latest_buffer. This plugin should follow the same pattern by importing IOProvider and calling io_provider.add_input() in formatted_latest_buffer.
| def tick(self) -> None: | ||
| """ | ||
| Tick method for periodic updates. | ||
| """ | ||
| time.sleep(60) |
There was a problem hiding this comment.
The tick() method sleeps for 60 seconds unconditionally. This blocks the thread and could impact the responsiveness of the action system. While this matches the base class default implementation (actions/base.py:95-99), consider whether this is appropriate for a Home Assistant connector that may need to handle multiple rapid commands. Most action connectors either override this with appropriate timing or don't block at all. For an async connector, consider using asyncio.sleep or removing the sleep entirely if not needed.
| try: | ||
| rgb = [int(x.strip()) for x in color_rgb.split(",")] | ||
| if len(rgb) == 3: | ||
| payload["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2]} |
There was a problem hiding this comment.
RGB color values are not validated to be within the 0-255 range. The code parses integers from the color_rgb string but doesn't check if they're valid (0-255). Values outside this range could cause unexpected behavior in Home Assistant. Consider adding validation to ensure each RGB component is in the valid range.
| async def test_connect_unsupported_device_type(self, connector): | ||
| """Test handling of unsupported device types.""" | ||
| input_data = HomeAssistantInput( | ||
| device_type="unsupported", | ||
| entity_id="unsupported.test", | ||
| action="test", | ||
| ) | ||
|
|
||
| await connector.connect(input_data) | ||
|
|
||
| assert connector._last_result.success is False | ||
| assert "Unsupported device type" in connector._last_result.message | ||
|
|
There was a problem hiding this comment.
Missing test coverage for network error scenarios. The _call_service method in rest_api.py handles aiohttp.ClientError and generic exceptions (lines 153-158), but there are no tests verifying this error handling works correctly. Consider adding tests that simulate network errors, timeouts, and connection failures to ensure graceful degradation.
| that are exposed via MQTT in Home Assistant. | ||
| """ | ||
|
|
||
| import asyncio |
There was a problem hiding this comment.
Import of 'asyncio' is not used.
| import asyncio |
| home devices. | ||
| """ | ||
|
|
||
| import asyncio |
There was a problem hiding this comment.
Import of 'asyncio' is not used.
| import asyncio |
tests/actions/test_home_assistant.py
Outdated
| """ | ||
|
|
||
| import pytest | ||
| from unittest.mock import AsyncMock, MagicMock, patch |
There was a problem hiding this comment.
Import of 'MagicMock' is not used.
| from unittest.mock import AsyncMock, MagicMock, patch | |
| from unittest.mock import AsyncMock, patch |
tests/actions/test_home_assistant.py
Outdated
| SwitchAction, | ||
| ThermostatAction, | ||
| ) |
There was a problem hiding this comment.
Import of 'SwitchAction' is not used.
| SwitchAction, | |
| ThermostatAction, | |
| ) | |
| ThermostatAction, | |
| ) |
|
|
||
| import pytest | ||
| import time | ||
| from unittest.mock import AsyncMock, MagicMock, patch |
There was a problem hiding this comment.
Import of 'MagicMock' is not used.
| from unittest.mock import AsyncMock, MagicMock, patch | |
| from unittest.mock import AsyncMock, patch |
- Remove unused imports (asyncio, MagicMock, SwitchAction) - Remove unsupported MEDIA_PLAYER from DeviceType enum - Add RGB color validation (0-255 range) in REST and MQTT connectors - Add access_token empty warning in REST connector and input plugin - Implement persistent MQTT connection instead of per-message - Add close() method to MQTT connector for cleanup - Document MQTT topic format convention in docstring - Fix input plugin to match OM1 codebase patterns: - Import and use IOProvider for add_input() - Fix formatted_latest_buffer to use INPUT/START/END format - Fix raw_to_text to not return (just update self.messages) - Clear messages buffer after formatting - Fix poll sleep to use remaining interval time - Document state change detection limitation (state string only) - Add network error tests (aiohttp.ClientError, HTTP errors) - Add RGB out-of-range test and empty token warning test - Fix test_poll_respects_interval and add interval-passed test - Update formatted_latest_buffer test for new OM1 convention - Change config verify_ssl default to true with security warning - Fix LICENSE path in docs (../LICENSE -> ../../LICENSE)
Summary
This PR adds Home Assistant integration to OM1, enabling robots to control and monitor smart home devices.
🤖 Home Assistant Integrated
Features
Action Plugin (
home_assistant)Input Plugin (
HomeAssistantStateInput)Files Changed
src/actions/home_assistant/interface.py- Action interface definitionssrc/actions/home_assistant/connector/rest_api.py- REST API connectorsrc/actions/home_assistant/connector/mqtt.py- MQTT connectorsrc/inputs/plugins/home_assistant_state.py- State monitoring inputtests/actions/test_home_assistant.py- Action tests (30+ test cases)tests/inputs/test_home_assistant_state.py- Input testsconfig/home_assistant.json5- Demo configurationdocs/smart_devices/HOME_ASSISTANT_SETUP.md- Setup documentationConfiguration Example
🎥 Demo Video
Coming soon - will record once PR is reviewed.
📑 Notes
Closes #366