← 개발자 참고로 ← Back to Developer notes
AEB MQTT 브리지에 붙는 SmartThings Edge 드라이버를 어떻게 개발하는가 — 무엇을, 어떤 순서로.
외부 클라우드가 MQTT(mTLS)로 디바이스 상태를 푸시하는데, SmartThings Edge 드라이버는 허브 샌드박스 안이라 그 장기 연결을 직접 들 수 없을 때 씁니다. 같은 LAN의 AEB가 대신 구독해 주고, 드라이버는 LAN HTTP로 메시지를 받습니다.
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.
모든 호출은 LAN HTTP로 AEB(http://<aeb-ip>:8088)에 보냅니다. 헤더 X-AEB-Api-Version: 1 필수. 아래 Lua는 참고 구현 EdgeBridgeBaseDriver의 src/mqtt_test.lua·src/setup_server.lua에서 발췌했습니다.
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).
POST /mqtt/sessions. 응답에 sessionId와 csrPem이 옵니다. 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
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.)
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).
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.
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로 폴링합니다 — state가 DISCONNECTED면 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.
위 5단계 + LAN 탐색(mDNS) + in-hub HTTP 서버가 통째로 들어 있는 참고 저장소입니다. fork 한 뒤 src/mqtt_test.lua의 메시지 해석 로직만 자기 서비스에 맞게 바꾸면 됩니다.
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.
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.
LG ThinQ는 AWS IoT 기반 mTLS MQTT로 가전 상태를 푸시합니다. AEB는 ThinQ를 전혀 모르는 범용 브리지이므로, "ThinQ에 맞춰 위 5단계를 어떻게 채우나"가 곧 통합 방법입니다.
:8883.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.
:8883.mTLS MQTT로 상태를 푸시하는 어떤 클라우드든 같은 패턴으로 붙습니다 — 인증서·엔드포인트·토픽·clientId만 바뀝니다.
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 | |
| Tuya | Tuya 스마트홈 기기 상태 | 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.
GET .../status의 진단 필드로 대부분 원인을 짚을 수 있습니다.
effectiveClientId가 의도한 값인지 확인. fallback(aeb-<id>)이면 connect에 clientId를 안 넣은 것. ThinQ/AWS IoT 정책 clientId와 일치해야 함.liveClientIdConnections가 2 이상이면 같은 clientId로 여러 세션이 떠 서로 evict 중. 세션을 하나만 유지하세요.lastError 문자열 확인(SNI/인증서/타임아웃 등).state=CONNECTED인지, forwardTarget이 허브의 in-hub 서버를 가리키는지, pendingForwardCount가 쌓이는지(=Wi-Fi/포워딩 문제) 확인.forwardTargetHealthy가 false거나 forwardFailureCount가 계속 쌓이면, PUT /forward는 성공했지만 실제 배달(POST)이 실패 중인 것입니다(예: 등록된 포트가 재시작 사이 죽어있었음). 이때는 즉시 PUT /forward 재등록으로 복구되는지 확인하세요 — 없으면 자체 폴백 폴링 주기까지 지연이 이어집니다.The diagnostic fields of GET .../status pinpoint most causes.
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.liveClientIdConnections ≥ 2, multiple sessions share a clientId and evict each other. Keep a single session.lastError string (SNI/cert/timeout, …).state=CONNECTED, that forwardTarget points at the hub's in-hub server, and whether pendingForwardCount is piling up (= Wi-Fi/forwarding issue).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.