1. 언제 쓰나

외부 클라우드가 MQTT(mTLS)로 디바이스 상태를 푸시하는데, SmartThings Edge 드라이버는 허브 샌드박스 안이라 그 장기 연결을 직접 들 수 없을 때 씁니다. 같은 LAN의 AEB가 대신 구독해 주고, 드라이버는 LAN HTTP로 메시지를 받습니다.

  • LG ThinQ, AWS IoT Core 등 mTLS MQTT로 상태를 푸시하는 클라우드 기기.
  • 폴링 대신 실시간 푸시로 상태를 받고 싶은 통합.
  • 단방향 수신만 필요한 경우(명령 전송은 기존 EdgeBridge HTTP API 사용).
먼저 읽기: 프로토콜·REST·메시지 스키마의 정확한 정의는 MQTT 브리지 스펙에 있습니다. 이 페이지는 "드라이버가 무엇을 호출하나"에 집중합니다.

1. When to use it

Use it when an external cloud pushes device state over MQTT (mTLS) but your SmartThings Edge driver — sandboxed in the hub — cannot hold that long-lived connection. AEB on the same LAN subscribes on its behalf, and the driver receives messages over LAN HTTP.

  • Cloud devices that push state over mTLS MQTT — LG ThinQ, AWS IoT Core, etc.
  • Integrations that want real-time push instead of polling.
  • Receive-only needs (for sending commands use the existing EdgeBridge HTTP API).
Read first: the exact protocol, REST, and message schema live in the MQTT bridge spec. This page focuses on "what the driver calls".

2. 드라이버가 해야 할 일 — 5단계

