ACM-B1MOS-hosts-MQTT-polling

ACM-B1MOS /etc/hosts MQTT 우회와 Home Assistant 폴링

1. 문서 목적

삼성 에어모니터 ACM-B1MOS가 사용하는 ClearGrass MQTT 서버를 장치의 /etc/hosts에서 로컬 MQTT 브로커로 우회하고, Home Assistant가 MQTT 명령 cid: 200을 이용해 현재 센서값 전체 리포팅을 주기적으로 요청하는 방법을 정리한다.

이 문서는 다음 작업의 결과를 기준으로 작성했다.

  • USB ADB root 접속을 통한 장치 파일과 네트워크 상태 확인
  • SansaApp ARM 바이너리의 IDA Pro 정적 분석
  • acm-b1mos.pcap 및 MQTT 패킷 분석
  • 로컬 MQTT 브로커 연결 시험

작성 및 최종 확인일: 2026-07-26

2. 확인된 장치 및 MQTT 정보

항목
제품Samsung ACM-B1MOS
장치 IP192.168.1.51
로컬 MQTT 브로커 IP192.168.1.146
장치 ID / MQTT Client ID7EF6FAA4016711EBA78F00163E000886
원래 MQTT 서버mqtt.ko.cleargrass.com:1883
HTTPS fallback 서버sansa.cleargrass.com:443
MQTT command topicsansa/command/7EF6FAA4016711EBA78F00163E000886
MQTT data topicsansa/data/7EF6FAA4016711EBA78F00163E000886
MQTT response topicsansa/response/7EF6FAA4016711EBA78F00163E000886

저장된 원본 PCAP 약 69초 구간에서 확인된 외부 목적지는 다음 두 곳이다.

mqtt.ko.cleargrass.com  -> 39.106.170.175:1883
sansa.cleargrass.com    -> 161.117.191.11:443

이 구간에는 Samsung 또는 SmartThings 도메인으로 직접 연결한 기록이 없었다. 장치가 ClearGrass/Sansa backend로 데이터를 보내고, SmartThings와의 연동은 서버 간에 이뤄지는 구조로 추정된다.

별도의 실시간 tcpdump에서는 www.baidu.com DNS 질의도 관찰되었지만, 이는 인터넷 연결 확인 용도로 추정되며 센서 데이터 전송 증거는 아니다.

3. 현재 /etc/hosts 패치 상태

장치의 현재 /etc/hosts에는 다음 레코드가 적용되어 있다.

192.168.1.146 mqtt.ko.cleargrass.com

장치 내부의 패치 전 백업:

/etc/hosts.pre-mqtt-patch

PC에 보관한 패치 전 백업:

D:\잡동사니\localthings\acm-b1mos-hosts.backup

공유기 DNS 우회 기능을 끈 뒤 ADB로 다시 확인한 결과는 다음과 같다.

공유기 DNS 직접 조회:
  mqtt.ko.cleargrass.com -> 39.106.170.175

장치 일반 resolver:
  mqtt.ko.cleargrass.com -> 192.168.1.146

SansaApp 실제 연결:
  192.168.1.51 -> 192.168.1.146:1883 ESTABLISHED

SansaApp을 종료하고 supervisor가 다시 실행하게 한 뒤에도 192.168.1.146:1883으로 재접속하는 것을 확인했다. 따라서 현재 MQTT 우회는 공유기 DNS 설정에 의존하지 않고 장치의 /etc/hosts만으로 동작한다.

4. ADB 상태 확인

Windows에서 ADB 장치 연결을 확인한다.

adb devices

정상 예시:

List of devices attached
aa28d817    device

현재 hosts 파일 확인:

adb shell "cat /etc/hosts"

DNS 서버가 직접 반환하는 값 확인:

adb shell "nslookup mqtt.ko.cleargrass.com"

장치 resolver가 실제로 사용하는 값 확인:

adb shell "ping -c 1 -W 1 mqtt.ko.cleargrass.com"

MQTT 연결 확인:

adb shell "netstat -tnp 2>/dev/null | grep ':1883'"

정상 상태에서는 목적지가 다음과 같아야 한다.

192.168.1.146:1883 ESTABLISHED .../SansaApp

nslookup은 DNS 서버에 직접 질의하므로 /etc/hosts를 반영하지 않을 수 있다. 실제 애플리케이션의 일반 hostname resolver 동작은 ping과 연결 socket으로 확인한다.

5. /etc/hosts 패치 절차

5.1 원본 백업

장치 내부 백업:

adb shell "cp /etc/hosts /etc/hosts.pre-mqtt-patch"

PC 백업:

adb pull /etc/hosts acm-b1mos-hosts.backup

5.2 PC에서 hosts 파일 편집

백업 파일을 복사한 뒤 마지막에 다음 줄을 추가한다.

192.168.1.146 mqtt.ko.cleargrass.com

브로커 IP가 변경되었다면 192.168.1.146 대신 실제 브로커 IP를 사용한다.

5.3 장치에 적용

adb push acm-b1mos-hosts.patched /tmp/hosts.patched
adb shell "cp /tmp/hosts.patched /etc/hosts && chmod 664 /etc/hosts && sync"

5.4 SansaApp 재접속

현재 PID 확인:

adb shell "pidof SansaApp"

출력된 PID를 종료한다.

adb shell "kill <SANSAAPP_PID>"

장치의 supervisor가 SansaApp을 다시 실행한다. 약 10~30초 후 다음 명령으로 로컬 브로커 재접속을 확인한다.

adb shell "pidof SansaApp"
adb shell "netstat -tnp 2>/dev/null | grep ':1883'"

5.5 원상복구

adb shell "cp /etc/hosts.pre-mqtt-patch /etc/hosts && chmod 664 /etc/hosts && sync"

그 후 SansaApp을 다시 시작하거나 장치를 재부팅한다.

장치는 overlay filesystem을 사용하므로 일반 재부팅 후에는 유지될 가능성이 높지만, 공장 초기화나 펌웨어 업데이트 후에는 /etc/hosts 패치를 다시 확인해야 한다.

6. MQTT 명령 파서 분석 결과

6.1 CID 중복 판정

펌웨어는 처리한 CID 전체 목록을 저장하지 않는다. RAM에 마지막 일반 CID 하나만 기억하고 바로 이전 CID와 같을 때만 무시한다.

1 -> 2 -> 1 -> 2

처럼 두 값을 교대로 사용해도 계속 처리된다.

  • CID 유효시간 또는 만료시간은 없음
  • SansaApp 시작 시 마지막 CID는 -1로 초기화
  • 장치 재시작 후 CID 기록은 유지되지 않음
  • signed 32-bit 정수로 처리

예약 CID:

CID기능
100MQTT 데이터 처리 완료 애플리케이션 ACK
200현재 센서값 전체 동기화 요청

6.2 cid: 200 동작

다음 메시지를 command topic에 발행하면 장치는 전체 리포팅 카운터를 강제로 만료 상태로 만든 뒤 slot_sync_data()를 즉시 호출한다.

{"cid":200}

이 명령은 센서를 물리적으로 새로 측정하라는 sample 명령과 다르다.

  • 현재 장치가 보유한 센서값을 즉시 전체 payload로 조립
  • 평상시 변화 감지 필터를 우회해 모든 사용 가능한 필드를 포함
  • 각 센서의 실제 측정 주기는 변경하지 않음
  • 물리 센서값이 아직 갱신되지 않았다면 이전 값과 동일할 수 있음

6.3 cid: 200 반복 시 주의점

200을 연속으로 보내면 두 번째 명령부터 마지막 CID와 같아 무시된다. ACK용 cid: 100은 특수 분기에서 처리되어 마지막 일반 CID를 변경하지 않는다.

따라서 다음 순서는 반복 폴링에 사용할 수 없다.

200 -> ACK 100 -> 200

반복하려면 기능이 없는 일반 CID를 사이에 넣는다.

201 -> 200 -> 201 -> 200

{"cid":201}은 특별한 명령을 실행하지 않고 마지막 CID를 바꾸는 용도로 사용할 수 있다. response topic에 OK 응답이 생길 수 있지만 센서 data topic에는 영향을 주지 않는다.

두 메시지 모두 Retain을 사용하면 안 된다.

7. MQTT Explorer 수동 시험

구독:

sansa/data/7EF6FAA4016711EBA78F00163E000886
sansa/response/7EF6FAA4016711EBA78F00163E000886

발행 topic:

sansa/command/7EF6FAA4016711EBA78F00163E000886

발행 순서:

{"cid":201}

약 0.2초 후:

{"cid":200}

설정:

QoS: 0
Retain: OFF

정상이라면 data topic으로 sensor_data가 포함된 전체 리포트가 들어온다.

8. Home Assistant 30초 폴링 자동화

Home Assistant의 자동화 생성 화면에서 YAML 편집 모드로 전환한 뒤 다음 내용을 붙여 넣는다.

alias: 에어모니터 30초 전체값 폴링
description: "30초마다 ACM-B1MOS의 현재 센서값 전체 MQTT 리포팅 요청"
mode: single

triggers:
  - trigger: time_pattern
    seconds: "/30"

conditions: []

actions:
  - action: mqtt.publish
    data:
      topic: "sansa/command/7EF6FAA4016711EBA78F00163E000886"
      payload: '{"cid":201}'
      qos: 0
      retain: false

  - delay:
      milliseconds: 250

  - action: mqtt.publish
    data:
      topic: "sansa/command/7EF6FAA4016711EBA78F00163E000886"
      payload: '{"cid":200}'
      qos: 0
      retain: false

실행 흐름:

매 00초, 30초
  -> cid 201 발행: 마지막 CID 변경
  -> 250ms 대기
  -> cid 200 발행: 전체 센서값 즉시 리포팅 요청
  -> 장치가 data topic으로 payload 발행

30초마다 전체 payload를 받더라도 각 센서의 실제 측정값이 반드시 30초마다 변하는 것은 아니다.

