- OTA Update๋ ๊ธฐ๊ธฐ์ ํ์จ์ด/์ํํธ์จ์ด๋ฅผ ์ ์ ์ฐ๊ฒฐ ์์ด Wi-Fiยท์ ๋ฃฐ๋ฌ ๋ฑ ๋ฌด์ ๋คํธ์ํฌ๋ก ๊ฐฑ์ ํ๋ ๋ฐฉ์
- ์๋ฒ ๋๋ยทIoT ๊ธฐ๊ธฐ ํ์ ์ผ๋ก๋ **FOTA(Firmware Over-The-Air)**๋ผ๊ณ ๋ถ๋ฅด๋ ๋ฐฐํฌ ๊ธฐ๋ฒ
- ์ฌ์ฉ์๊ฐ USB ์ผ์ด๋ธยท์ ์ฉ ํด ์์ด ์๊ฒฉ์์ ๋๊ท๋ชจ๋ก ๋ฐฐํฌ ๊ฐ๋ฅํ ์ ๋ฐ์ดํธ ์ฑ๋
- ๋ค์ด๋ก๋ โ ๊ฒ์ฆ โ ์ฌ๋ถํ โ ์๊ฐ์ง๋จ โ ํ์ /๋กค๋ฐฑ์ผ๋ก ์ด์ด์ง๋ ๋ฌด์ค๋จยท๋ณต๊ตฌ๊ฐ๋ฅ(power-safe) ๊ฐฑ์ ์ ์ฐจ
ํด๋น ๊ฐ๋ ์ด ํ์ํ ์ด์
- ํ์ฅ์ ์ด๋ฏธ ๋ฐฐํฌ๋ ์์ญ~์๋ง ๋์ ๊ธฐ๊ธฐ๋ฅผ ์ผ์ผ์ด ํ์ํ๊ฑฐ๋ USB๋ก ๋ฌผ๋ฆฌ ์ฐ๊ฒฐํ์ง ์๊ณ ๋ฒ๊ทธ ์์ ยท๊ธฐ๋ฅ ์ถ๊ฐ๋ฅผ ๋ฐ์ํ๊ธฐ ์ํด
- ์ฌ์ฉ์์๊ฒ โ์ ๋ฒ์ ์ด ๋์์ผ๋ ์ปดํจํฐ์ ์ฐ๊ฒฐํด์ ์ฌ์ดํธ์์ ํ์จ์ด๋ฅผ ๋ฐ์ ๊ตฌ์ฐ์ธ์โ๋ผ๊ณ ์๊ตฌํ์ง ์๊ณ , ๊ธฐ๊ธฐ๊ฐ ์ค์ค๋ก ๋ฐ์ ์ ์ฉํ๋ ๊ฒฝํ์ ์ฃผ๊ธฐ ์ํด
- ์ ๋ฐ์ดํธ ๋์ค ์ ์ /๋คํธ์ํฌ ๋๊น์ด ๋ฐ์ํด๋ ๊ธฐ๊ธฐ๊ฐ ๋ฒฝ๋(brick)์ด ๋์ง ์๋๋ก ์ด์ค ์ฌ๋กฏ + ๋กค๋ฐฑ์ผ๋ก ์์ ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ํด
AS-IS โ USB๋ก ์ง์ ๊ตฝ๋ ๋ฐฉ์ (์ฌ์ฉ์๊ฐ ์ง์ํ๋ ํ๋ฆ)
sequenceDiagram autonumber actor U as ์ฌ์ฉ์ participant PC as ์ปดํจํฐ(ํ๋์ ํด) participant D as ESP32 ๊ธฐ๊ธฐ U->>PC: ๋ฆด๋ฆฌ์ค ์ฌ์ดํธ์์ .bin ํ์จ์ด ๋ค์ด๋ก๋ U->>D: USB ์ผ์ด๋ธ ๋ฌผ๋ฆฌ ์ฐ๊ฒฐ PC->>D: esptool๋ก flash ์ ์ฒด ๊ตฝ๊ธฐ D-->>U: ์ฌ๋ถํ ํ ์ ํ์จ์ด ์คํ Note over U,D: ๊ธฐ๊ธฐ๋ง๋ค ์ผ์ด๋ธ ์ฐ๊ฒฐยท์๋ ์์ ํ์ โ ํ์ฅ ๋ถ๊ฐ
TO-BE โ OTA (๊ธฐ๋ํ๋ ํ๋ฆ: ์๋ฆผ โ ๋ฒํผ โ ๋ฌด์ ๊ฐฑ์ )
sequenceDiagram autonumber participant S as ํ์จ์ด ์๋ฒ/ํด๋ผ์ฐ๋ participant D as ESP32 ๊ธฐ๊ธฐ actor U as ์ฌ์ฉ์(์ฑ) S-->>D: "์ ๋ฒ์ v1.2.0 ์์" ์๋ฆผ(push/manifest) D-->>U: ์ฑ์ ์ ๋ฐ์ดํธ ๊ฐ๋ฅ ํ์ U->>D: [์ ๋ฐ์ดํธ] ๋ฒํผ ํด๋ฆญ D->>S: ์ ํ์จ์ด ์ด๋ฏธ์ง ์์ฒญ(HTTPS) S-->>D: ์ด๋ฏธ์ง ์คํธ๋ฆฌ๋ฐ ์ ์ก D->>D: ๋นํ์ฑ ์ฌ๋กฏ์ ๊ธฐ๋ก + ์๋ช /๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ D->>D: ๋ถํ ํํฐ์ ์ ํ ํ ์ฌ๋ถํ D->>D: ์๊ฐ์ง๋จ ์ฑ๊ณต โ ํ์ (์คํจ ์ ์๋ ๋กค๋ฐฑ) D-->>U: ์ ๋ฐ์ดํธ ์๋ฃ ๋ณด๊ณ
OTA๊ฐ ์ค์ ๋ก ๋์ํ๋ ๋ฐฉ์ (ESP32/ESP-IDF ๊ธฐ์ค)
ESP32๋ ํ๋์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํํฐ์ (partition) ์ด๋ผ๋ ์ฉ๋๋ณ ๊ตฌํ์ผ๋ก ๋๋ ์ด๋ค. OTA์ ํต์ฌ์ **์ฑ ์ฌ๋กฏ์ด 2๊ฐ(์ด์ค ์ฌ๋กฏ, A/B)**๋ผ๋ ์ ์ด๋ค.
ํํฐ์ ๋ ์ด์์
ํํฐ์
๊ตฌ์ฑ์ CSV๋ก ์ ์ํ๋ฉฐ, ๊ธฐ๋ณธ ์์น๋ ์คํ์
0x8000(4KB ํ ์นํฐ)์ด๋ค.
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
otadata, data, ota, 0xf000, 0x2000
factory, app, factory, 0x10000, 1M
ota_0, app, ota_0, , 1M
ota_1, app, ota_1, , 1M| ํํฐ์ | ์ข ๋ฅ | ์ญํ |
|---|---|---|
factory | app | ๊ณต์ฅ ์ถํ ์ด๊ธฐ ํ์จ์ด (์ ํ์ ) |
ota_0 / ota_1 | app | ์๋ก ๊ต๋๋ก ์ฐ์ด๋ ๋ ๊ฐ์ ์ฑ ์ฌ๋กฏ |
otadata | data | ๋ค์์ ์ด๋ ์ฌ๋กฏ์ ๋ถํ ํ ์ง ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ + ๋กค๋ฐฑ ์ํ (๋ ์นํฐ๋ผ ์ ์ ์๋ ํ๋๋ ์์กด) |
nvs | data | Wi-Fi ์๊ฒฉ์ฆ๋ช ยท์ค์ ๋ฑ ๋นํ๋ฐ์ฑ ์ ์ฅ. ์ฑ์ ๊ฐ์๋ผ์๋ ๊ฐ ๋ณด์กด โ ์ฌ์ฉ์ ์ค์ ์ ์ง |
์ฑ ํํฐ์ ์ 64KB ์ ๋ ฌ์ด ํ์ํ๊ณ , Name์ ์ต๋ 16๋ฐ์ดํธ๋ค. ์คํ์ ์ ๋น์๋๋ฉด ์๋ ๊ณ์ฐ๋๋ค.
์ ๋ฐ์ดํธ ์ ์ฐจ โ โ๋นํ์ฑ ์ฌ๋กฏ์ ์ฐ๊ณ ํฌ์ธํฐ๋ง ๋ฐ๊ฟ ์ฌ๋ถํ โ
sequenceDiagram autonumber participant App as ํ์ฌ ์ฑ(ota_0์์ ์คํ ์ค) participant Flash as ๋นํ์ฑ ์ฌ๋กฏ(ota_1) participant OtaData as otadata ํํฐ์ participant Boot as ๋ถํธ๋ก๋ App->>Flash: esp_ota_begin() โ ๋์ ์ฌ๋กฏ ์ค๋น/erase loop ๋ค์ด๋ก๋๋๋ ์ฒญํฌ๋ง๋ค App->>Flash: esp_ota_write() โ ์กฐ๊ฐ ๊ธฐ๋ก end App->>Flash: esp_ota_end() โ ์ด๋ฏธ์ง ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ App->>OtaData: esp_ota_set_boot_partition() โ ๋ค์ ๋ถํ =ota_1 App->>Boot: ์ฌ๋ถํ Boot->>OtaData: ์ด๋ ์ฌ๋กฏ ๋ถํ ํ ์ง ํ์ธ Boot->>Flash: ota_1์ ์ ํ์จ์ด ๋ก๋/์คํ
์ด ๊ตฌ์กฐ ๋๋ถ์ ๋ค์ด๋ก๋ ๋์ค ์ ์์ด ๊บผ์ ธ๋ ์๋ ์ฌ๋กฏ(ota_0)์ ๊ทธ๋๋ก ๋ฉ์ฉกํด์ ๊ธฐ๊ธฐ๊ฐ ์ฃฝ์ง ์๋๋ค.
๋ถํธ๋ก๋์ ๋ถํ ์ ํ
**๋ถํธ๋ก๋(bootloader)**๋ ์ ์์ด ์ผ์ง ๋ ๊ฐ์ฅ ๋จผ์ ๋๋ ์ฝ๋๋ก, ํํฐ์
ํ
์ด๋ธ๊ณผ otadata๋ฅผ ์ฝ์ด ์ด๋ ์ฑ์ ์คํํ ์ง ๊ฒฐ์ ํ๋ค.
sequenceDiagram autonumber participant BL as 2์ฐจ ๋ถํธ๋ก๋ participant PT as ํํฐ์ ํ ์ด๋ธ(0x8000) participant OD as otadata participant App as ์ ํ๋ ์ฑ ์ฌ๋กฏ BL->>PT: ํํฐ์ ๋ ์ด์์ ์ฝ๊ธฐ BL->>OD: ์ด๋ OTA ์ฌ๋กฏ์ด ํ์ฑ์ธ์ง ํ์ธ alt otadata ๋น์ด์์/์์ BL->>App: factory ์ฑ์ผ๋ก ํด๋ฐฑ else ์ ํจํ OTA ์ฌ๋กฏ ์ง์ BL->>App: ์ง์ ๋ ota_N ์ฑ ๋ก๋/์คํ end
์์ ์ฅ์น: Rollback๊ณผ Anti-Rollback
- Rollback(๋กค๋ฐฑ): ์ ํ์จ์ด๊ฐ ์ฒซ ๋ถํ
์ ์๊ฐ์ง๋จ์ ์คํจํ๊ฑฐ๋ ๋ถํ
์งํ ๊ณ์ ์ฃฝ์ผ๋ฉด, ๋ถํธ๋ก๋๊ฐ ์๋์ผ๋ก ์ด์ ์ ์ ์ฌ๋กฏ์ผ๋ก ๋๋๋ฆฐ๋ค.
- ์ ์ฑ์ ์ฒซ ๋ถํ
์์ ์ ์ ํ์ธ ํ
esp_ota_mark_app_valid_cancel_rollback()๋ฅผ ํธ์ถํด โ๋ ์ ์โ์ด๋ผ๊ณ ํ์ ํด์ผ ํ๋ค. - ํ์ ์์ด ์ฃฝ์ผ๋ฉด ์ํ๊ฐ
PENDING_VERIFY โ ABORTED๋ก ๋์ด๊ฐ๋ฉฐ ๋๋์๊ฐ๋ค.
- ์ ์ฑ์ ์ฒซ ๋ถํ
์์ ์ ์ ํ์ธ ํ
- Anti-Rollback(๋ค์ด๊ทธ๋ ์ด๋ ๋ฐฉ์ง): eFuse(1ํ์ฑ ํ๋์จ์ด ํจ์ฆ)์ ๋ณด์ ๋ฒ์ ์ ๊ธฐ๋กํด, ์์ ์ทจ์ฝ ๋ฒ์ ์ผ๋ก ๋๋๋ฆฌ๋ ๊ณต๊ฒฉ์ ์ฐจ๋จํ๋ค. 32๋นํธ๋ผ ์ต๋ 32๋ฒ์ anti-rollback ๊ฐฑ์ ํ๋๊ฐ ์๋ค.
ํต์ฌ: โ์๋ฆผ โ ์ ๋ฐ์ดํธ ๋ฒํผโ UX๋ ์ด๋ป๊ฒ ๋ง๋๋๊ฐ
์ํ๋ ๊ฒฝํ์ ๊ด๊ฑด์ **โ๊ธฐ๊ธฐ๊ฐ ์ ๋ฒ์ ์ ์ด๋ป๊ฒ ์์์ฑ๋๋โ**์ด๊ณ , ํฌ๊ฒ ๋ ๋ฐฉ์์ด๋ค.
1) Pull ๋ฐฉ์ (ํด๋ง + ๋ฒ์ ๋งค๋ํ์คํธ)
๊ธฐ๊ธฐ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ฒ์ ์์ JSON(๋งค๋ํ์คํธ)์ ํ์ธํ๋ค.
{ "version": "1.2.0", "url": "https://.../fw_1.2.0.bin", "sha256": "..." }๊ธฐ๊ธฐ๊ฐ ์๊ธฐ ๋ฒ์ ๊ณผ ๋น๊ตํด์ ๋ ๋์ผ๋ฉด โ์
๋ฐ์ดํธ ๊ฐ๋ฅโ์ผ๋ก ํ์ โ ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ๊ทธ๋ url์ ๋ฐ์ OTA ์ ์ฐจ ์คํ. ์๋ฒ ์ธํ๋ผ๊ฐ ๋จ์(์ ์ ํ์ผ ํธ์คํ
์ด๋ฉด ์ถฉ๋ถ)ํ์ง๋ง, ํ์ธ ์ฃผ๊ธฐ๋งํผ ์๋ฆผ์ด ๋ฆ๊ณ ๋ถํ์ํ ํธ๋ํฝ์ด ์๋ค.
2) Push ๋ฐฉ์ (MQTT ์์ ์ฐ๊ฒฐ)
**MQTT(Message Queuing Telemetry Transport)**๋ ์ ์ ๋ ฅยท์ ๋์ญํญ ๊ธฐ๊ธฐ์ ์ต์ ํ๋ ๋ฐํ/๊ตฌ๋ (pub/sub) ๊ฒฝ๋ ๋ฉ์์ง ํ๋กํ ์ฝ์ด๋ค. ๊ธฐ๊ธฐ๊ฐ ๋ธ๋ก์ปค์ ์์ ์ฐ๊ฒฐํด OTA ํ ํฝ์ ๊ตฌ๋ ํ๋ฉด, ์๋ฒ๊ฐ ์ ๋ฒ์ ์ publishํ๋ ์๊ฐ ์ฆ์ ๊ธฐ๊ธฐ์ ๋๋ฌํ๋ค.
sequenceDiagram autonumber participant D as ๊ธฐ๊ธฐ(๊ตฌ๋ ์) participant B as ๋ธ๋ก์ปค participant S as ์๋ฒ(๋ฐํ์) D->>B: topic "device/123/ota" ๊ตฌ๋ + ์์ ์ฐ๊ฒฐ Note over D,B: ํ์์ ์ ํด, ์ฐ๊ฒฐ๋ง ์ ์ง S->>B: publish "v1.2.0 ์์" to "device/123/ota" B-->>D: ์ฆ์ ๋ฉ์์ง ์ ๋ฌ (์๋ฆผ!)
MQTT์์ ๊ผญ ์์์ผ ํ 4๊ฐ์ง:
- Broker(๋ธ๋ก์ปค): ๋ชจ๋ ๋ฉ์์ง๊ฐ ๊ฑฐ์ณ ๊ฐ๋ ์ค์ ํ๋ธ. ๊ตฌ๋ ๊ด๋ฆฌยท๋ผ์ฐํ ๋ด๋น (์: Mosquitto, HiveMQ, AWS IoT Core).
- Topic(ํ ํฝ):
device/123/ota๊ฐ์ ๊ณ์ธตํ ์ฃผ์. ๊ตฌ๋ ์๋ ๊ด์ฌ ํ ํฝ๋ง ๋ฐ๋๋ค. - QoS(์ ๋ฌ ๋ณด์ฆ):
0์ต๋ 1ํ(fire-and-forget),1์ต์ 1ํ(์ค๋ณต ๊ฐ๋ฅ),2์ ํํ 1ํ. OTA ์๋ฆผ์ ๋ณดํต QoS 1 ์ด์. - Retained / Last Will: ํ ํฝ์ ๋ง์ง๋ง ๋ฉ์์ง๋ฅผ ์ ๊ตฌ๋ ์์๊ฒ ์๋ ์ ๋ฌ(retained), ๊ธฐ๊ธฐ๊ฐ ๊ฐ์๊ธฐ ๋๊ธฐ๋ฉด ๋์ ๋ณด๋ด๋ ์ ์ธ ๋ฉ์์ง(LWT)๋ก ์จ๋ผ์ธ ์ฌ๋ถ ๊ฐ์ง.
๋ชฉํ(์๋ฆผ + ๋ฒํผ, USB ์ง์)์๋ Push(MQTT) ๋๋ ๊ด๋ฆฌํ ํ๋ซํผ์ด ๊ฐ์ฅ ์ ๋ง๋๋ค. ์๋ฆผ๋ง ๋จผ์ ์ฃผ๊ณ ์ค์ ๋ค์ด๋ก๋/์ ์ฉ์ ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅธ ์ด๋ฒคํธ์์ ํธ๋ฆฌ๊ฑฐํ๋ฉด, โ์๋ฆผ โ ๋ฒํผ โ ๊ฐฑ์ โ ํ๋ฆ์ด ๊ทธ๋๋ก ๊ตฌํ๋๋ค. ์งํ๋ฅ ยท์ฑ๊ณต/์คํจ๋ ๊ฐ์ MQTT ์ฑ๋๋ก ๋๋ณด๊ณ ํ ์ ์๋ค.
ESP32์์ OTA๋ฅผ ๊ตฌํํ๋ ์ ํ์ง
| ๋ฐฉ๋ฒ | ํน์ง | ์๋ฆผ/๋ฒํผ UX |
|---|---|---|
esp_https_ota (ESP-IDF) | HTTPS๋ก ์ด๋ฏธ์ง ๋ฐ์ ๊ตฝ๋ ํ์ค ์ปดํฌ๋ํธ. ์๋ฒยท๋งค๋ํ์คํธ ์ง์ ๊ตฌ์ฑ | ์ง์ ๊ตฌํ |
| ESP RainMaker | Espressif ๊ด๋ฆฌํ ํด๋ผ์ฐ๋. esp_rmaker_ota_enable() ํ ์ค๋ก OTA + ํฐ ์ฑ + ์๋ฆผ + ์งํ๋ฅ ๋๋ณด๊ณ (AWS S3 ๊ธฐ๋ฐ, MQTT Force Push) | ๊ฑฐ์ ์์ฑํ ์ ๊ณต |
Arduino ArduinoOTA/Update | ๊ฐ๋ฐ ๋จ๊ณ ๋ก์ปฌ ๋คํธ์ํฌ ์ ๋ก๋์ ํธ๋ฆฌ | ๋ฐฐํฌ์ฉ์ผ๋ก ๋ถ์ ํฉ |
esp32FOTA ๋ผ์ด๋ธ๋ฌ๋ฆฌ | JSON ๋งค๋ํ์คํธ ๊ธฐ๋ฐ ํด๋ง OTA๋ฅผ ๊ฐ๋จํ ๊ตฌํ | ๋งค๋ํ์คํธ๋ก ๊ตฌํ |
| AWS IoT / Azure Device Update | ๋๊ท๋ชจ fleet ์บ ํ์ธยท๋กค์์ ๊ด๋ฆฌ | ํ๋ซํผ ์ ๊ณต |
์ค์ ํ์ดํ๋ผ์ธ: ๋ด๊ฐ ๊ฐ๋ฐ โ ๋ฐฐํฌ โ ๋ณด๋๊ฐ ์ค์ค๋ก ์ค์น
๊ฐ์ฅ ์ค์ํ ์ ์ โ โOTA ํด๋ผ์ด์ธํธ๋ ์ด๋ฏธ ํ ํ์จ์ด ์์ ์๋คโ
OTA๊ฐ ์ฑ๋ฆฝํ๋ ค๋ฉด, ์ฒ์ ํ๋งคํ v1.0.0 ํ์จ์ด ์์ โ์ ๋ฐ์ดํธ๋ฅผ ํ์ธยท๋ค์ด๋ก๋ยท์ค์นํ๋ ์ฝ๋(OTA ํด๋ผ์ด์ธํธ)โ๊ฐ ์ด๋ฏธ ๋ค์ด ์์ด์ผ ํ๋ค. ๋์ค์ ์๋ฒ์ ๋ญ ์ฌ๋ ค์ ๊ธฐ๊ธฐ๋ฅผ โ์คํโ์ํค๋ ๊ฒ ์๋๋ผ, ๊ธฐ๊ธฐ ์์ ๊ทธ ์ฝ๋๊ฐ ์ค์ค๋ก ์๋ฒ๋ฅผ ํ์ธํ๊ณ ์๊ธฐ ์์ ์ ๊ฐ์๋ผ์ด๋ค. ๊ฐ๋ฐ์๊ฐ ์๊ฒฉ์์ ์๋๋ ๊ฑด ์ค์ง โ์๋ฒ์ ์ bin๊ณผ ๋ฒ์ ์ ๋ณด๋ฅผ ์ฌ๋ฆฌ๋ ๊ฒโ๋ฟ์ด๋ค.
๋ง์ฝ v1.0.0์ OTA ํด๋ผ์ด์ธํธ๋ฅผ ์ ๋ฃ๊ณ ํ์๋ค๋ฉด, ๊ทธ ๋ณด๋๋ค์ ์์ USB๋ก๋ง ์ ๋ฐ์ดํธํด์ผ ํ๋ค. ๊ทธ๋์ ์ฒซ ์ถํ ๋ OTA ํด๋ผ์ด์ธํธ๋ฅผ ๊ผญ ํฌํจ์ํค๋ ๊ฒ ํต์ฌ์ด๋ค.
์ ์ฒด ๊ทธ๋ฆผ
sequenceDiagram autonumber actor Dev as ๋(๊ฐ๋ฐ์) participant PIO as PlatformIO participant Host as ํ์จ์ด ํธ์คํ <br/>(S3/GitHub Release/์๋ฒ) participant Broker as MQTT ๋ธ๋ก์ปค participant Board as ESP32(v1.0.0 ์คํ ์ค) actor User as ์ต์ข ์ฌ์ฉ์(์ฑ) Dev->>PIO: ๋ฒ์ 1.1.0์ผ๋ก ๋น๋ PIO-->>Dev: firmware.bin ์์ฑ Dev->>Host: firmware.bin + manifest.json ์ ๋ก๋ Dev->>Broker: "v1.1.0 ๋์์" publish (์ ํ) Broker-->>Board: OTA ํ ํฝ์ผ๋ก ์๋ฆผ ์ ๋ฌ Board-->>User: ์ฑ์ "์ ๋ฐ์ดํธ ๊ฐ๋ฅ" ํ์ User->>Board: [์ ๋ฐ์ดํธ] ๋ฒํผ ํด๋ฆญ Board->>Host: manifest.json ์ฝ๊ณ firmware.bin ์์ฒญ(HTTPS) Host-->>Board: bin ์คํธ๋ฆฌ๋ฐ Board->>Board: ๋นํ์ฑ ์ฌ๋กฏ(ota_1)์ ๊ธฐ๋ก+๊ฒ์ฆ Board->>Board: ๋ถํ ์ฌ๋กฏ ์ ํ ํ ์ฌ๋ถํ Board-->>User: v1.1.0๋ก ์ ๋ฐ์ดํธ ์๋ฃ ๋ณด๊ณ
Step 1 โ PlatformIO๋ก ๋น๋ํ๊ณ ๋ฒ์ ์ ์ฌ๋๋ค
๋ฒ์ ์ ์ฝ๋์ ๋ฐ์๋ฌ์ผ ๊ธฐ๊ธฐ๊ฐ โ๋ด ๋ฒ์ vs ์๋ฒ ๋ฒ์ โ์ ๋น๊ตํ ์ ์๋ค. platformio.ini์์ ๋น๋ ํ๋๊ทธ๋ก ์ฃผ์
ํ๋ค.
; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
build_flags = -DFW_VERSION="1.1.0" ; ์ฝ๋์์ ์ด ๊ฐ์ผ๋ก ์๊ธฐ ๋ฒ์ ์ธ์
lib_deps = chrisjoyce911/esp32FOTA ; OTA ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋น๋ํ๋ฉด ์ฐ์ถ๋ฌผ์ ์ฌ๊ธฐ์ ์๊ธด๋ค:
.pio/build/esp32dev/firmware.bin โ ์ด ํ์ผ์ ๋ฐฐํฌํ๋ค
Step 2 โ bin์ โ์ด๋๊ฐโ์ ์ฌ๋ฆฌ๊ณ , ๋ฒ์ ๋งค๋ํ์คํธ๋ฅผ ํจ๊ป ๋๋ค
firmware.bin์ ๊ธฐ๊ธฐ๊ฐ HTTPS๋ก ์ ๊ทผ ๊ฐ๋ฅํ ๊ณณ์ ์ฌ๋ฆฐ๋ค. ํ๋ณด:
| ํธ์คํ | ์ฑ๊ฒฉ |
|---|---|
| AWS S3 / ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง | ์ ์ ํ์ผ, ํ์ฅ์ฑ ์ข์. RainMaker๋ ๋ด๋ถ์ ์ผ๋ก S3 ์ฌ์ฉ |
| GitHub Releases | ๋ฌด๋ฃยท๊ฐ๋จ. ๋ฆด๋ฆฌ์ค์ bin ์ฒจ๋ถํ๋ฉด ๊ณ ์ URL ์ ๊ณต |
| ์ง์ ์ด์ํ๋ nginx/์๋ฒ | ์์ ํ ํต์ , ์ ๊ทผ์ ์ด ์์ |
๊ทธ๋ฆฌ๊ณ **๋ฒ์ ์ ๋ณด๋ฅผ ๋ด์ ์์ JSON(๋งค๋ํ์คํธ)**์ ๊ฐ์ด ์ฌ๋ฆฐ๋ค. ๊ธฐ๊ธฐ๋ bin์ด ์๋๋ผ ์ด JSON์ ๋จผ์ ๋ณธ๋ค.
// manifest.json (esp32FOTA ํ์ ์์)
{
"type": "my-board",
"version": "1.1.0",
"host": "https://fw.example.com",
"port": 443,
"bin": "/firmware/my-board_1.1.0.bin"
}Step 3 โ ๊ธฐ๊ธฐ์ โ์ ๋ฒ์ ๋์๋คโ๊ณ ์๋ฆฐ๋ค (๋ ๋ฐฉ๋ฒ)
- Push (MQTT): ์๋ฒ๊ฐ
device/+/otaํ ํฝ์ ์ ๋ฒ์ ์ publish โ ์จ๋ผ์ธ ๊ธฐ๊ธฐ๊ฐ ์ฆ์ ๋ฐ์ ์ฑ์ ๋ฐฐ์ง ํ์. ์ค์๊ฐ์ฑ์ด ํ์ํ๋ฉด ์ด ๋ฐฉ์. - Pull (ํด๋ง): ๊ธฐ๊ธฐ๊ฐ ๋ถํ
์ + N์๊ฐ๋ง๋ค
manifest.json์ GETํด์ ์๊ธฐ ๋ฒ์ ๋ณด๋ค ๋์ผ๋ฉด ํ์. ์๋ฒ๊ฐ ๋จ์(์ ์ ํ์ผ๋ง)ํด์ ์์ํ๊ธฐ ์ฝ๋ค.
๋ ๋ค โ์๋ฆผ๋งโ ํ๋ ๋จ๊ณ๋ค. ์ค์ ๋ค์ด๋ก๋๋ ์์ง ์์ํ์ง ์๋๋ค โ ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅด๋ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.
Step 4 โ ๋ฒํผ ํด๋ฆญ โ ๊ธฐ๊ธฐ๊ฐ ์ค์ค๋ก ๋ค์ดยท๊ฒ์ฆยท์ค์นยท์ฌ๋ถํ
์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅด๋ฉด(์ฑ์ด MQTT/BLE/HTTP๋ก ๊ธฐ๊ธฐ์ โ์ ๋ฐ์ดํธ ์์โ ์ ํธ๋ฅผ ๋ณด๋), ๊ธฐ๊ธฐ ์์ OTA ํด๋ผ์ด์ธํธ ์ฝ๋๊ฐ ๋๋จธ์ง๋ฅผ ์ ๋ถ ์๋์ผ๋ก ํ๋ค.
#include <esp32FOTA.hpp>
// ๋ด ๋ณด๋ ํ์
, ํ์ฌ ๋ฒ์ ์ ์๋ ค์ฃผ๋ฉฐ OTA ํด๋ผ์ด์ธํธ ์์ฑ
esp32FOTA fota("my-board", FW_VERSION); // FW_VERSION = "1.1.0"
void setup() {
fota.setManifestURL("https://fw.example.com/manifest.json");
// (๊ถ์ฅ) ์๋ช
/์ธ์ฆ์ ๊ฒ์ฆ ์ค์ โ ์์กฐ ํ์จ์ด ์ฐจ๋จ
}
// ์ฌ์ฉ์๊ฐ "์
๋ฐ์ดํธ" ๋ฒํผ์ ๋๋ ์ ๋ ํธ์ถ๋๋ ํจ์
void onUpdateButtonPressed() {
if (fota.execHTTPcheck()) { // ๋งค๋ํ์คํธ ๋ฒ์ > ๋ด ๋ฒ์ ?
fota.execOTA(); // ์ฌ๊ธฐ์ ์ ๋ถ ์๋ ์ํ โ
// 1) firmware.bin ์คํธ๋ฆฌ๋ฐ ๋ค์ด๋ก๋
// 2) ๋นํ์ฑ ์ฌ๋กฏ(ota_1)์ esp_ota_write๋ก ๊ธฐ๋ก
// 3) ์ด๋ฏธ์ง ๋ฌด๊ฒฐ์ฑ/์๋ช
๊ฒ์ฆ
// 4) otadata ํฌ์ธํฐ๋ฅผ ์ ์ฌ๋กฏ์ผ๋ก ๋ณ๊ฒฝ
// 5) esp_restart() โ ์ฌ๋ถํ
// ์ฌ๋ถํ
ํ ๋ถํธ๋ก๋๊ฐ ota_1์ ์ ํ โ v1.1.0 ์คํ
}
}โ๋ด๊ฐ ์คํ ์ ์์ผ๋ ์๋์ผ๋ก ์ค์น๋๋โ ์ด์
ํต์ฌ์ ๋ค์ด๋ก๋๋ bin์ ์ฌ๋์ด ์ด๋์ ๋ณต์ฌํ๊ฑฐ๋ ์คํํ๋ ๊ฐ๋
์ด ์๋๋ผ๋ ์ ์ด๋ค. execOTA()๊ฐ ๋ฐ์ ๋ฐ์ดํธ๋ฅผ ๊ณง๋ฐ๋ก ๋นํ์ฑ ํํฐ์
(ota_1)์ ๊ตฝ๊ณ , otadata์ ๋ถํ
ํฌ์ธํฐ๋ง ๋ฐ๊พผ ๋ค ์ฌ๋ถํ
ํ๋ค. ๊ทธ๋ฌ๋ฉด ๋ถํธ๋ก๋๊ฐ ๋ค์ ๋ถํ
์์ ์ ์ฌ๋กฏ์ ์๋์ผ๋ก ๊ณจ๋ผ ์คํํ๋ค. ์ฆ:
- ๋ค์ด๋ก๋ == ์ค์น๋ค. ๋ฐ์ ์ฆ์ ํ๋์์ ๊ธฐ๋ก๋๋ฏ๋ก ๋ณ๋ โ์ค์น ์คํโ ๋จ๊ณ๊ฐ ์๋ค.
- ์ฑ๊ณต ํ์ธ ์ ๊น์ง ์๋ ์ฌ๋กฏ(ota_0)์ ์ด์ ์์ด, ์ ํ์จ์ด๊ฐ ๋ถํ ์ ์คํจํ๋ฉด ์๋ ๋กค๋ฐฑ๋๋ค.
- ๊ฐ๋ฐ์๊ฐ ํ๋ ์ผ์ ์ค์ง **Step 1~3(๋น๋ยท์ ๋ก๋ยท์๋ฆผ)**๋ฟ. Step 4์ ๋ฌผ๋ฆฌ์ ์ค์น๋ ์ ๋ถ ๊ธฐ๊ธฐ๊ฐ ํ๋ค.
๋ณด์: ์ด๋ฏธ์ง ์๋ช ๊ณผ Secure Boot
OTA๋ โ์๊ฒฉ์์ ์ฝ๋๋ฅผ ๋ฐ์ ์คํโํ๋ ํต๋ก๋ผ, ๊ฒ์ฆ์ด ์์ผ๋ฉด ๊ณต๊ฒฉ์๊ฐ ์ ์ฑ ํ์จ์ด๋ฅผ ๋ฐ์ด๋ฃ๋ ๋ฐฑ๋์ด๊ฐ ๋๋ค. ๊ทธ๋์ ๋ ๊ฒน์ ๋ฐฉ์ด๊ฐ ํ์ํ๋ค.
- ์ ์ก ๊ตฌ๊ฐ ์ํธํ: HTTPS/TLS๋ก ๋ฐ์ ์ค๊ฐ์ยทDNS ์คํธํ ๋ฐฉ์ง (RainMaker๋ ์๋ฒ ์ธ์ฆ์ ๊ฒ์ฆ ํฌํจ).
- ์ด๋ฏธ์ง ์๋ช ๊ฒ์ฆ (Secure Boot): ์๋ช ๋ ์ด๋ฏธ์ง๋ง ๋ถํ ์ ํ์ฉํ๋ค. ESP32 Secure Boot v2๋ RSA-3072(PSS) ๊ณต๊ฐํค ์ํธ๋ก ๋ถํธ๋ก๋ยท์ฑ์ ๊ฒ์ฆํ๊ณ , ๊ณต๊ฐํค์ ์ง๋ฌธ์ eFuse์ ๊ฐ์ธํด ์ยท๋ณ์กฐ๋ฅผ ๋ง๋๋ค.
sequenceDiagram autonumber participant ROM as ROM ๋ถํธ๋ก๋(๋ณ๊ฒฝ๋ถ๊ฐ) participant BL as 2์ฐจ ๋ถํธ๋ก๋ participant App as ์ฑ ์ด๋ฏธ์ง participant eF as eFuse(๊ณต๊ฐํค ์ง๋ฌธ) ROM->>eF: ๊ณต๊ฐํค ์ง๋ฌธ ์ฝ๊ธฐ ROM->>BL: 2์ฐจ ๋ถํธ๋ก๋ ์๋ช ๊ฒ์ฆ Note over ROM,BL: ์คํจ ์ ๋ถํ ์ค๋จ BL->>App: ์ฑ ์ด๋ฏธ์ง ์๋ช ๊ฒ์ฆ (๋ถํ ์ + OTA ์) App-->>App: ๊ฒ์ฆ ํต๊ณผ โ ์คํ
ํต์ฌ์ OTA๋ก ๋ฐ์ ์ ์ด๋ฏธ์ง๋ ๋ถํ
์์ ๊ณผ OTA ์ ์ฉ ์์ ๋ชจ๋ ๊ฐ์ eFuse ๊ณต๊ฐํค ์ง๋ฌธ์ผ๋ก ์๋ช
๊ฒ์ฆ์ ํต๊ณผํด์ผ ์ฑํ๋๋ค๋ ์ ์ด๋ค. ์ฆ esp_ota_end()์ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ ์์, Secure Boot๋ โ์ ํ ์๋ช
โ๊น์ง ์๊ตฌํ๋ ์์ ๋ฐฉ์ด์ ์ด๋ค.
- Secure Boot vs Flash Encryption: Secure Boot๋ ์์กฐ ์ฝ๋ ์คํ์ ๋ง๊ณ (๋ฌด๊ฒฐ์ฑ), Flash Encryption์ ํ๋์ ๋ด์ฉ ์ฝ๊ธฐ๋ฅผ ๋ง๋๋ค(๊ธฐ๋ฐ์ฑ). ๋ชฉ์ ์ด ๋ฌ๋ผ ํจ๊ป ์ฐ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค.
- Anti-Rollback: ์ทจ์ฝํ ๊ตฌ๋ฒ์ ์ผ๋ก์ ๊ฐ์ ๋ค์ด๊ทธ๋ ์ด๋๋ฅผ ์ฐจ๋จ(์ ์์ ์ฅ์น ์ ์ฐธ๊ณ ).
- ์ผ๋ฐ์ ์ํ: spoofing, tampering, replay, DoS, ๊ถํ ์์น โ ๊ฒ์ฆ ์๋ OTA๋ ๊ณง ๋ฐฑ๋์ด๋ผ๋ ๊ด์ ์ด ํ์ํ๋ค.