Skip to content
Learn Agentic AI14 min read0 views

Build a Smart Home Agent: Device Control, Scene Management, and Automation Rules

Create a smart home AI agent that controls IoT devices, manages lighting and climate scenes, and configures automation rules — a complete home automation assistant built with Python and the OpenAI Agents SDK.

Why a Smart Home Agent

Smart home devices are powerful individually but painful to orchestrate. Adjusting lights, thermostat, and speakers typically means opening three different apps. A smart home agent provides a single natural language interface: say "movie night" and it dims the lights, sets the thermostat to 72, and queues your playlist. This tutorial builds a complete home automation agent with device control, scene management, and rule-based automation.

Project Setup

mkdir smarthome-agent && cd smarthome-agent
python -m venv venv && source venv/bin/activate
pip install openai-agents pydantic
mkdir -p src
touch src/__init__.py src/devices.py src/scenes.py
touch src/automation.py src/agent.py

Step 1: Device Simulator

We simulate IoT devices with an in-memory registry. Each device has a type, state, and configurable properties.

# src/devices.py
from pydantic import BaseModel

class DeviceState(BaseModel):
    power: bool = False
    brightness: int | None = None  # 0-100 for lights
    temperature: float | None = None  # for thermostat
    volume: int | None = None  # 0-100 for speakers
    color: str | None = None  # for color lights
    mode: str | None = None  # for multi-mode devices

class Device(BaseModel):
    id: str
    name: str
    room: str
    device_type: str  # light, thermostat, speaker, lock, camera
    state: DeviceState

class DeviceRegistry:
    def __init__(self):
        self.devices: dict[str, Device] = {}
        self._init_defaults()

    def _init_defaults(self):
        defaults = [
            Device(id="light_lr", name="Living Room Light",
                   room="living room", device_type="light",
                   state=DeviceState(power=True, brightness=80)),
            Device(id="light_br", name="Bedroom Light",
                   room="bedroom", device_type="light",
                   state=DeviceState(power=False, brightness=50,
                                     color="warm white")),
            Device(id="thermo_main", name="Main Thermostat",
                   room="hallway", device_type="thermostat",
                   state=DeviceState(power=True, temperature=72.0,
                                     mode="auto")),
            Device(id="speaker_lr", name="Living Room Speaker",
                   room="living room", device_type="speaker",
                   state=DeviceState(power=False, volume=40)),
            Device(id="lock_front", name="Front Door Lock",
                   room="entrance", device_type="lock",
                   state=DeviceState(power=True, mode="locked")),
            Device(id="light_kt", name="Kitchen Light",
                   room="kitchen", device_type="light",
                   state=DeviceState(power=True, brightness=100)),
        ]
        for d in defaults:
            self.devices[d.id] = d

    def get_device(self, device_id: str) -> Device | None:
        return self.devices.get(device_id)

    def find_by_room(self, room: str) -> list[Device]:
        room_lower = room.lower()
        return [
            d for d in self.devices.values()
            if room_lower in d.room.lower()
        ]

    def find_by_type(self, device_type: str) -> list[Device]:
        return [
            d for d in self.devices.values()
            if d.device_type == device_type
        ]

    def set_property(
        self, device_id: str, prop: str, value
    ) -> str:
        device = self.get_device(device_id)
        if not device:
            return f"Device {device_id} not found"
        if not hasattr(device.state, prop):
            return f"Property {prop} not valid for this device"
        setattr(device.state, prop, value)
        return f"Set {device.name} {prop} to {value}"

    def get_all_status(self) -> str:
        lines = []
        for d in self.devices.values():
            status = "ON" if d.state.power else "OFF"
            extra = []
            if d.state.brightness is not None:
                extra.append(f"brightness={d.state.brightness}%")
            if d.state.temperature is not None:
                extra.append(f"temp={d.state.temperature}F")
            if d.state.volume is not None:
                extra.append(f"vol={d.state.volume}%")
            if d.state.mode:
                extra.append(f"mode={d.state.mode}")
            detail = ", ".join(extra) if extra else ""
            lines.append(
                f"  {d.name} ({d.room}): {status} {detail}"
            )
        return "\n".join(lines)

registry = DeviceRegistry()

Step 2: Scene Manager

Scenes apply multiple device changes simultaneously.

# src/scenes.py
from src.devices import registry

SCENES = {
    "movie night": [
        ("light_lr", "brightness", 20),
        ("light_lr", "color", "warm white"),
        ("speaker_lr", "power", True),
        ("speaker_lr", "volume", 60),
        ("thermo_main", "temperature", 72.0),
    ],
    "good morning": [
        ("light_br", "power", True),
        ("light_br", "brightness", 80),
        ("light_kt", "power", True),
        ("light_kt", "brightness", 100),
        ("thermo_main", "temperature", 70.0),
    ],
    "goodnight": [
        ("light_lr", "power", False),
        ("light_kt", "power", False),
        ("light_br", "brightness", 10),
        ("lock_front", "mode", "locked"),
        ("thermo_main", "temperature", 68.0),
        ("speaker_lr", "power", False),
    ],
    "away": [
        ("light_lr", "power", False),
        ("light_br", "power", False),
        ("light_kt", "power", False),
        ("thermo_main", "temperature", 65.0),
        ("lock_front", "mode", "locked"),
    ],
}

def activate_scene(scene_name: str) -> str:
    actions = SCENES.get(scene_name.lower())
    if not actions:
        available = ", ".join(SCENES.keys())
        return f"Scene not found. Available: {available}"
    results = []
    for device_id, prop, value in actions:
        result = registry.set_property(device_id, prop, value)
        results.append(result)
    return f"Scene '{scene_name}' activated:\n" + "\n".join(results)

def list_scenes() -> list[str]:
    return list(SCENES.keys())

Step 3: Automation Rules

# src/automation.py
from datetime import datetime
from src.devices import registry

class AutomationRule:
    def __init__(
        self, name: str, trigger: str, device_id: str,
        prop: str, value, active: bool = True,
    ):
        self.name = name
        self.trigger = trigger  # e.g., "time:22:00", "sunset"
        self.device_id = device_id
        self.prop = prop
        self.value = value
        self.active = active

rules: list[AutomationRule] = []

def add_rule(
    name: str, trigger: str, device_id: str,
    prop: str, value,
) -> str:
    rules.append(AutomationRule(name, trigger, device_id, prop, value))
    return f"Rule '{name}' created: on {trigger}, set {device_id} {prop}={value}"

def list_rules() -> str:
    if not rules:
        return "No automation rules configured."
    lines = []
    for r in rules:
        status = "active" if r.active else "paused"
        lines.append(
            f"  - {r.name}: {r.trigger} -> "
            f"{r.device_id}.{r.prop}={r.value} [{status}]"
        )
    return "\n".join(lines)

def evaluate_rules():
    now = datetime.now().strftime("%H:%M")
    triggered = []
    for rule in rules:
        if not rule.active:
            continue
        if rule.trigger == f"time:{now}":
            registry.set_property(rule.device_id, rule.prop, rule.value)
            triggered.append(rule.name)
    return triggered

Step 4: Wire Up the Agent

# src/agent.py
import asyncio
from agents import Agent, Runner, function_tool
from src.devices import registry
from src.scenes import activate_scene, list_scenes
from src.automation import add_rule, list_rules

@function_tool
def get_device_status() -> str:
    """Get status of all smart home devices."""
    return registry.get_all_status()

@function_tool
def control_device(
    device_id: str, property_name: str, value: str,
) -> str:
    """Control a specific device property."""
    typed_value: bool | int | float | str = value
    if value.lower() in ("true", "false"):
        typed_value = value.lower() == "true"
    elif value.replace(".", "").isdigit():
        typed_value = float(value) if "." in value else int(value)
    return registry.set_property(device_id, property_name, typed_value)

@function_tool
def set_scene(scene_name: str) -> str:
    """Activate a predefined scene."""
    return activate_scene(scene_name)

@function_tool
def create_automation(
    name: str, trigger: str, device_id: str,
    prop: str, value: str,
) -> str:
    """Create a time-based automation rule."""
    return add_rule(name, trigger, device_id, prop, value)

@function_tool
def view_automations() -> str:
    """View all automation rules."""
    return list_rules()

home_agent = Agent(
    name="Smart Home Assistant",
    instructions="""You are a smart home control agent.
Help users control devices, activate scenes, and set up automations.
Always confirm actions taken. When the user describes a mood or
activity, suggest an appropriate scene. Device IDs: light_lr,
light_br, light_kt, thermo_main, speaker_lr, lock_front.
Available scenes: movie night, good morning, goodnight, away.""",
    tools=[
        get_device_status, control_device, set_scene,
        create_automation, view_automations,
    ],
)

async def main():
    result = await Runner.run(
        home_agent,
        "Set up movie night and also create an automation "
        "to lock the front door every night at 11pm.",
    )
    print(result.final_output)

if __name__ == "__main__":
    asyncio.run(main())

The agent activates the movie night scene, then creates the nightly lock automation — all from a single natural language command.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

FAQ

How would I connect this to real smart home devices?

Replace the DeviceRegistry with API calls to platforms like Home Assistant, SmartThings, or Philips Hue. Each platform provides REST APIs or Python libraries for device control. The agent tools remain the same — only the underlying registry methods change from in-memory state updates to HTTP requests.

Can the agent learn my preferences over time?

Yes. Log every command the user gives and the scenes they activate. Over time, analyze patterns — for example, if the user always dims lights at 9pm, proactively suggest creating an automation rule. Store preferences in a JSON file or database that the agent reads on startup.

How do I handle conflicting automation rules?

Add a priority field to AutomationRule and a conflict detection method that checks whether two rules modify the same device property at the same time. When conflicts are detected, the agent alerts the user and asks which rule should take precedence before activating them.


#SmartHome #IoT #AIAgent #Python #HomeAutomation #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.