AI 기반 서비스

Today
-
Total
-
← 목록으로

클라우드가 잠든 새벽 6시 - Tapo 로컬 제어로 전환한 이유

이창연•2026년 2월 5일

클라우드가 잠든 새벽 6시

이번엔 진짜 됐다고 생각했다

지난번 버그 4개를 모두 고쳤다. OFF/ON 분리 세션, 상태 검증, 10분 재시도, 인증 모니터링까지. Watchdog v5.0, tapo-spark.py v2.0.

그리고 2월 5일 새벽 3시 13분. DGX Spark가 또 꺼졌다.

새벽 3시: 완벽한 복구

텔레그램 알림이 왔다:

⚡️ Spark 전원 꺼짐 감지
• Tapo P110M 전원 사이클 실행 중...
• 유형: 최초 시도
• 플러그 IP: 192.168.50.77

watchdog 로그를 보면:

[03:13:32] WARN: CraftBay MCP 헬스체크 실패 (연속 1 회)
[03:13:34] WARN: Spark 서버 ping 실패 - 전원 꺼짐 추정
[03:13:35] TAPO: 전원 사이클 시작
[03:13:42] TAPO: 전원 사이클 완료 - 플러그 ON 확인됨
[03:13:42] TAPO: 전원 사이클 성공, 90초 부팅 대기 필요

7초 만에 전원 사이클 완료. 상태 검증까지 통과. 그리고 10분 뒤 재시도:

[03:23:33] TAPO: 전원 사이클 시작
[03:23:40] TAPO: 전원 사이클 완료 - 플러그 ON 확인됨
[03:24:32] INFO: CraftBay MCP 복구됨 (이전 실패 횟수: 11, 복구방식: Tapo 전원제어)

완벽했다. v2.0 스크립트가 의도한 대로 동작했다. 분리 세션, 상태 검증, 10분 재시도 — 모든 개선점이 제 역할을 했다.

이때까지만 해도 "드디어 자동 복구 시스템이 완성됐다"고 생각했다.

새벽 6시: 같은 코드, 다른 결과

3시간 뒤인 새벽 6시. Spark가 또 꺼졌다. 하룻밤에 두 번.

⚡️ Spark 전원 꺼짐 감지
• Tapo P110M 전원 사이클 실행 중...
• 유형: 최초 시도
• 플러그 IP: 192.168.50.77

🔴 Tapo 전원 사이클 실패
• 플러그 ON 상태 확인 불가
• 수동 확인 필요

같은 코드가 3시간 전엔 성공하고, 지금은 실패한다.

watchdog 로그를 자세히 보면:

[06:00:36] TAPO: 전원 사이클 시작
[06:00:36] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:00:36] TAPO: 안전장치 - 명시적 ON 시도
[06:00:38] ERROR: 전원 사이클 후 플러그 상태 비정상 (null) - 강제 ON 재시도
[06:00:40] ERROR: 강제 ON 재시도도 실패 (null)

핵심은 null. 플러그 상태가 true도 false도 아니라 null이다.

status_json 명령이 {"device_on": null}을 반환했다는 것은, 플러그에 연결 자체가 안 됐다는 뜻이다.

범인: TP-Link 클라우드

tapo-spark.py v2.0의 get_device() 함수:

async def get_device():
    client = ApiClient(TAPO_EMAIL, TAPO_PASSWORD)
    return await client.p110(PLUG_IP)

ApiClient는 tapo 라이브러리(0.8.8)의 클라이언트다. 이 라이브러리는 로컬 IP로 접속하는 것처럼 보이지만, 실제로는 TP-Link 클라우드에 먼저 인증한 뒤 로컬 디바이스에 연결하는 구조다.

miniPC → TP-Link Cloud (인증) → Tapo P110M (로컬)

새벽 3시에는 클라우드가 응답했고, 6시에는 응답하지 않았다.

3회 연속 시도(6:00, 6:01, 6:02) 모두 같은 패턴으로 실패:

[06:00:36] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:01:34] ERROR: Tapo cycle 명령 실패 (exit code: 0)
[06:02:35] ERROR: Tapo cycle 명령 실패 (exit code: 0)

10분 간격 재시도 로직이 작동했지만, 근본 원인이 클라우드 인증 실패이니 몇 번을 시도해도 결과는 같다.

6시 3분, 내가 Tapo 앱으로 직접 플러그를 켰다. Spark 부팅, 서비스 복구. 또다시 수동 개입.

구조적 문제

지금까지의 장애 히스토리를 돌아보면:

날짜원인복구 실패 이유
1/30WOL 미지원하드웨어 한계
1/31DHCP IP 변경네트워크 한계
2/2스크립트 버그내 코드 문제
2/5클라우드 인증외부 의존성

코드 버그는 고칠 수 있다. 하드웨어 한계는 우회할 수 있다. 그런데 외부 클라우드 서비스의 가용성은 내가 통제할 수 없다.

IoT 디바이스가 클라우드에 의존한다는 건, 인터넷이 끊기거나 클라우드 서버가 점검 중이면 내 집 안에 있는 플러그도 제어 못 한다는 뜻이다. 서버 자동 복구라는 미션 크리티컬한 용도에 이런 구조는 맞지 않다.

해결: 로컬 제어로 전환

Tapo P110M은 로컬 프로토콜(KLAP)을 지원한다. 다만 기본적으로 잠겨있다.

시도 1: python-kasa

TP-Link 계열 로컬 제어의 사실상 표준 라이브러리. 설치 후 테스트:

$ python3 -c "from kasa import Discover; ..."

kasa.exceptions.UnsupportedDeviceError: Unsupported device 192.168.50.77
of type SMART.TAPOPLUG with encrypt_scheme
EncryptionScheme(encrypt_type='TPAP', http_port=80, lv=2)

P110M이 사용하는 TPAP 암호화 프로토콜을 python-kasa가 아직 지원하지 않는다. 탈락.

시도 2: tapo 라이브러리 로컬 모드

같은 tapo 0.8.8 라이브러리인데, 실은 로컬 KLAP 핸드셰이크를 먼저 시도한다. 다만:

Exception: Tapo(Forbidden { code: "FORBIDDEN",
description: "Make sure Third-Party Compatibility is turned on
in the Tapo app." })

"타사 호환성(Third-Party Compatibility)" 설정이 꺼져있어서 403으로 차단됐다.

이건 Tapo 앱에서 딱 한 번만 켜주면 된다:

나 → 타사 서비스 → 타사 호환성 ON

이 설정을 켜면 로컬 네트워크에서 직접 KLAP 프로토콜로 통신할 수 있다. 클라우드를 완전히 우회한다:

Before: miniPC → TP-Link Cloud (인증) → Tapo P110M
After:  miniPC → Tapo P110M (직접)

적용 결과

타사 호환성을 켜고 테스트:

$ python3 -c "..."
Device: 서버 플러그
On: True
로컬 연결 성공!

같은 코드, 같은 라이브러리. 클라우드를 거치지 않고 로컬 KLAP으로 직접 연결된다.

코드 변경은 0줄. Tapo 앱에서 설정 하나 켠 것이 전부다.

개선해야 할 것들

  1. 타사 호환성 켜기 → 완료 (로컬 연결 테스트 성공 확인)
  2. Spark가 하룻밤에 2번 꺼지는 근본 원인 — 과부하? 과열? 전원? 아직 모른다
  3. python-kasa TPAP 지원 모니터링 — 지원되면 전환 고려 (커뮤니티가 더 큼)

교훈

미션 크리티컬한 자동화에 클라우드 의존성을 넣지 마라.

IoT가 편리한 건 클라우드 덕분이고, 불안한 것도 클라우드 때문이다. "보통은 잘 되니까"로 넘기면, 안 되는 그 한 번이 새벽 6시에 찾아온다.

로컬에서 할 수 있으면 로컬로 하자. 특히 서버 전원 같은 건.


이 시리즈 이전 글: