# Controlling Your Home From the Terminal: Home Assistant, Docker, and a Custom CLI
> [!tip] TLDR
> Set up Home Assistant in Docker on a Raspberry Pi, build a Python CLI (`hactl`) with Typer + httpx to control your devices from the terminal, and wire it into Claude Code for AI-assisted home control. Copy this prompt into Claude Code:
>
> *"Scaffold a Home Assistant deployment for Raspberry Pi with Docker (host networking, privileged mode), create a Python CLI using Typer and httpx that wraps the HA REST API with commands for thermostat, lights, locks, and scenes, and add a deploy.sh script that rsyncs configs via SSH."*
>
> Read on if you want to understand why and how this works.
I have ten smart home apps on my phone. Ten. One for lights. One for the thermostat. One for locks. One for the garage. One for cameras. And five more I've forgotten about because they send notifications I've trained myself to ignore.
My wife's #1 complaint: "I just want to open the garage from the car without fighting with the app." Fair.
My #1 complaint: I build [[AI Development & Agents/autonomous-ai-agent-squad-10-dollars-month|autonomous AI agent systems]] for a living, but I can't dim my kitchen lights without opening three apps. Something had to change.
## The Problem: Death by a Thousand Apps
Here's what our smart home looked like before this project:
- **Philips Hue** for indoor lights (bridge-based, works well in isolation)
- **LIFX** and **Govee** bulbs scattered through other rooms (WiFi, separate apps)
- **TP-Link Kasa** smart plugs and switches
- **Nest** thermostat, cameras, doorbell, smoke detectors (Google Home app)
- **Yale** smart locks on two doors (Yale Access app)
- **MyQ** garage door (MyQ app, universally hated)
- **Alexa** speakers everywhere (the Alexa app)
- **Ceiling fans** with RF remotes (no app at all, just vibes)
That's at least seven different ecosystems with no shared control plane. Want to set a "goodnight" scene? Open Hue, turn off lights. Open Nest, lower the thermostat. Open Yale, check locks. Open MyQ, verify the garage is closed. Hope you didn't forget anything.
Alexa was supposed to be the glue. It wasn't. Half the integrations broke regularly, and Amazon's idea of "smart home" is "buy more Amazon products." We killed it entirely.
## Why Home Assistant
[Home Assistant](https://www.home-assistant.io/) is open source home automation software. It runs locally, supports 2,000+ integrations, and replaces the cloud-dependent app soup with a single interface. No subscription fees. No vendor lock-in. Your data stays on your hardware.
The architecture is straightforward: every device talks to Home Assistant through its native protocol (Zigbee, WiFi, cloud API, local API). HA becomes the single brain. Then you talk to HA, through dashboards, voice, phone, or (if you're like me) the terminal.
> [!info] Open Source Matters Here
> Home Assistant's source is on GitHub with 70,000+ stars. The community ships integrations faster than any vendor could. When Yale changed their API last year, the community had a fix within days. Try getting that from a corporate app team.
## The Setup: Pi + Docker, One Evening
I run HA on a Raspberry Pi in Docker. The Pi is always on, sips power, and connects to my home network via a mesh VPN for remote access from anywhere.
The `docker-compose.yaml` is minimal:
```yaml
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
privileged: true
network_mode: host
restart: unless-stopped
volumes:
- ./config:/config
environment:
- TZ=America/New_York
```
Two things matter here:
1. **`network_mode: host`** gives HA direct access to the network for mDNS and SSDP discovery. Without this, auto-discovery of devices like thermostats and vacuums silently fails.
2. **`privileged: true`** enables USB passthrough for Zigbee coordinators. If you're adding Zigbee devices later, you'll need this.
Deployment is a bash script that rsyncs the config directory to the Pi and restarts the container:
```bash
#!/bin/bash
PI_HOST="pi"
rsync -av --delete \
--exclude '.storage/' \
--exclude 'custom_components/' \
--exclude '*.log' \
--exclude '*.db' \
docker-compose.yaml config/ ${PI_HOST}:~/homeassistant/
ssh ${PI_HOST} "cd ~/homeassistant && docker compose up -d"
```
> [!warning] Exclude HA Runtime Directories
> Home Assistant creates `.storage/`, `blueprints/`, and database files that are owned by root inside the container. If you rsync these from your laptop, you'll overwrite HA's internal state. Always exclude them.
The entire config directory is version-controlled in Git. Dashboards are YAML-mode (not the UI editor), which means I can review changes in pull requests and roll back if something breaks.
## What Came Online
### Nest Thermostat (5 minutes)
The Nest integration uses Google's Smart Device Management API. There's a one-time $5 fee to Google for API access and an OAuth dance to connect. Once linked, the thermostat appeared with full control: target temperature, HVAC mode, current readings.
### Yale Locks (Auto-discovered)
Two Yale Assure Lock 2 units (front door and porch) appeared automatically through the Yale Home integration. These are WiFi locks, no Zigbee dongle required. Battery levels show up as sensors, which feed into automations (alert at 20%, critical TTS announcement at 10%).
### Roborock Vacuum (Auto-discovered)
This was the pleasant surprise. The Roborock Q5 Pro showed up via auto-discovery the moment HA started. Full control: start, stop, pause, return to dock, room-specific cleaning. Zero configuration.
### Google TTS (Free, Built-in)
Home Assistant includes Google Translate TTS out of the box. No API key, no setup. Point it at your Nest speakers and you get voice announcements: "The garage has been open for 10 minutes," "Front door battery is critically low," "Good morning, it's 42 degrees outside."
## The CLI: `hactl`
Dashboards are fine. Voice is fine. But I live in a terminal. I wanted to type `hactl thermostat set-temp 72` and have it happen.
`hactl` is a Python CLI built with [Typer](https://typer.tiangolo.com/) for the command interface and [httpx](https://www.python-httpx.org/) for async HTTP calls to HA's REST API. It has 13 command groups:
```bash
hactl thermostat set-temp 72 # Set target temperature
hactl thermostat set-mode cool # Switch to cooling
hactl thermostat status # Current readings
hactl lights on kitchen # Turn on kitchen lights
hactl lights brightness living 50 # Dim to 50%
hactl lock status # All locks + battery levels
hactl lock lock front_door # Lock the front door
hactl scenes activate movie_night # Activate a scene
hactl vacuum start # Start the robot vacuum
hactl notify say "Dinner is ready" # TTS to all speakers
```
The client is a thin wrapper around HA's REST API:
```python
class HomeAssistantClient:
def __init__(self, host: str, token: str):
self.host = host.rstrip("/")
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
def call_service(self, domain: str, service: str,
entity_id: str, **data):
url = f"{self.host}/api/services/{domain}/{service}"
payload = {"entity_id": entity_id, **data}
response = httpx.post(url, headers=self.headers,
json=payload, timeout=10)
response.raise_for_status()
return response.json()
```
Config precedence: CLI flags > environment variables > config file. The HA token is stored in the system keychain, never in plaintext.
## The Real Payoff: AI-Assisted Home Control
Here's where it gets interesting. I use [[Practical Applications/my-personal-ai-assistant-clawdbot-seneca|Claude Code as my primary development interface]]. Since `hactl` is a standard CLI tool installed on my machine, Claude Code can call it directly.
That means I can type, in a Claude Code conversation:
> "It's getting warm in here, bump the temperature down a couple degrees"
And Claude runs:
```bash
hactl thermostat status # Check current: 74°F, target 72°F
hactl thermostat set-temp 70 # Lower by 2 degrees
```
No app. No phone. No voice command. Just natural language in the same terminal where I'm writing code.
I built a `mode` command specifically for this use case. It maps 16 natural-language states to the right HA entities:
```bash
hactl mode away # Activate away scene (lights off, lock doors, lower temp)
hactl mode bedtime # Run bedtime script (dim, lock, set temp, arm security)
hactl mode movie # Movie scene (dim lights, set mood lighting)
hactl mode vacation # Vacation mode (randomize lights, lock everything, eco temp)
hactl mode lockdown # Lock all doors, close garage, turn off exterior lights
```
Each mode maps to either a scene (instant state change) or a script (multi-step sequence). When Claude sees "I'm heading out," it can run `hactl mode away` and the house responds: lights off, doors locked, thermostat set to eco, cameras active.
### OpenClaw: Autonomous Agents That Control Your Home
This goes further than interactive sessions. I run [OpenClaw](https://github.com/OpenClaw), [[AI Development & Agents/autonomous-ai-agent-squad-10-dollars-month|an autonomous agent framework]] where persistent AI agents operate on their own schedules. One of those agents acts as a local co-pilot with access to every CLI tool on my machine.
Because `hactl` is just another CLI, an autonomous agent can use it without prompting. A morning routine agent could check the weather forecast, adjust the thermostat based on the day's conditions, and set the house to the right mode before I'm even at my desk. An evening agent could run `hactl mode bedtime` at 11 PM if it notices I'm still working. The house becomes part of the agent's toolkit, not just a separate system I have to manage.
The pattern is straightforward: give agents access to CLI tools, let them compose actions based on context. Smart home control is just another surface area.
> [!tip] Build CLI Tools for Everything
> If you're building [[Practical Applications/auto-updating-claude-code-cli-tools|CLI tools that Claude Code can use]], smart home control is a natural extension. Any REST API can become a CLI. Any CLI becomes a tool your AI assistant or autonomous agents can invoke. The compound effect of having everything terminal-accessible is significant.
## What I Learned
**Start with what auto-discovers.** Don't plan the perfect Zigbee mesh on day one. Plug in HA, see what shows up. The Roborock vacuum, the Nest thermostat, and the Yale locks all appeared without me touching a config file. Build from there.
**YAML-mode dashboards are worth the effort.** The visual editor is easier initially, but you lose version control, code review, and the ability to template across multiple dashboards. I maintain three dashboards (main, wall tablet, device management) in Git, and deploy changes the same way I deploy code.
**The REST API is the real product.** Dashboards and voice are consumer interfaces. The REST API is the developer interface. Once you have programmatic access, everything else follows: CLI tools, AI integration, custom automations, monitoring.
**Open source wins on pace.** When I needed Mushroom cards for the dashboard, HACS (the community store) had them. When I needed battery monitoring, there was a card for that. The community ships faster than any single vendor.
## What's Next
The foundation is deployed. The next phases:
- **Zigbee mesh**: Adding Inovelli Blue switches room by room (physical switches that always work, even if HA is down)
- **Garage**: Ratgdo v2.5 via ESPHome for local garage control (bye, MyQ cloud)
- **Wall tablet**: Fire HD 10 running Fully Kiosk Browser with the tablet dashboard
- **More automations**: Presence-based lighting, seasonal HVAC schedules, doorbell TTS announcements
The smart home went from "ten apps and a prayer" to a single Git repo, a Docker container, and a CLI command. My wife can open the garage from her car. I can adjust the thermostat from my code editor. The Alexa speakers are unplugged in a drawer.
That's the whole point. Technology should simplify, not fragment. Open source, local control, and a good CLI go a long way.
---
### Related Articles
- [[Practical Applications/my-personal-ai-assistant-clawdbot-seneca|My Personal AI Assistant Lives Everywhere: Building with Clawdbot]]
- [[Practical Applications/auto-updating-claude-code-cli-tools|Auto-Updating CLI Tools for Claude Code Skills]]
- [[AI Development & Agents/autonomous-ai-agent-squad-10-dollars-month|I Built an Autonomous AI Agent Squad for $10/Month]]
---
<p style="text-align: center;"><strong>About the Author</strong>: Justin Johnson builds AI systems and writes about practical AI development.</p>
<p style="text-align: center;"><a href="https://justinhjohnson.com">justinhjohnson.com</a> | <a href="https://twitter.com/bioinfo">Twitter</a> | <a href="https://www.linkedin.com/in/justinhaywardjohnson/">LinkedIn</a> | <a href="https://rundatarun.io">Run Data Run</a> | <a href="https://subscribe.rundatarun.io">Subscribe</a></p>