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
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.