SESE Controller

ESP-IDF 기반 ESP32-Ethernet-Kit 및 ESP32-C3-SUPERMINI 컨트롤러입니다. 최초 설정은 별도 앱이나 PoP 없이 스마트폰 브라우저에서 진행하며, 연결 후에는 Wi-Fi station과 HTTPS OTA를 사용합니다.

최초 Wi-Fi 설정

  1. 전원을 켭니다.
  2. 스마트폰 Wi-Fi에서 SESE_XXXXXX를 선택합니다. SoftAP 비밀번호는 없습니다.
  3. captive portal이 자동으로 열리지 않으면 브라우저에서 http://192.168.4.1을 엽니다.
  4. 검색 목록에서 Wi-Fi를 선택합니다. 숨겨진 네트워크는 숨겨진 Wi-Fi 직접 입력을 선택하고 SSID를 대소문자까지 정확히 입력합니다.
  5. 해당 Wi-Fi 비밀번호를 입력합니다.
  6. 장치가 실제로 IP 주소를 받은 뒤에만 설정을 NVS에 저장합니다.
  7. 성공 결과를 잠시 표시한 다음 HTTP/DNS server와 SoftAP을 종료하고 station mode를 유지합니다.

설정 실패 시 후보 비밀번호는 저장하지 않으며 portal을 유지해 다시 시도할 수 있습니다. Wi-Fi 비밀번호는 UART 로그나 HTTP 응답에 출력하지 않습니다. SSID 32바이트와 64바이트 hex PSK도 배열 전체를 길이 기반으로 복사해 지원합니다.

Provisioning 정책

  • SoftAP: SESE_XXXXXX, open authentication, 최대 접속자 1명
  • Portal: DNS redirect + DHCP captive-portal URI(Option 114)
  • 직접 접속 주소: http://192.168.4.1
  • API:
    • GET /api/v1/networks
    • GET /api/v1/status
    • POST /api/v1/config
  • 설정 window: 10분
  • 저장소: application-owned NVS namespace wifi_cfg의 versioned blob
  • ESP Wi-Fi 자체 저장소: WIFI_STORAGE_RAM
  • 설정 성공 후 SoftAP 종료
  • 일반 station 연결이 5회 연속 실패하면 기존 설정을 삭제하지 않고 제한된 복구 portal 시작

ESP SoftAP Provision 앱, esp_prov.py, Protocomm 및 PoP는 제품 설정 절차에 사용하지 않습니다.

Wi-Fi 설정 초기화

실행 중 BOOT 버튼을 5초 이상 누르면 wifi_cfg만 삭제하고 재부팅하여 browser provisioning으로 돌아갑니다. ESP32-Ethernet-Kit은 GPIO0, ESP32-C3-SUPERMINI는 GPIO9을 사용합니다. 다른 NVS 데이터와 OTA 파티션은 지우지 않습니다.

개발 시 전체 flash 초기화가 필요한 경우에만 다음 명령을 사용합니다.

source ~/esp/esp-idf/export.sh
idf.py -p /dev/ttyUSB1 erase-flash
idf.py -p /dev/ttyUSB1 flash

erase-flash는 Wi-Fi 설정 외 데이터도 모두 삭제하므로 제품의 일반 재설정 절차가 아닙니다.

빌드·다운로드·모니터

ESP32-Ethernet-Kit

source ~/esp/esp-idf/export.sh
idf.py build
idf.py -p /dev/ttyUSB1 flash
idf.py -p /dev/ttyUSB1 monitor
  • ESP-IDF: v6.0.2
  • 대상: esp32
  • Flash: 4MB
  • 다운로드 baudrate: 460800
  • UART: 115200

ESP32-C3-SUPERMINI

ESP32용 sdkconfig와 빌드 결과를 덮어쓰지 않도록 별도의 설정과 build directory를 사용합니다.

source ~/esp/esp-idf/export.sh
idf.py -B build-esp32c3 -D SDKCONFIG=sdkconfig.esp32c3 set-target esp32c3
idf.py -B build-esp32c3 -D SDKCONFIG=sdkconfig.esp32c3 build

최초 다운로드 시 BOOT(GPIO9)을 누른 상태에서 RESET한 뒤 /dev/ttyACM0이 나타나면 다음과 같이 기록합니다.

esptool --chip esp32c3 --port /dev/ttyACM0 --baud 460800 \
  --before no-reset --after hard-reset write-flash \
  --flash-mode dio --flash-size 4MB --flash-freq 80m \
  0x0 build-esp32c3/bootloader/bootloader.bin \
  0x8000 build-esp32c3/partition_table/partition-table.bin \
  0xf000 build-esp32c3/ota_data_initial.bin \
  0x20000 build-esp32c3/sese-controller.bin

다운로드 후 ROM의 waiting for download 상태가 유지되면 BOOT을 누르지 않은 상태에서 USB 전원을 다시 연결합니다. C3 설정은 native USB Serial/JTAG console과 GPIO9 BOOT long-press를 사용합니다.

  • 검증 보드: ESP32-C3 revision v0.4
  • 실제 Flash: 4 MiB (JEDEC 20:4016)
  • USB: Espressif USB Serial/JTAG (303a:1001)
  • console: /dev/ttyACM0, 115200 baud

Portal 시작 시 firmware가 /, /api/v1/status, /api/v1/networks에 대한 로컬 HTTP self-test를 한 번 실행합니다. 성공 로그는 다음과 같습니다.

Captive portal HTTP and Wi-Fi scan API self-test passed

NTP 시간 동기화

Wi-Fi에서 IP를 받은 뒤 SNTP를 시작해 시스템 시간을 동기화합니다.

기본 설정:

  • NTP server: pool.ntp.org
  • timezone: KST-9 (UTC+09:00)
  • 최초 동기화 대기: 30초
  • 이후 주기 동기화: ESP-IDF SNTP 주기(현재 1시간)

idf.py menuconfig의 SESE Controller 메뉴에서 server, POSIX timezone 및 최초 대기 시간을 변경할 수 있습니다. 시스템 epoch는 UTC로 유지하고, 사람이 읽는 로그만 설정한 timezone으로 표시합니다. 최초 요청이 timeout되어도 background SNTP 재시도는 계속되며 OTA worker는 기존 재시도 정책을 유지합니다.

정상 로그 예시:

NTP time synchronized: 2026-08-31T...+0900
Initial system time ready: 2026-08-31T...+0900

MQTT client

Wi-Fi 및 최초 NTP 처리 후 MQTT client를 시작합니다. broker URI가 비어 있으면 기능은 안전하게 비활성화됩니다. mqtts://와 wss:// URI에는 ESP-IDF certificate bundle 및 hostname 검증을 적용합니다.

idf.py menuconfig의 SESE Controller 메뉴:

  • Enable MQTT client
  • MQTT broker URI
  • MQTT heartbeat interval (seconds) (기본 60초)

broker URI에 username, password, token 등 credential을 넣지 마십시오. 현재 구현은 broker credential을 소스·로그·build 설정에 저장하지 않습니다.

장치 ID는 Wi-Fi Station MAC의 마지막 3바이트로 만든 SESE_XXXXXX 형식입니다. 장치 SESE_5F2A8C의 topic 예시는 다음과 같습니다.

Topic 방향 QoS/retain Payload
sese/SESE_5F2A8C/status publish QoS 1, retained online 상태·장치 ID·firmware version. 비정상 연결 종료 시 LWT {"online":false}
sese/SESE_5F2A8C/heartbeat publish QoS 0 장치 ID·Unix timestamp·uptime seconds
sese/SESE_5F2A8C/command/ping subscribe QoS 1 정확히 ping
sese/SESE_5F2A8C/response/pong publish QoS 1 pong 응답·장치 ID·Unix timestamp

연결이 끊기면 5초 후 자동 재연결하며, 재연결 때 status와 heartbeat를 즉시 다시 발행합니다.

로컬 Mosquitto contract test 환경:

cd test/mqtt
docker compose up -d
# 테스트가 끝나면 LAN에 공개된 anonymous listener를 즉시 종료합니다.
docker compose down