9. MQTT 애플리케이션 ACK 선택 사항

장치는 data topic으로 값을 발행한 뒤 약 2초 동안 다음 애플리케이션 ACK를 기다린다.

{"cid":100,"code":0}

원래 ClearGrass MQTT backend가 데이터 처리를 완료하면 이 응답을 보내는 구조다. ACK를 받지 못하면 장치는 동일 데이터를 다음 HTTPS endpoint로 다시 전송한다.

https://sansa.cleargrass.com/v2/sansa/data/new_report

SmartThings와 HA를 함께 유지하려는 경우

ACK 자동화를 만들지 않는 것을 기본값으로 권장한다.

장치 -> 로컬 MQTT -> HA 수신
                   -> ACK 없음
약 2초 후 장치 -> ClearGrass HTTPS fallback

SmartThings가 ClearGrass backend의 데이터를 이용하는 구조라면 기존 SmartThings 갱신을 계속 유지할 가능성이 높다.

완전 로컬 전용으로 사용하는 경우

HTTPS fallback을 막으려면 다음 ACK 자동화를 추가할 수 있다.

alias: 에어모니터 MQTT ACK
description: "로컬 MQTT 수신 후 ClearGrass HTTPS fallback 방지"
mode: queued
max: 10

triggers:
  - trigger: mqtt
    topic: "sansa/data/7EF6FAA4016711EBA78F00163E000886"

conditions: []

actions:
  - action: mqtt.publish
    data:
      topic: "sansa/command/7EF6FAA4016711EBA78F00163E000886"
      payload: '{"cid":100,"code":0}'
      qos: 0
      retain: false

이 자동화를 활성화하면 해당 리포트의 HTTPS fallback이 중단되므로 기존 SmartThings 값이 갱신되지 않을 가능성이 높다.

10. 관련 명령 분석 참고

start_job

{
  "cid": 1201,
  "method": "start_job",
  "meta_data": {
    "duration": 86400,
    "rate": 5
  }
}

start_job은 명령을 받으면 rate 값과 관계없이 slot_sync_data()를 즉시 한 번 호출한다. 그래서 테스트 시 바로 한 번 리포팅되는 것이 정상이다.

rate는 이후 내부 타이머가 현재보다 빠를 때만 변경한다. 기본 타이머는 10초이므로 rate: 10은 사실상 변화가 없다. 타이머를 줄여도 변화 감지 필터 때문에 매번 전체 payload가 발행되는 것은 아니다.

rate: 0은 0ms 타이머가 될 위험이 있으므로 사용하지 않는다.

sample

펌웨어의 MQTT parser와 sample handler 사이에 JSON 구조 불일치 버그가 있다.

  • parser는 meta_data만 내부 객체에 복사
  • handler는 내부 객체의 최상위 params를 검색
  • 최상위 params를 MQTT payload에 넣어도 parser가 복사하지 않음

따라서 현재 바이너리에서는 MQTT JSON 조합만으로 sample 명령을 정상 실행할 수 없다. handler 또는 parser 바이너리 수정이 필요하다.

11. MQTT debug 설정

MqttApp::slot_change_debug(bool)은 현재 펌웨어에서 빈 함수다. 이 토글은 MQTT 서버, 토픽, 주기, QoS 또는 publish 동작을 바꾸지 않는다.

숨겨진 메뉴의 DEBUG_AND_TEST는 별도 기능이다.

  • 전체 장치 로그 암호화 비활성화
  • 센서 및 통신 상세 로그 기록
  • MQTT hostname, client ID, username/password, payload가 평문 로그에 기록될
    수 있음
  • HTTP fallback URL 및 요청 내용도 기록될 수 있음

분석할 때만 활성화하고 평소에는 끄는 것이 안전하다. ADB debug는 adbd 서비스를 켜는 별도 기능이며 MQTT 동작에는 영향을 주지 않는다.

12. 확인 체크리스트

  • adb devices에서 장치가 device 상태로 표시됨
  • /etc/hosts에 로컬 브로커 레코드가 있음
  • nslookup은 공인 IP, ping은 로컬 IP를 표시함
  • SansaApp 재시작 후 로컬 브로커 1883에 연결됨
  • MQTT Explorer에서 201 -> 200 발행 후 전체 data payload 수신
  • HA 30초 폴링 자동화 활성화
  • 센서 엔티티가 약 30초마다 새 MQTT 메시지를 처리함
  • SmartThings 병행 여부에 따라 ACK 자동화 사용 여부 결정

13. 보안 주의 사항

  • MQTT TCP 1883은 평문이므로 반드시 신뢰할 수 있는 내부 LAN에서만 사용한다.
  • 장치 MQTT username/password가 들어 있는 PCAP과 로그를 공개 저장소에
    커밋하지 않는다.
  • DEBUG_AND_TEST 로그에는 MQTT 인증정보가 평문으로 남을 수 있다.
  • /etc/hosts 변경 전 원본 백업을 유지한다.
  • 공장 초기화나 펌웨어 업데이트 후에는 hosts 패치와 MQTT 연결을 다시
    확인한다.