Home Assistant를 텔레그램으로 제어하기: Gemini AI 자연어 스마트홈 봇 만들기
"거실 불 꺼줘"라고 텔레그램에 보내면 집의 조명이 꺼진다면? Home Assistant(이하 HA)를 사용하면서 항상 아쉬웠던 점이 있었습니다. 외출 중에 기기를 제어하려면 HA 앱을 열어야 하고, 자동화를 확인하려면 웹 UI에 접속해야 했거든요. 특히 아내에게 "HA 대시보드에서 거실 에어컨 entity 찾아서 꺼줘"라고 말할 수는 없잖아요.
그래서 텔레그램 봇 + Google Gemini AI를 조합해서, 자연어로 스마트홈을 제어할 수 있는 봇을 만들었습니다. Proxmox 홈랩의 Docker 컨테이너 하나로 돌아가고, 메모리도 50~80MB밖에 안 먹습니다.
완성된 봇이 할 수 있는 것
이 봇으로 할 수 있는 것들을 먼저 정리해보겠습니다.
| 기능 | 방법 | 예시 |
|---|---|---|
| 기기 상태 확인 | /status | 방별 조명/에어컨 상태 한눈에 |
| 기기 켜기/끄기 | /on 거실 에어컨 | 이름으로 검색하여 제어 |
| 자동화 관리 | /auto | 자동화 목록 + 활성화/비활성화 |
| 자연어 제어 | 텍스트 입력 | "거실 불 꺼줘", "에어컨 24도로 설정" |
| 기기 목록 | /devices | 전체 기기를 도메인별로 정리 |
자연어 제어가 핵심입니다. Gemini AI가 사용자의 말을 분석해서 어떤 entity_id에 어떤 서비스를 호출해야 하는지 자동으로 판단합니다.

사전 준비
환경 정보
- Home Assistant: Proxmox VM/LXC에서 실행 중
- 연동 기기: 코맥스 월패드(SmartThings 경유), SmartThings 기기, LG TV, Roborock 로봇청소기, Bambu Lab 3D 프린터
- 실행 환경: Proxmox LXC 컨테이너 (Docker)
- Python: 3.12
필요한 것
- HA Long-Lived Access Token — HA 웹 UI → 프로필 → 보안 → 장기 액세스 토큰 생성
- 텔레그램 봇 토큰 — @BotFather에서 /newbot으로 생성
- Gemini API 키 — Google AI Studio에서 무료 발급
- Docker — 컨테이너로 실행할 환경
프로젝트 구조
ha-bot/
├── config/
│ └── settings.py # 환경 설정 (HA URL, 토큰 등)
├── ha/
│ ├── client.py # HA REST API 클라이언트
│ └── automations.py # 자동화 설정 정의
├── ai/
│ └── assistant.py # Gemini AI 자연어 처리
├── telegram_bot.py # 텔레그램 봇 메인
├── Dockerfile
├── docker-compose.yml
└── .env # 환경변수 (API 키 등)핵심 구현
1. HA REST API 클라이언트
HA의 REST API를 비동기(aiohttp)로 호출하는 클라이언트입니다. 텔레그램 봇이 비동기로 동작하기 때문에 API 클라이언트도 async로 만들어야 합니다.
import aiohttp
class HAClient:
def __init__(self):
self.base_url = "http://192.168.20.110:8123"
self.headers = {
"Authorization": f"Bearer {HA_TOKEN}",
"Content-Type": "application/json",
}
async def call_service(self, domain, service, entity_id=None, **data):
"""HA 서비스 호출 — 기기 제어의 핵심"""
payload = dict(data)
if entity_id:
payload["entity_id"] = entity_id
url = f"{self.base_url}/api/services/{domain}/{service}"
async with aiohttp.ClientSession(headers=self.headers) as session:
async with session.post(url, json=payload) as resp:
return resp.status in (200, 201)
async def turn_on(self, entity_id, **kwargs):
domain = entity_id.split(".")[0]
return await self.call_service(domain, "turn_on", entity_id, **kwargs)
async def turn_off(self, entity_id):
domain = entity_id.split(".")[0]
return await self.call_service(domain, "turn_off", entity_id)entity_id에서 도메인(light, switch, climate 등)을 자동 추출해서 적절한 서비스를 호출합니다. turn_on, turn_off, toggle 같은 공통 서비스는 대부분의 도메인에서 동작합니다.

2. Gemini AI 자연어 처리
이 부분이 가장 재미있는 부분입니다. 사용자가 "거실 불 꺼줘"라고 보내면, Gemini가 이걸 분석해서 JSON 형태의 HA API 호출로 변환합니다.
from google import genai
from google.genai import types
class AIAssistant:
def __init__(self):
self._client = genai.Client(api_key=GEMINI_API_KEY)
def parse_command(self, user_message, devices_context):
prompt = f"""## 현재 기기 목록
{devices_context}
## 사용자 명령
{user_message}
"""
response = self._client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPT,
temperature=0.1,
response_mime_type="application/json",
),
)
return json.loads(response.text)핵심은 시스템 프롬프트입니다. AI에게 현재 기기 목록(entity_id, 이름, 상태)을 전달하고, 사용자의 명령을 3가지 타입으로 분류하도록 합니다:
- action: 기기 제어 (domain, service, entity_id, data)
- query: 상태 조회 (조회할 entity_id 목록)
- chat: 일반 대화
temperature=0.1로 설정해서 AI가 창의적인 해석 대신 정확한 매칭을 하도록 했습니다. response_mime_type="application/json"으로 JSON 출력을 강제하면 파싱 실패도 거의 없습니다.
3. 텔레그램 봇
python-telegram-bot 라이브러리를 사용합니다. 명령어 핸들러와 자연어 메시지 핸들러를 분리했습니다.
from telegram.ext import Application, CommandHandler, MessageHandler, filters
app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
# 기기 목록 캐싱 (5분마다 갱신)
async def _get_devices(context):
cache = context.bot_data.get("devices_cache")
cache_time = context.bot_data.get("devices_cache_time")
if cache and (datetime.now() - cache_time).seconds < 300:
return cache
# HA API에서 새로 가져오기
devices = await ha.get_devices_summary()
context.bot_data["devices_cache"] = devices
return devices⚠️ 주의사항: HA에 기기가 많으면(제 경우 200개 이상) 매번 API를 호출하는 건 비효율적입니다. 5분 캐싱으로 해결했습니다.
자연어 메시지 처리는 이렇게 동작합니다:
async def handle_message(update, context):
text = update.message.text
devices = await _get_devices(context)
devices_text = _devices_to_text(devices) # AI에 전달할 기기 목록
# Gemini AI로 명령 분석
result = await loop.run_in_executor(None, ai.parse_command, text, devices_text)
if result["type"] == "action":
for action in result["actions"]:
await ha.call_service(action["domain"], action["service"], action["entity_id"])
await update.message.reply_text(result["response"])
elif result["type"] == "query":
# 상태 조회 후 응답
...Gemini API 호출은 블로킹이라 run_in_executor로 스레드풀에서 실행합니다. 안 그러면 AI가 응답할 때까지 봇이 먹통이 됩니다.

4. 방별 상태 표시
/status를 누르면 기기를 방별로 분류해서 보여줍니다. entity_id나 friendly_name에서 방 이름을 추출하는 방식입니다.
🏠 기기 상태
📍 거실
🔴🌡️ 에어컨: off
🔴🪟 커튼: closed
🟢📺 TV: on
📍 안방
🔴🌡️ 에어컨: off
🟢🔌 멀티탭: on
📍 수현이방
🟢💡 조명: on
🤖 로봇청소기: 대기 중자동화 등록
봇에서 /setup 명령으로 미리 정의된 자동화를 HA에 일괄 등록할 수 있게 만들었습니다.
AWAY_MODE = {
"id": "telegram_bot_away_mode",
"alias": "외출 모드",
"trigger": [{
"platform": "state",
"entity_id": "person.pswq",
"to": "not_home",
"for": {"minutes": 5},
}],
"action": [
{"service": "light.turn_off", "target": {"entity_id": "all"}},
{"service": "climate.turn_off", "target": {"entity_id": [...]}},
{"service": "cover.close_cover", "target": {"entity_id": [...]}},
],
}현재 등록한 자동화:
| 자동화 | 트리거 | 동작 |
|---|---|---|
| 외출 모드 | 5분간 외출 감지 | 조명/에어컨 끄기, 커튼 닫기, 청소기 시작 |
| 귀가 모드 | 귀가 감지 | 조명 켜기, 커튼 열기 |
| 아침 커튼 | 매일 07:00 | 거실 커튼 열기 |
| 저녁 커튼 | 매일 22:00 | 거실 커튼 닫기 |
| 3D 프린터 팬 | 출력 시작/완료 | 팬 자동 ON, 완료 10분 후 OFF + 알림 |