모든 호출은 LAN HTTP로 AEB(http://<aeb-ip>:8088)에 보냅니다. 헤더 X-AEB-Api-Version: 1 필수. 아래 Lua는 참고 구현 EdgeBridgeBaseDriversrc/mqtt_test.lua·src/setup_server.lua에서 발췌했습니다.

2. What the driver does — 5 steps

Every call goes over LAN HTTP to AEB (http://<aeb-ip>:8088) with the header X-AEB-Api-Version: 1. The Lua below is excerpted from the reference driver EdgeBridgeBaseDriver (src/mqtt_test.lua, src/setup_server.lua).

1세션 생성 → CSR 수령Create session → get CSR

POST /mqtt/sessions. 응답에 sessionIdcsrPem이 옵니다. sessionId는 영속 저장(앱 재시작 대비).

POST /mqtt/sessions returns sessionId and csrPem. Persist the sessionId (to survive restarts).

local code, body = http.request("POST", ip, port, "/mqtt/sessions", "{}", API_HEADERS)
local obj = json.decode(body)              -- { sessionId, csrPem, state="CREATED" }
device:set_field("mqtt_session_id", obj.sessionId, { persist = true })
log.info("CSR to sign:\n" .. obj.csrPem)   -- hand csrPem to your CA

2CSR 서명 (외부 CA)Sign the CSR (external CA)

AEB 밖에서 처리합니다. CSR을 대상 서비스의 CA(예: ThinQ, AWS IoT)에 보내 클라이언트 인증서(certPem)를 받습니다. 개인키는 AEB 안에만 있으므로 여기서 다루지 않습니다. (참고 드라이버는 서명된 cert를 certUrl HTTP로 받아옵니다.)

Done outside AEB. Send the CSR to the target service's CA (e.g. ThinQ, AWS IoT) and get back a client certificate (certPem). The private key never leaves AEB, so it is not involved here. (The reference driver fetches the signed cert over HTTP from a certUrl.)

3브로커 접속 + 구독Connect + subscribe

POST /mqtt/sessions/{id}/connect에 서명된 certPem, 브로커 endpoint/port, 구독할 topics, 그리고 정책에 맞는 clientId를 보냅니다.

POST /mqtt/sessions/{id}/connect with the signed certPem, the broker endpoint/port, the topics to subscribe, and a policy-matching clientId.

local conn = json.encode({
  certPem     = signed_cert_pem,
  caPem       = ca_pem,                    -- optional
  endpoint    = "xxxx-ats.iot.ap-northeast-2.amazonaws.com",
  port        = 8883,
  topics      = { "device/+/state", "device/status" },
  qos         = 1,
  keepAliveSec = 60,
  clientId    = "my-device-id",            -- AWS IoT/ThinQ require this
})
http.request("POST", ip, port, "/mqtt/sessions/"..sid.."/connect", conn, API_HEADERS)
-- 200 -> state CONNECTING. Mind mTLS PEM newline escaping (see jesc in the reference driver).

4포워드 타깃 등록Register forward target

PUT /mqtt/sessions/{id}/forward로 드라이버의 in-hub HTTP 서버를 포워드 목적지로 등록합니다. hubAddress를 생략하면 AEB가 요청 소스 IP(=허브)를 자동 사용합니다.

PUT /mqtt/sessions/{id}/forward registers the driver's in-hub HTTP server as the destination. Omit hubAddress and AEB uses the request source IP (= the hub).

local body = json.encode({ hubPort = hub_port, path = "/aeb/ingest" })
local code, rbody = http.request("PUT", ip, port,
  "/mqtt/sessions/"..sid.."/forward", body, API_HEADERS)
-- 200 -> { forwardTarget = "http://<hub>:<port>/aeb/ingest" }
-- 404 SESSION_NOT_FOUND -> session lost on AEB restart; start over from step 1.

5메시지 수신 + 중복제거 + emitReceive + de-dup + emit

AEB가 POST /aeb/ingest로 메시지를 보냅니다. seq로 멱등 중복제거하고, payload를 해석해 capability 이벤트를 emit 합니다. AEB가 불필요하게 재시도하지 않도록 항상 200을 응답하세요.

AEB delivers messages via POST /aeb/ingest. De-dup by seq, interpret payload, and emit capability events. Always return 200 so AEB doesn't retry needlessly.

function M.on_ingest(driver, body)
  local msg = json.decode(body)            -- { sessionId, seq, topic, payload, payloadEncoding, ts }
  local last = seen_seq[msg.sessionId]
  if last and msg.seq <= last then return true end   -- at-least-once: skip dup
  seen_seq[msg.sessionId] = msg.seq
  -- parse msg.payload (utf8/base64) and emit your capability events here
  return true
end

상태 점검은 GET /mqtt/sessions/{id}/status로 폴링합니다 — stateDISCONNECTED면 3번(connect)을 다시 호출해 재접속을 드라이버가 주도합니다. 추가로 forwardTargetHealthy===false면(연결은 살아있지만 배달이 실패 중) 4번(forward 등록)을 즉시 다시 호출하세요 — PUT /forward는 멱등이라 매 폴링 주기마다 불러도 안전합니다.

Poll health with GET /mqtt/sessions/{id}/status — if state is DISCONNECTED, re-issue step 3 (connect); the driver drives reconnection. Also, if forwardTargetHealthy===false (connection alive but delivery failing), re-issue step 4 (register forward) immediately — PUT /forward is idempotent, so calling it every polling cycle is safe.

3. EdgeBridgeBaseDriver로 시작

위 5단계 + LAN 탐색(mDNS) + in-hub HTTP 서버가 통째로 들어 있는 참고 저장소입니다. fork 한 뒤 src/mqtt_test.lua의 메시지 해석 로직만 자기 서비스에 맞게 바꾸면 됩니다.

3. Start from EdgeBridgeBaseDriver

A reference repo that already wires the 5 steps + LAN discovery (mDNS) + an in-hub HTTP server. Fork it and replace only the message-interpretation logic in src/mqtt_test.lua for your service.

EdgeBridgeBaseDriver

MQTT 흐름: mqtt_test.lua (run_flow / do_connect / register_forward / on_ingest), 수신 라우트: setup_server.lua/aeb/ingest.

MQTT flow: mqtt_test.lua (run_flow / do_connect / register_forward / on_ingest); ingest route: /aeb/ingest in setup_server.lua.

GitHub에서 보기View on GitHub

4. 사례: LG ThinQ

LG ThinQ는 AWS IoT 기반 mTLS MQTT로 가전 상태를 푸시합니다. AEB는 ThinQ를 전혀 모르는 범용 브리지이므로, "ThinQ에 맞춰 위 5단계를 어떻게 채우나"가 곧 통합 방법입니다.

  • 인증서 — 1단계 CSR을 ThinQ 계정/디바이스에 등록해 클라이언트 인증서를 발급받습니다. ThinQ(AWS IoT)가 서명자입니다.
  • endpoint / port — ThinQ가 발급한 AWS IoT ATS 엔드포인트, :8883.
  • clientId — ThinQ IoT 정책에 묶인 clientId를 정확히 사용. 불일치 시 CONNACK 타임아웃.
  • topics — 디바이스 shadow/state 토픽 구독. 받은 payload(JSON)를 파싱해 전원·운전·온도 등을 SmartThings capability로 매핑.
참고: 아직 공개된 전용 LG 예제 드라이버는 없습니다. 위는 AEB의 범용 모델에 ThinQ를 끼워 넣는 패턴입니다. 인증·토큰 갱신·토픽 스키마 같은 ThinQ 고유 부분은 각자 구현해야 합니다.

4. Case study: LG ThinQ

LG ThinQ pushes appliance state over AWS IoT-based mTLS MQTT. Since AEB is a generic bridge that knows nothing about ThinQ, "how you fill in the 5 steps for ThinQ" is the integration.

  • Certificate — register the step-1 CSR with your ThinQ account/device to get a client cert. ThinQ (AWS IoT) is the signer.
  • endpoint / port — the AWS IoT ATS endpoint ThinQ gives you, :8883.
  • clientId — use exactly the clientId bound to the ThinQ IoT policy. A mismatch causes a CONNACK timeout.
  • topics — subscribe to the device shadow/state topics. Parse the received JSON payload and map power/mode/temperature to SmartThings capabilities.
Note: there is no published LG-specific example driver yet. The above is the pattern for fitting ThinQ onto AEB's generic model. The ThinQ-specific parts — auth, token refresh, topic schema — are yours to implement.

5. 다른 활용 케이스

mTLS MQTT로 상태를 푸시하는 어떤 클라우드든 같은 패턴으로 붙습니다 — 인증서·엔드포인트·토픽·clientId만 바뀝니다.

5. Other use cases

Any cloud that pushes state over mTLS MQTT fits the same pattern — only the cert, endpoint, topics, and clientId change.

서비스Service 예시 용도Example use
AWS IoT Core직접 운영하는 IoT 기기의 디바이스 shadow/telemetry 수신Device shadow/telemetry from your own IoT fleet
LG ThinQ세탁기·에어컨·냉장고 상태 푸시Washer/AC/fridge state push
Tesla차량 텔레메트리/상태 스트림Vehicle telemetry/state stream
TuyaTuya 스마트홈 기기 상태Tuya smart-home device state
Philips Hue / Nest이벤트 스트림 기반 통합Event-stream-based integrations

각 서비스의 인증 방식·토픽 스키마는 서비스 문서를 따르세요. AEB가 책임지는 건 "구독→포워딩"까지입니다.

Follow each service's docs for its auth and topic schema. AEB owns only the "subscribe → forward" part.

6. 트러블슈팅

GET .../status의 진단 필드로 대부분 원인을 짚을 수 있습니다.

  • CONNACK 타임아웃 / 연결 안 됨effectiveClientId가 의도한 값인지 확인. fallback(aeb-<id>)이면 connect에 clientId를 안 넣은 것. ThinQ/AWS IoT 정책 clientId와 일치해야 함.
  • 같은 clientId 중복 연결liveClientIdConnections가 2 이상이면 같은 clientId로 여러 세션이 떠 서로 evict 중. 세션을 하나만 유지하세요.
  • 실패 원인 일반lastError 문자열 확인(SNI/인증서/타임아웃 등).
  • 메시지가 안 옴state=CONNECTED인지, forwardTarget이 허브의 in-hub 서버를 가리키는지, pendingForwardCount가 쌓이는지(=Wi-Fi/포워딩 문제) 확인.
  • 연결은 정상인데 상태 반영이 계속 늦음(수 분 단위)forwardTargetHealthy가 false거나 forwardFailureCount가 계속 쌓이면, PUT /forward는 성공했지만 실제 배달(POST)이 실패 중인 것입니다(예: 등록된 포트가 재시작 사이 죽어있었음). 이때는 즉시 PUT /forward 재등록으로 복구되는지 확인하세요 — 없으면 자체 폴백 폴링 주기까지 지연이 이어집니다.

6. Troubleshooting

The diagnostic fields of GET .../status pinpoint most causes.

  • CONNACK timeout / won't connect → check effectiveClientId is what you intended. If it's the fallback (aeb-<id>) you didn't send clientId on connect. It must match the ThinQ/AWS IoT policy clientId.
  • Same clientId, multiple connections → if liveClientIdConnections ≥ 2, multiple sessions share a clientId and evict each other. Keep a single session.
  • General failure cause → read the lastError string (SNI/cert/timeout, …).
  • No messages arriving → confirm state=CONNECTED, that forwardTarget points at the hub's in-hub server, and whether pendingForwardCount is piling up (= Wi-Fi/forwarding issue).
  • Connection looks fine but state updates keep lagging (minutes) → if forwardTargetHealthy is false or forwardFailureCount keeps climbing, PUT /forward succeeded but actual delivery (POST) is failing (e.g. the registered port died between restarts). Verify that re-issuing PUT /forward immediately resolves it — otherwise the delay persists until your own fallback polling cycle catches up.