test/mqtt 설정은 실제 장치가 LAN에서 접근하는 일회성 통합 테스트 전용이며 운영 환경에 사용하지 않습니다.

자동 HTTPS OTA

4MB flash partition:

  • ota_0: 1920KB
  • ota_1: 1920KB
  • otadata: 활성 slot 및 rollback 상태
  • nvs: Wi-Fi 설정
  • storage: application data

설정:

idf.py menuconfig

SESE Controller 메뉴:

  • Enable automatic HTTPS OTA
  • HTTPS OTA firmware URL
  • 최초 확인 지연, 정기 확인 간격, 실패 재시도 간격

URL이 비어 있으면 OTA worker는 비활성 상태입니다. URL에 token, password 또는 API key를 포함하지 마십시오. 서버는 ESP-IDF application binary를 직접 반환하고 공인 CA의 유효한 HTTPS 인증서를 사용해야 합니다.

릴리스 절차:

  1. CMakeLists.txt의 PROJECT_VER를 증가시킵니다.
  2. idf.py build를 실행합니다.
  3. build/sese-controller.bin을 HTTPS endpoint에 원자적으로 배포합니다.
  4. 장치는 project name과 version을 검증한 뒤 inactive OTA slot에 설치합니다.
  5. 새 firmware가 Wi-Fi 연결 진단을 통과해야 rollback 상태를 해제합니다.

동일하거나 낮은 버전, 다른 project name, 불완전하거나 검증에 실패한 이미지는 거부합니다.

보안 경계

현재 onboarding은 사용성을 위해 open SoftAP + HTTP를 사용합니다. 주변 공격자가 무선 트래픽을 관찰하거나 설정을 시도할 수 있으며 nonce가 암호화나 소유 증명이 되지는 않습니다.

적용한 완화책:

  • 미설정 장치에서만 자동 portal 진입
  • 이미 설정된 장치는 BOOT 5초 long-press로 재설정
  • 10분 timeout
  • SoftAP 접속자 1명 제한
  • boot별 random nonce로 설정 POST 검증
  • request body 및 SSID/password 길이 제한
  • 동시 scan/설정 요청 제한
  • 실제 연결 성공 후에만 NVS 저장
  • 성공 후 HTTP/DNS/SoftAP 종료
  • 비밀번호 비로깅·비반향 및 임시 buffer 정리

양산 threat model에서는 NVS encryption, flash encryption, secure boot 및 provisioning transport 암호화를 별도로 검토해야 합니다.

Architecture and verification

The reusable components/kesp32-app-base component owns NVS initialization, browser provisioning, Wi-Fi readiness, NTP, HTTPS OTA/rollback, reset-button policy, and generic MQTT transport. Its public entry point is esp32_app_base_start() in include/esp32_app_base.h; consumers provide board reset settings, network/product-health callbacks, and product MQTT topics/payloads. main/app_main.c owns one-time integration startup and main/sese_product.c owns SESE identity, topics, payloads, and health policy. examples/minimal_consumer demonstrates a second consumer without source copies.

Partitions, sdkconfig, target selection, service identity, and product callbacks are consumer-owned. Existing compatibility Kconfig symbols retain the SESE_* prefix. Runtime Wi-Fi, MQTT, and OTA behavior remains hardware-gated and was not claimed from compilation.

The host test in test/host compiles the actual extracted OTA major.minor.patch comparator used by firmware. Run cd test/host && ./run_tests.sh. The acceptance build evidence is kept in build-verify-esp32/ and build-verify-esp32c3/, with target-specific binaries and log/ output; no hardware flash, reset, serial, Wi-Fi, MQTT, or OTA exercise is claimed.

ESP-IDF 6.0.2's local esp_https_ota sources do not expose the removed partial-download initializer fields in the installed headers; the component therefore does not hard-code them. Partial-download behavior, when enabled by a future/alternate SDK configuration, remains SDK-owned and must be checked against that SDK's header/Kconfig before enabling.

S
Description
No description provided
Readme
35 KiB
Languages
C 82%
Shell 10.1%
CMake 7.9%