Skip to content
devkoriel
Go back

Tessie API와 Telegram으로 손쉽게 만드는 테슬라 제어 봇

17 min read Edit on GitHub

안녕하세요, 테슬라 차주 여러분!

오늘은 여러분의 테슬라 차량을 더욱 편리하게 관리할 수 있는 Telegram 봇을 만드는 방법을 소개하려고 합니다. 초보자도 쉽게 따라할 수 있도록 단계별로 자세히 설명드리니, 천천히 따라와 주세요!

Telegram 봇이란?

**Telegram 봇(Telegram Bot)**은 Telegram 메신저에서 자동으로 작동하는 프로그램입니다. 이를 통해 메시지를 주고받거나, 특정 명령어에 반응하여 다양한 작업을 수행할 수 있습니다. 이번 글에서는 Telegram 봇을 이용해 테슬라 차량의 상태를 조회하고, 잠금/잠금 해제, 서리 제거, 창문 닫기 등 여러 기능을 제어하는 방법을 알아보겠습니다.

준비물

봇을 만들기 전에 몇 가지 준비물이 필요합니다:

  1. Telegram 계정: Telegram 앱을 설치하고 가입하세요.
  2. Heroku 계정: 무료로 사용할 수 있는 클라우드 플랫폼입니다. Heroku 가입하기
  3. Tessie 계정: Tessie는 테슬라 차량을 제어하기 위한 API를 제공합니다. Tessie 가입하기 (가입 및 API 키 발급 과정은 아래에서 자세히 설명)
  4. Python 설치: Python은 프로그래밍 언어로, 봇을 작성하는 데 사용됩니다. Python 다운로드
  5. Git 설치: 버전 관리 도구로, Heroku에 코드를 배포할 때 필요합니다. Git 다운로드

단계별 가이드

1단계: Tessie 가입하고 API 토큰 받기

Tessie 웹사이트 방문하기

회원가입 및 로그인

API 키 발급받기

TESSIE_API_KEY=abcdefghijklmnopqrstuvwxyz1234567890

차량 VIN 확인하기

VEHICLE_VIN=LRWYGCFS9RC562139

2단계: Telegram 봇 생성하기

BotFather와 대화하기

새 봇 생성하기

API 토큰 받기

123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ

3단계: Heroku 준비하기

Heroku에 로그인하기

Heroku CLI 설치하기

Heroku에 로그인하기

$ heroku login

4단계: 프로젝트 준비하기

프로젝트 디렉토리 만들기

Python 가상환경 설정하기

$ mkdir my_tesla_bot
$ cd my_tesla_bot
$ python -m venv venv
$ venv\Scripts\activate
$ source venv/bin/activate

필요한 패키지 설치하기

$ pip install python-telegram-bot requests python-dotenv

필수 파일 만들기

5단계: 코드 작성하기

  1. bot.py 파일 작성하기

먼저 필요한 모듈을 임포트하고 환경 변수를 설정합니다:

import os
import logging
from telegram.ext import Updater, CommandHandler
from telegram import Update
import requests
from dotenv import load_dotenv

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

load_dotenv()

TELEGRAM_TOKEN = os.getenv('TELEGRAM_TOKEN')
TESSIE_API_KEY = os.getenv('TESSIE_API_KEY')
VEHICLE_VIN = os.getenv('VEHICLE_VIN')

TESSIE_HEADERS = {
    "accept": "application/json",
    "authorization": f"Bearer {TESSIE_API_KEY}"
}

AUTHORIZED_USERS = os.getenv('AUTHORIZED_USERS')
if AUTHORIZED_USERS:
    AUTHORIZED_USERS = [int(user_id) for user_id in AUTHORIZED_USERS.split(',')]
else:
    AUTHORIZED_USERS = []

승인된 사용자만 봇을 사용할 수 있도록 데코레이터를 만듭니다:

def restricted(func):
    """함수 접근을 승인된 사용자로 제한"""
    def wrapper(update: Update, context):
        user_id = update.effective_user.id
        if user_id not in AUTHORIZED_USERS:
            update.message.reply_text("🚫 접근 권한이 없습니다.")
            return
        return func(update, context)
    return wrapper