Docker 배포
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "telegram_bot.py"]# docker-compose.yml
services:
ha-bot:
build: .
container_name: ha-bot-telegram
restart: unless-stopped
env_file: .env
volumes:
- ./logs:/app/logsdocker compose up -d --build이게 끝입니다. 컨테이너 하나로 메모리 50~80MB만 사용하면서 돌아갑니다. restart: unless-stopped로 서버 재시작 시에도 자동으로 올라옵니다.
⚠️ 트러블슈팅
entity_id에 특수문자가 있으면 텔레그램 HTML 파싱 실패
처음에 텔레그램 메시지를 HTML 모드(parse_mode=ParseMode.HTML)로 보냈더니, entity_id에 <>가 포함된 경우 파싱 에러가 났습니다. 해결: 기기 목록/상태 표시는 그냥 일반 텍스트로 전환했습니다. 꾸미기보다 안정성이 중요합니다.
Gemini AI가 엉뚱한 entity_id를 반환
기기 목록을 AI에 전달할 때 entity_id | friendly_name | state 형태로 주니까, AI가 가장 유사한 entity_id를 정확하게 찾아줍니다. 기기 목록을 캐싱해서 항상 최신 상태를 전달하는 게 중요합니다.
기기가 200개 이상이면 Gemini 컨텍스트가 커짐
전체 기기 목록을 매번 AI에 전달하니 토큰이 꽤 소모됩니다. 제어 가능한 도메인(light, switch, climate 등)만 필터링하면 절반 이하로 줄일 수 있습니다.
마무리
텔레그램 봇 하나로 스마트홈 제어가 이렇게 편해질 줄 몰랐습니다. 특히 Gemini AI 덕분에 entity_id를 외울 필요 없이 자연어로 제어할 수 있는 게 가장 큰 장점입니다. 아내에게 "텔레그램에서 거실 불 꺼줘라고 보내면 돼"라고 말할 수 있게 됐거든요.
다음 단계로는:
- 코맥스 월패드 연동 (EW11 + SmartThings Edge 드라이버)
- 세탁기/건조기 완료 알림 자동화
- 에어컨 온도 기반 자동 제어
홈랩에서 이런 프로젝트를 하나씩 만들어가는 재미가 있습니다. 궁금한 점이 있다면 댓글로 남겨주세요.
'IT > HomeLabs' 카테고리의 다른 글
| [HomeLabs] UPS 홈서버 전원 관리: 정전 대비 및 데이터 보호 완벽 가이드 (0) | 2026.04.13 |
|---|---|
| [HomeLabs] Home Assistant 음성 제어 2026.4: 로컬 AI 홈 오토메이션 완벽 가이드 (0) | 2026.04.09 |
| [HomeLabs] Docker Compose 2026 홈랩 멀티컨테이너 완전 가이드: 실전 Compose 설정 (1) | 2026.04.08 |
| [HomeLabs] 저전력 미니PC로 나만의 홈서버 구축하기: 전력소모 줄이기 (0) | 2026.04.05 |
| [HomeLabs] Proxmox에 Home Assistant 올리기 ② — Mushroom Cards 대시보드 만들기 (0) | 2026.04.04 |
| [HomeLabs] Proxmox에 Home Assistant 올리기 ① — 설치부터 SmartThings 연동까지 (0) | 2026.04.04 |