Tessie API와 통신하기 위한 핵심 함수들을 작성합니다:

def miles_to_km(miles):
    try:
        return miles * 1.60934
    except (TypeError, ValueError):
        return 'N/A'

def get_vehicle_info():
    url = "https://api.tessie.com/vehicles"
    response = requests.get(url, headers=TESSIE_HEADERS)
    if response.status_code == 200:
        data = response.json()
        return data if 'results' in data else None
    return None

def send_vehicle_command(command, params=None):
    url = f"https://api.tessie.com/{VEHICLE_VIN}/command/{command}"
    try:
        response = requests.post(url, headers=TESSIE_HEADERS, params=params)
        if response.status_code == 200:
            data = response.json()
            return data if 'result' in data else None
        return None
    except requests.RequestException as e:
        logger.error(f"Request exception: {e}")
        return None

차량 상태를 조회하는 /status 명령어 핸들러입니다. 배터리, 온도, 위치 등 다양한 정보를 표시합니다:

@restricted
def status(update: Update, context):
    vehicle_info = get_vehicle_info()
    if vehicle_info and 'results' in vehicle_info and len(vehicle_info['results']) > 0:
        vehicle = vehicle_info['results'][0]
        last_state = vehicle.get('last_state', {})
        charge_state = last_state.get('charge_state', {})
        climate_state = last_state.get('climate_state', {})
        drive_state = last_state.get('drive_state', {})
        vehicle_state = last_state.get('vehicle_state', {})
        vehicle_config = last_state.get('vehicle_config', {})

        battery_level = charge_state.get('battery_level', 'N/A')
        battery_range = charge_state.get('battery_range', 'N/A')
        if isinstance(battery_range, (int, float)):
            battery_range = f"{miles_to_km(battery_range):.2f} km"

        latitude = drive_state.get('latitude', 'N/A')
        longitude = drive_state.get('longitude', 'N/A')
        location_text = f"[지도에서 보기](https://www.google.com/maps/search/?api=1&query={latitude},{longitude})" if latitude != 'N/A' else "N/A"

        status_text = f"""
🚗 **차량 이름**: {last_state.get('display_name', 'N/A')}
🔋 **배터리**: {battery_level}% ({battery_range})
⚡️ **충전 상태**: {charge_state.get('charging_state', 'N/A')}
🌡 **실내/실외 온도**: {climate_state.get('inside_temp', 'N/A')}°C / {climate_state.get('outside_temp', 'N/A')}°C
🔒 **잠금**: {'잠김' if vehicle_state.get('locked') else '열림'}
📍 **위치**: {location_text}
🛣 **주행 거리계**: {miles_to_km(vehicle_state.get('odometer', 0)):.0f} km
"""
        update.message.reply_text(status_text, parse_mode='Markdown', disable_web_page_preview=True)
    else:
        update.message.reply_text('차량 정보를 가져올 수 없습니다.')

차량 제어 명령어 핸들러들입니다 (잠금, 서리 제거, 창문 닫기 등):

@restricted
def lock(update: Update, context):
    params = {'retry_duration': 40, 'wait_for_completion': 'true'}
    result = send_vehicle_command('lock', params=params)
    if result and result.get('result'):
        update.message.reply_text('🔒 차량이 잠겼습니다.')
    else:
        update.message.reply_text('차량 잠금에 실패했습니다.')

@restricted
def unlock(update: Update, context):
    params = {'retry_duration': 40, 'wait_for_completion': 'true'}
    result = send_vehicle_command('unlock', params=params)
    if result and result.get('result'):
        update.message.reply_text('🔓 차량 잠금 해제에 성공했습니다.')
    else:
        update.message.reply_text('차량 잠금 해제에 실패했습니다.')

@restricted
def start_defrost(update: Update, context):
    params = {'retry_duration': 40, 'wait_for_completion': 'true'}
    result = send_vehicle_command('start_max_defrost', params=params)
    if result and result.get('result'):
        update.message.reply_text('❄️ 차량의 서리 제거가 시작되었습니다.')
    else:
        update.message.reply_text('서리 제거 시작에 실패했습니다.')

@restricted
def close_windows(update: Update, context):
    params = {'retry_duration': 40, 'wait_for_completion': 'true'}
    result = send_vehicle_command('close_windows', params=params)
    if result and result.get('result'):
        update.message.reply_text('🪟 모든 창문이 닫혔습니다.')
    else:
        update.message.reply_text('창문 닫기에 실패했습니다.')

마지막으로 봇을 시작하는 main() 함수입니다:

def main():
    updater = Updater(TELEGRAM_TOKEN, use_context=True)
    dp = updater.dispatcher

    dp.add_handler(CommandHandler('start', start))
    dp.add_handler(CommandHandler('help', help_command))
    dp.add_handler(CommandHandler('status', status))
    dp.add_handler(CommandHandler('lock', lock))
    dp.add_handler(CommandHandler('unlock', unlock))
    dp.add_handler(CommandHandler('start_defrost', start_defrost))
    dp.add_handler(CommandHandler('close_windows', close_windows))
    dp.add_handler(CommandHandler('open_charge_port', open_charge_port))
    dp.add_handler(CommandHandler('close_charge_port', close_charge_port))

    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()
  1. Procfile 작성하기
worker: python bot.py
  1. .env 파일 작성하기
TELEGRAM_TOKEN=여기에_텔레그램_봇_토큰을_입력하세요
TESSIE_API_KEY=여기에_테슬라_API_키를_입력하세요
VEHICLE_VIN=여기에_차량의_VIN_번호를_입력하세요
AUTHORIZED_USERS=사용자ID1,사용자ID2

주의: .env 파일은 중요한 정보가 포함되어 있으므로 절대 공유하지 마세요.

6단계: Heroku에 배포하기

Git 초기화 및 커밋하기

$ git init
$ git add .
$ git commit -m "Initial commit"

Heroku 앱 생성하기

$ heroku create

Heroku에 배포하기

$ git push heroku master

또는

$ git push heroku main

환경 변수 설정하기

Tip: 터미널에서 Heroku CLI를 사용하여 환경 변수를 설정할 수도 있습니다:

$ heroku config:set TELEGRAM_TOKEN=여기에_텔레그램_봇_토큰을_입력하세요
$ heroku config:set TESSIE_API_KEY=여기에_테슬라_API_키를_입력하세요
$ heroku config:set VEHICLE_VIN=여기에_차량의_VIN_번호를_입력하세요
$ heroku config:set AUTHORIZED_USERS=사용자ID1,사용자ID2

Dyno 시작하기

$ heroku ps:scale worker=1

7단계: Telegram 봇 테스트하기

Telegram에서 봇과 대화하기

명령어 테스트하기

8단계: 에러 처리 및 추가 정보 표시

봇을 사용하면서 발생할 수 있는 다양한 에러 상황을 대비하여 코드를 강화했습니다. 예를 들어, 명령어 실행 실패 시 사용자에게 상세한 정보를 제공합니다.

또한, /status 명령어는 차량의 다양한 정보를 상세하게 표시합니다. 예를 들어, 배터리 수준, 충전 상태, 실내/실외 온도, 차량 위치 등을 한눈에 확인할 수 있습니다.

위 5단계의 send_vehicle_command 함수에서 에러 처리를 강화했습니다. API 응답이 예상과 다를 경우 None을 반환하고, 네트워크 오류도 RequestException으로 처리합니다. /status 명령어에서는 배터리, 온도, 위치 등의 정보를 한눈에 볼 수 있도록 구성했습니다.

9단계: 유지 관리 및 보안

Heroku Dyno 관리하기

$ heroku ps

환경 변수 보호하기

venv/
.env

봇 보안 강화하기


Edit on GitHub
Share this post on:

Previous Post
Modern C++ lambda의 특징과 사용법
Next Post
테슬라 대시 캠, 센트리 캠 영상, NAS로 실시간 백업하기: TeslaUSB 완벽 가이드