Wiznet makers

Lihan__

Published August 18, 2026 ©

86 UCC

9 WCC

3 VAR

0 Contests

0 Followers

0 Following

Original Link

daikin-altherma-esp32

ESP32-S3 firmware bridging a Daikin Altherma heat pump to MQTT

COMPONENTS
PROJECT DESCRIPTION

Daikin Altherma → MQTT Bridge — One Cable Into the Cellar, One Register to Find It

#W5500 #MACRAW #ESP32S3 #PoE #RuntimeDetection #ESP-IDF #MQTT #HomeAssistant #HeatPump #ModbusTCP #OTA

📚 Context: Open-source home-automation firmware (MIT, v1.0.0, 451 commits) — a single-developer project with an unusually disciplined engineering culture: host-tested pure logic, source-text contract tests in CI, and a documentation set that states why every decision was made.

Implementation status: Fully implemented and running. The X10A protocol is validated against live bus captures; the W5500 wired transport is a complete bring-up path with runtime detection, failure unwind, and a cable-loss recovery policy — all of it host-tested and pinned by CI contract tests.


01 — What is this project?

A Daikin Altherma heat pump knows a great deal about itself — flow temperatures, compressor state, defrost cycles, hot-water tank behaviour — and shares almost none of it. The X10A service header on the indoor unit's PCB is the one place those numbers surface: a 5 V TTL UART at 9600 8E1, half-duplex, poll-only. The unit never volunteers a value; it answers a query and goes quiet.

daikin-altherma-esp32 is ESP-IDF firmware for the ESP32-S3 that taps X10A, decodes both protocol variants (I and S), converts the raw register pages into a labelled value catalog, and publishes everything to MQTT with Home Assistant discovery. Around that core it builds what a device left alone in a utility room for years actually needs:

  • an embedded web UI — one live dashboard, with any reading the firmware cannot currently stand behind shown as rather than as a number
  • signed dual-slot OTA (two ~2.03 MB slots) and a browser Web Serial installer
  • a 4 MiB wear-levelled history journal in the upper flash — a circular append-only ring of 256-byte records, CRC'd, with a separate commit word programmed last so a power cut cannot invalidate its predecessors
  • an optional second data source: a Daikin HomeHub read over Modbus TCP, discovered by mDNS
  • an optional ENV III outdoor climate sensor over I²C
  • a 24-hour plant checkup that turns the trend journal into plain-language diagnostics

The firmware is read-only by construction. X10A has no write command in the protocol, and the Modbus link has none in the code — no source file can even frame a Modbus write, and a CI contract test walks every file under main/ to keep it that way.

Then comes the problem that has nothing to do with heat pumps and everything to do with buildings: the machine is in the cellar, and the Wi-Fi is not. A heat pump lives in a utility room, behind concrete, at the far end of the house from the access point. A telemetry bridge that drops off the network every evening is not a telemetry bridge.

The answer is an optional wired transport built on the WIZnet W5500 — in practice an M5Stack AtomS3 Lite seated on an ATOMIC PoE Base, where one cable carries both the power and the LAN. And the way that transport is engineered is what makes this project worth reading.


02 — Why a wire, and why it had to be optional

🔷 Reason 1 — Radio does not reach where heat pumps live

Concrete, plant rooms, cellars, outbuildings. This is the classic case where an installer has a network cable or a PoE switch available and no realistic Wi-Fi path. PoE closes it completely: no outlet needed beside the unit, no repeater, no powerline adapter — one cable, one run, done.

🔷 Reason 2 — The radio is expensive on a board this small

Starting the Wi-Fi station costs roughly 50 KB of heap on a device whose real binding constraint is the largest contiguous free block. A board that comes up wired never starts the radio at all, and never puts a second netif on the same subnet for nothing.

🔷 Reason 3 — But it could not become a second firmware

CI publishes exactly one esp32s3 image. A compile-time board choice would fork the binary, its OTA manifest and the web installer — three artefacts, per board, forever. So the wired transport had to be something the same image discovers at runtime. That constraint is what drives the whole W5500 design below.

🔷 WIZnet ecosystem context — why this is not an "either/or" against the internal MAC

Worth stating plainly, because it decides the whole design space: the ESP32-S3 has no internal Ethernet MAC. The original ESP32's EMAC peripheral did not carry forward, so the familiar "RMII + external PHY (LAN8720 / LAN8742A)" route is not merely inconvenient on this chip — it does not exist. Every wired ESP32-S3 is an SPI Ethernet controller, and that is exactly the slot the W5500 fills.

OptionOn ESP32-S3Verdict
Internal EMAC + RMII PHYNot available — no EMAC peripheral on S3Ruled out by silicon
ENC28J60 over SPI10 Mbps half-duplex, 8 KB buffer, long errata historyWorkable, materially weaker
WIZnet W5500 over SPI10/100 auto-negotiating MAC+PHY, 32 KB internal buffer, first-class ESP-IDF driverChosen

Within WIZnet's own line the same slot is served by the W5100S and by the W6100 (which adds IPv6 dual-stack), and the W55RP20 folds an RP2040 alongside the same core for a single-package solution. For this project the W5500 is the right member of the family: it is the part the M5Stack ATOMIC PoE Base already carries, and the part with a maintained Espressif-published driver component (espressif/w5500).


03 — System architecture

 

The OTA path, over whichever transport is carrying the device

Separate failure class ─ BOOT-LOOP SAFE MODE (logic/boot_guard.hpp): both slots share one NVS, so rolling back the IMAGE cannot fix a CONFIG crash-loop (wrong X10A RX/TX pins). Crash-only boots are counted; past a threshold the device comes up minimally — WiFi + web UI + OTA, no poll, no MQTT — so the bad setting is fixable in a browser instead of over USB. Second install path ─ BROWSER WEB SERIAL: the manifest publishes only the occupied flash_args ranges as separate parts, so a no-Erase install skips nvs. "Erase" stays the explicit factory-reset path.

 


04 — Why the WIZnet W5500? ⭐

🔷 The technical core: a whole Ethernet front end behind four wires

The W5500 is a 10/100 auto-negotiating MAC and PHY in one package, driven entirely over SPI. On an ESP32-S3 that is not a convenience — it is the only shape wired Ethernet can take, because the chip has no EMAC to pair a PHY with. Four pads (SCLK 5 · CS 6 · MISO 7 · MOSI 8), SPI mode 0 at 20 MHz, and the board is on the LAN.

Crucially, four pads is few enough that the trade stays survivable. On an AtomS3 Lite the PoE base occupies the entire side header — and the Grove port that remains is exactly what X10A needs. The heat pump link and the network link coexist on a 24 × 24 mm board because the Ethernet controller asked for four pins instead of the fourteen an RMII PHY would have wanted.

🔷 The mode: MACRAW (Sn_MR = 0x04) — and why the TOE is deliberately unused

This is the identity of the integration, so it is worth being precise about it.

The espressif/w5500 component drives the part as a plain Ethernet MAC: socket 0 is placed in MACRAW mode, the full 16 KB RX buffer is assigned to that one socket, and raw Ethernet frames are shuttled across SPI in both directions. TCP, UDP, IP, ARP, DHCP and DNS all happen above it, in lwIP on the ESP32-S3. The W5500's celebrated hardwired TCP/IP stack — the TOE that makes it the part it is on an 8-bit MCU — is switched off here on purpose.

That is not a limitation. It is the correct engineering answer to what this firmware actually needs:

What the firmware needsWhere it comes from
TLS for signed HTTPS OTA and the weather feedmbedTLS on the ESP32-S3
MQTT client with HA discovery, syslog, SNTPESP-IDF components over lwIP
mDNS responder (<hostname>.local)espressif/mdns over lwIP
A captive DNS server, an HTTP server with 36 routesesp_http_server over lwIP
An Ethernet MAC and PHY it does not physically haveW5500

A hardware TCP socket cannot carry an mbedTLS session, an MQTT client library or a 36-route HTTP server — those are stack-resident features the ESP32-S3 already had. What the SoC was missing was the bottom two layers. MACRAW is the mode that supplies exactly that and nothing else, and it is the reason a single W5500 slots under an existing, fully-featured software stack without a line above it changing. net.hpp states the consequence directly: this is a second transport, not a second source — MQTT, syslog, SNTP, OTA and HTTP all run over the wire unchanged, and the bridge above does not know or care which one carries it.

For makers, that is the takeaway worth carrying away: the W5500 has two distinct careers. On a small MCU with no stack, it is a TOE that gives you sockets for free. On an application SoC that already owns lwIP and mbedTLS, it is a clean SPI-attached MAC+PHY in MACRAW. Same silicon, opposite half of the datasheet.

🔷 One register turns a board variant into a runtime feature

This is the most elegant thing the W5500 does for this project, and it costs a single SPI transaction.

VERSIONR lives at offset 0x0039 of the W5500's Common Register block and reads a fixed 0x04 on every part ever made. The firmware reads exactly that one byte at boot — three bytes of address phase plus one data byte, a single 4-byte full-duplex transfer:

  • Reads 0x04 → a controller is wired to those pads. Bring up the netif, take a lease, and never start the Wi-Fi radio at all.
  • Reads anything else → free the SPI bus again. The board is byte-for-byte the Wi-Fi device it was before this code existed: no netif, no driver, no task, no event handler.

A floating MISO reads 0x00 or 0xFF, so there is no realistic false positive. That single deterministic register is what lets CI keep publishing one image for every board — the wired variant and the wireless variant are the same binary, and the difference is discovered in microseconds at boot rather than declared at compile time. Any part whose identity is guessed from behaviour instead of read from a fixed register could not have carried this design.

🔷 Polled operation, because the base routes no interrupt

The ATOMIC PoE Base carries SCLK, CS, MISO, MOSI and power — and no interrupt line. So the driver runs with int_gpio_num = -1 and poll_period_ms = 10. This is a supported first-class mode (Espressif ships a CI configuration for exactly it), and the project's own Kconfig help is precise about what the number means: it bounds RX latency, not throughput — each poll drains everything already queued in the W5500's buffer. A 16 KB on-chip RX buffer is what makes a 10 ms poll interval a comfortable design point instead of a packet-loss budget.

🔷 The details a careful integration gets right

  • MAC address: the W5500 carries no MAC of its own, so the firmware supplies one — ESP_MAC_ETH, the ESP32-S3's eFuse-derived Ethernet address. Stable across reboots and distinct from the Wi-Fi STA MAC, so the two interfaces can never collide on one LAN.
  • Route priority: ESP-IDF ships WIFI_STA_DEF at 100 and ETH_DEF at 50, so the wire would lose by default. net.cpp raises the Ethernet netif to 128 — and the code comments are honest that this governs off-link traffic only, since lwIP's on-link routing walks netif_list regardless.
  • A probe guard that protects the heat pump: the identity probe drives a clock and a chip-select. The documented AtomS3 Lite alternative puts X10A on GPIO5/6 — the same pads. So net_eth_probe_allowed() refuses to probe at all when any of the four overlap the configured X10A / sensor / indicator / button pins. Without it, an unguarded boot probe would clock SPI edges onto a heat pump's live service bus.
  • Complete failure unwind: a positive probe leaves an SPI bus installed. Every later allocation or start failure reverses all of it in strict reverse order — driver stop, handler unregister, netif glue delete, driver uninstall, MAC delete, PHY delete, netif destroy, and only then spi_bus_free() — because a half-unwound Ethernet attempt would hand the Wi-Fi fallback exactly the heap pressure that killed the wired path.
  • Cable-loss recovery as a stated policy: a board that came up wired has no radio running and cannot safely grow one from an event handler. So a pulled cable earns a deliberate reboot after ~30 s, which re-runs the same boot fork from scratch. The 24-hour trends survive it. With no Wi-Fi configured it does not reboot at all — it simply waits for the wire, and takes it back when it returns.

🔷 Verified evidence ✅

  • VERSIONR = 0x04 detection implemented in net.cpp and used as the actual boot fork
  • ✅ Pinned dependency espressif/w5500: "^2.0.0" in main/idf_component.yml, locked in dependencies.lock
  • ✅ Pins, clock, poll interval and DHCP window all exposed as Kconfig (CONFIG_DAIKIN_ETH_*)
  • ✅ Transport policy extracted into IDF-free logic/net_link.hpp and host-tested — route ownership, boot fork, portal suppression, probe permission, cable-loss recovery
  • test/test_transport_contract.mjs asserts over source text that the call sites still ask those rules — including that nothing may call wifi_start_sta() unconditionally again
  • ✅ Documented user-visible behaviour: the Connections tile grows an Ethernet row showing negotiated speed, and distinguishes no cable from cable connected, no address
  • ✅ Turning the transport off (CONFIG_DAIKIN_ETH_ENABLED=n) reclaims ~40 KB of flash and links no Ethernet code at all

05 — Key components

🌐 WIZnet W5500 — MACRAW mode (Sn_MR = 0x04), socket 0, full 16 KB RX buffer

The optional wired transport. 10/100 auto-negotiating MAC+PHY over SPI mode 0 at 20 MHz on SCLK 5 · CS 6 · MISO 7 · MOSI 8, polled every 10 ms with no interrupt line. Detected at boot by reading VERSIONR (0x0039) and comparing against the fixed 0x04. TCP/IP is handled by lwIP on the host SoC; the W5500 supplies the two layers the ESP32-S3 does not have in silicon.

🔲 ESP32-S3 (M5Stack AtomS3 Lite / Seeed XIAO ESP32-S3)

≥ 8 MB flash mandatory — two ~2.03 MB OTA slots below 4 MB and the entire upper 4 MiB reserved for the history journal. No PSRAM used; the heap discipline assumes internal RAM only. Native USB-Serial/JTAG for the browser installer.

🔌 M5Stack ATOMIC PoE Base

The W5500 carrier. Power and LAN on one cable, which is the entire point in a cellar. Occupies GPIO 5/6/7/8/38 — the AtomS3 Lite's whole side header — which the firmware turns into a stated constraint rather than a discovered one: once a controller is detected, those four pads vanish from every pin picker and a config POST naming one is refused.

📦 espressif/w5500 ^2.0.0 (managed component) · ESP-IDF ≥ 6.0

ESP-IDF 6.0 moved the SPI Ethernet chip drivers out of core esp_eth, which now ships only the MAC/PHY framework and the internal EMAC. The W5500 MAC/PHY pair is pulled from the component registry — the ^2.0.0 line is the one built against the 6.x esp_eth API, which is the project's real IDF floor.

🔗 X10A UART tap · Daikin HomeHub (Modbus TCP) · M5Stack ENV III

The three data sources, all read-only. X10A leads wherever it and the HomeHub report the same quantity; Modbus stays visible as the labelled second reading and covers the fallback when X10A is offline.


06 — Application scenarios

01. Plant-room and cellar telemetry over PoE

The originating case, and the most transferable one. Any monitored machine that lives where radio does not — boilers, heat pumps, pumps, compressors, tank levels, sub-meters. One PoE run replaces an outlet, an access point and a repeater.

02. Wired Ethernet on any ESP32-S3 product

Because the S3 has no internal EMAC, this repository is a directly reusable reference for every ESP32-S3 design that needs a cable: pin plan, SPI configuration, polled-mode rationale, MAC assignment, route priority, DHCP deadlines and a complete teardown path. Lift net.cpp and logic/net_link.hpp and most of the work is done.

03. One firmware image across wired and wireless fleet variants

The VERSIONR probe is a pattern, not a detail. Any product shipping both a Wi-Fi SKU and a wired/PoE SKU can use it to keep one binary, one OTA manifest and one installer — and let the hardware announce itself. This is a meaningful reduction in release-engineering surface for a small manufacturer.

04. Read-only industrial gateways where radio is unavailable or disallowed

The whole architecture — no write path anywhere, enforced by CI; a trust surface that grants the full API to the wired LAN while withholding it from an open setup radio; syslog, SNTP and signed OTA all transport-agnostic — is a good template for a monitoring bridge in an environment where wireless is impractical or prohibited.


Conclusion

A heat pump in a cellar needs a cable, and a project that ships one binary cannot afford a board variant. One W5500 register at 0x0039 resolves both.

  • ✅ Full X10A service-port decode (I and S variants), validated against live bus captures
  • ✅ MQTT with Home Assistant discovery, embedded live web UI, signed dual-slot OTA, browser installer
  • ✅ 4 MiB wear-levelled circular history journal with CRC and a last-programmed commit word
  • Optional W5500 wired transport in MACRAW mode, giving an ESP32-S3 the MAC+PHY it has no silicon for
  • Runtime detection via VERSIONR = 0x04 — one binary serves wired and wireless boards alike
  • ✅ PoE-ready on the M5Stack ATOMIC PoE Base: one cable carries power and LAN into the plant room
  • ✅ Ethernet wins the default route (netif priority 128) and suppresses both the Wi-Fi station and the captive portal, saving ~50 KB of heap on a wired boot
  • ✅ Probe guard that refuses to clock SPI onto a configured X10A service bus
  • ✅ Complete reverse-order failure unwind so a failed wired bring-up never poisons the Wi-Fi fallback
  • ✅ Transport policy extracted into host-tested pure logic and pinned by source-text CI contract tests

Q&A

Q. If the W5500's whole selling point is its hardwired TCP/IP stack, why run it in MACRAW and throw that away? Because the ESP32-S3 already owns a full network stack — lwIP, mbedTLS, an MQTT client, an mDNS responder, an HTTP server with 36 routes. What it does not own is an Ethernet MAC or PHY; the S3 dropped the original ESP32's EMAC peripheral entirely. MACRAW supplies precisely the missing bottom two layers and leaves everything above untouched, which is why the wire slid in under an existing firmware without a single line of the MQTT bridge changing. On a small MCU with no stack, the same chip would rightly be used the opposite way, in TOE mode.

Q. What does MACRAW mode actually cost compared with TOE? Every frame crosses SPI and the host CPU runs the protocol stack, so it costs SPI bandwidth and CPU cycles that TOE would have absorbed. For a telemetry bridge polling a 9600-baud serial bus and publishing a few dozen MQTT values, that cost is invisible — and the 16 KB on-chip RX buffer absorbs bursts between polls, which is exactly why a 10 ms polling interval works with no interrupt line at all.

Q. Why poll instead of using the W5500's interrupt output? The M5Stack ATOMIC PoE Base routes SCLK, CS, MISO, MOSI and power — there is no INT pin to wire. Polled operation is a supported ESP-IDF configuration rather than a workaround, and the poll interval bounds latency, not throughput: each poll drains whatever the W5500 has already buffered.

Q. How can the same binary serve a board with Ethernet and a board without? VERSIONR at 0x0039 reads a fixed 0x04 on every W5500, and a floating MISO reads 0x00 or 0xFF. One 4-byte SPI transfer at boot therefore distinguishes "a controller is wired here" from "these pads are empty" with no realistic false positive. Found → bring up the wire and skip the radio. Not found → free the SPI bus and behave exactly as a Wi-Fi-only build.

Q. Which W5500 pins does it use, and what does that cost? SCLK 5 · CS 6 · MISO 7 · MOSI 8, all Kconfig-settable. On an AtomS3 Lite the PoE base covers the entire side header, so X10A must use the Grove port — which means an Ethernet install has no pins left for the optional ENV III sensor. The firmware states that conflict up front instead of letting the user discover it. On a Seeed XIAO ESP32-S3 the same four pads are broken out with room to spare, so a hand-wired W5500 module leaves pins for both X10A and ENV III.

Q. Could a WIZnet part make this design better rather than merely possible? Two directions are worth noting. A W6100 in the same SPI slot would add IPv6 dual-stack for networks that need it. And on a fresh design, a W55RP20 would collapse the controller and an RP2040 into one package — attractive for a purpose-built PoE telemetry node where the ESP32-S3's Wi-Fi is no longer the point.


Related work — ESP32-S3 + WIZnet: Update Your App Over Wi-Fi or Ethernet

Both projects put an ESP32-S3 on a WIZnet controller and both take OTA seriously — but they place the update logic on opposite sides of the app boundary, which is the only comparison worth making here.

At the OTA levelThis projectThe WIZnet OTA bootloader
Where the update logic livesInside the app imageIn a factory partition that is never overwritten
App's own OTA codeRequired in every build, foreverNone — the example app is 80 lines with zero networking
An image that forgets OTAUnrecoverable without USBInstalls and runs fine
TriggerApp polls a signed HTTPS manifestBOOT button, empty otadata, or the app asks
DowntimeNone — the app keeps running through the downloadReboot → loader → download → reboot
Image authenticityRSA-3072 signature verified before boot-slot selection, plus a two-point downgrade gateTransport-level HTTPS, certificate pinned in factory
Failure recoveryArmed rollback + a connectivity-proving health gateBOOT button pulls a fresh image off the network
InterfaceOne transport per boot — the wire wins and the radio never startsEthernet and Wi-Fi up simultaneously, with different jobs

The trade reads cleanly in both directions. Keeping OTA inside the app is what lets this firmware cooperate with its own update — leasing heap, standing the MQTT publisher and X10A poller aside, quiescing an in-flight weather request. Moving OTA into a factory partition makes that cooperation impossible and simultaneously unnecessary, because the app is already stopped — and it removes the one failure this project cannot defend against: a healthy image that boots, proves connectivity, seals itself in, and can never update again.



다이킨 알테르마 → MQTT 브리지 — 지하실까지 들어가는 케이블 한 가닥, 그걸 찾아내는 레지스터 하나

#W5500 #MACRAW #ESP32S3 #PoE #런타임감지 #ESP-IDF #MQTT #HomeAssistant #히트펌프 #ModbusTCP #OTA

📚 배경: 오픈소스 홈오토메이션 펌웨어 (MIT, v1.0.0, 451 커밋). 1인 개발 프로젝트치고 엔지니어링 규율이 대단히 단단하다 — 순수 로직은 호스트에서 테스트하고, CI가 소스 텍스트 자체를 대상으로 계약 테스트를 걸며, 문서가 모든 결정의 이유를 명시한다.

구현 상태: 완전 구현 및 실동작. X10A 프로토콜은 실제 버스 캡처로 검증되었고, W5500 유선 전송 경로는 런타임 감지 · 실패 시 완전 되감기 · 케이블 분리 복구 정책까지 갖춘 완결된 브링업 코드다. 그리고 그 전부가 호스트 테스트와 CI 계약 테스트로 고정되어 있다.


01 — 이 프로젝트는 무엇인가?

다이킨 알테르마 히트펌프는 자기 자신에 대해 아주 많은 것을 알고 있다. 유량 온도, 컴프레서 상태, 제상 사이클, 온수 탱크 거동까지. 그런데 그중 밖으로 내주는 건 거의 없다. 그 숫자들이 유일하게 밖으로 나오는 지점이 실내기 PCB의 X10A 서비스 헤더다. 9600 8E1, 5 V TTL UART, 반이중, 폴링 전용. 유닛은 값을 먼저 내보내는 법이 없고 질문에만 답한 뒤 조용해진다.

daikin-altherma-esp32ESP32-S3용 ESP-IDF 펌웨어로, X10A를 탭해 두 가지 프로토콜 변종(I/S)을 모두 디코드하고, 원시 레지스터 페이지를 이름 붙은 값 카탈로그로 변환한 뒤 MQTT + Home Assistant 디스커버리로 전부 퍼블리시한다. 그 코어 주변에는, 기계실에 몇 년씩 방치될 장치가 실제로 필요로 하는 것들이 붙어 있다.

  • 내장 웹 UI — 라이브 대시보드 하나. 펌웨어가 현재 책임질 수 없는 값은 숫자 대신 로 표시한다
  • 서명된 듀얼 슬롯 OTA (약 2.03 MB 슬롯 2개)와 브라우저 Web Serial 인스톨러
  • 상위 플래시의 4 MiB 웨어 레벨링 히스토리 저널 — 256바이트 레코드의 원형 append-only 링, CRC 적용, 커밋 워드를 맨 마지막에 프로그래밍해서 정전이 앞선 레코드들을 무효화하지 못하게 했다
  • 선택적 2차 데이터 소스: mDNS로 발견하는 다이킨 HomeHub를 Modbus TCP로 읽음
  • 선택적 ENV III 외기 환경 센서 (I²C)
  • 트렌드 저널을 평문 진단으로 바꿔주는 24시간 플랜트 체크업

이 펌웨어는 구조적으로 읽기 전용이다. X10A 프로토콜에 쓰기 명령 자체가 없고, Modbus 링크도 코드상 쓰기 경로가 없다 — 어느 소스 파일도 Modbus 쓰기 프레임을 만들 수조차 없으며, CI 계약 테스트가 main/ 아래 모든 파일을 훑어 그 상태를 유지시킨다.

그리고 히트펌프와는 아무 상관 없고 건물과는 전적으로 상관있는 문제가 등장한다. 기계는 지하실에 있고, 와이파이는 없다. 히트펌프는 기계실, 콘크리트 뒤, AP에서 가장 먼 쪽에 산다. 매일 저녁 네트워크에서 떨어지는 텔레메트리 브리지는 텔레메트리 브리지가 아니다.

그 답이 WIZnet W5500 기반 선택적 유선 전송 경로다. 실물로는 M5Stack AtomS3 Lite를 ATOMIC PoE Base에 얹은 형태이고, 케이블 한 가닥이 전원과 LAN을 동시에 나른다. 그리고 그 전송 경로가 어떻게 설계되었는지가 이 프로젝트를 읽을 가치가 있게 만든다.


02 — 왜 유선이어야 했고, 왜 선택적이어야 했나

🔷 이유 1 — 히트펌프가 사는 곳까지 전파가 안 간다

콘크리트, 기계실, 지하실, 별동. 설치자에게 랜선이나 PoE 스위치는 있는데 현실적인 와이파이 경로는 없는 전형적인 상황이다. PoE는 이걸 완전히 닫는다: 유닛 옆에 콘센트도, 중계기도, 전력선 어댑터도 필요 없다. 한 가닥, 한 번의 배선으로 끝.

🔷 이유 2 — 이 정도 보드에서 라디오는 비싸다

와이파이 스테이션을 올리면 힙이 대략 50 KB 나간다. 그런데 이 장치의 실질적 제약은 전체 여유 힙이 아니라 가장 큰 연속 블록이다. 유선으로 부팅한 보드는 라디오를 아예 시작하지 않고, 같은 서브넷에 두 번째 netif를 공짜로 얹지도 않는다.

🔷 이유 3 — 그렇다고 두 번째 펌웨어가 될 수는 없었다

CI는 정확히 esp32s3 이미지 하나만 배포한다. 컴파일 타임 보드 선택으로 만들면 바이너리·OTA 매니페스트·웹 인스톨러가 보드마다 갈라진다. 산출물 세 종류가 보드 수만큼, 영원히. 그래서 유선 전송 경로는 같은 이미지가 런타임에 발견하는 무언가여야만 했다. 아래 W5500 설계 전체를 끌고 가는 게 바로 이 제약이다.

🔷 WIZnet 생태계 관점 — 내장 MAC과의 "둘 중 하나" 문제가 아니다

설계 공간 자체를 결정하는 사실이라 분명히 짚고 간다. ESP32-S3에는 내장 이더넷 MAC이 없다. 초기 ESP32의 EMAC 페리페럴이 S3로 넘어오지 않았기 때문에, 익숙한 "RMII + 외부 PHY(LAN8720 / LAN8742A)" 경로는 이 칩에서 불편한 정도가 아니라 아예 존재하지 않는다. ESP32-S3의 유선은 전부 SPI 이더넷 컨트롤러이고, 그 자리가 정확히 W5500의 자리다.

선택지ESP32-S3에서결론
내장 EMAC + RMII PHY불가 — S3에 EMAC 페리페럴 없음실리콘 레벨에서 탈락
ENC28J60 (SPI)10 Mbps 반이중, 8 KB 버퍼, 긴 errata 이력가능하지만 명백히 열세
WIZnet W5500 (SPI)10/100 오토네고 MAC+PHY, 32 KB 내부 버퍼, 1급 ESP-IDF 드라이버채택

WIZnet 라인업 내에서 같은 자리는 W5100SW6100(IPv6 듀얼스택 추가)이 함께 채우고, W55RP20은 같은 코어에 RP2040을 합쳐 단일 패키지 해법을 만든다. 이 프로젝트에는 W5500이 패밀리 중 맞는 멤버다. M5Stack ATOMIC PoE Base가 이미 싣고 있는 부품이고, Espressif가 직접 배포·유지하는 드라이버 컴포넌트(espressif/w5500)가 있는 부품이기 때문이다.


03 — 시스템 아키텍처

 


04 — 왜 WIZnet W5500인가? ⭐

🔷 기술적 핵심: 선 네 가닥 뒤에 이더넷 프런트엔드 전체

W5500은 10/100 오토네고 MAC과 PHY가 한 패키지에 들어간 부품이고, 전부 SPI로 제어된다. ESP32-S3에서 이건 편의가 아니라 유선 이더넷이 취할 수 있는 유일한 형태다. PHY를 붙일 EMAC 자체가 칩에 없기 때문이다. 패드 네 개(SCLK 5 · CS 6 · MISO 7 · MOSI 8), SPI mode 0 · 20 MHz면 보드가 LAN에 올라간다.

중요한 건 네 개가 트레이드가 감당 가능할 만큼 적다는 점이다. AtomS3 Lite에서 PoE 베이스는 사이드 헤더 전체를 점유하는데, 남는 Grove 포트가 정확히 X10A가 필요로 하는 자리다. 24 × 24 mm 보드에서 히트펌프 링크와 네트워크 링크가 공존할 수 있는 이유는, 이더넷 컨트롤러가 RMII PHY라면 요구했을 14개 대신 4개만 요구했기 때문이다.

🔷 사용 모드: MACRAW (Sn_MR = 0x04) — 그리고 TOE를 의도적으로 쓰지 않는 이유

이게 이 통합의 정체성이므로 정확하게 짚는다.

espressif/w5500 컴포넌트는 이 부품을 순수 이더넷 MAC으로 몬다. 소켓 0을 MACRAW 모드에 놓고, 16 KB RX 버퍼 전체를 그 소켓 하나에 할당한 뒤, 생 이더넷 프레임을 SPI로 양방향 실어 나른다. TCP·UDP·IP·ARP·DHCP·DNS는 전부 그 , ESP32-S3의 lwIP에서 일어난다. 8비트 MCU에서 W5500을 W5500답게 만드는 그 유명한 하드와이어드 TCP/IP 스택 — TOE는 여기서 의도적으로 꺼져 있다.

이건 한계가 아니다. 이 펌웨어가 실제로 필요로 하는 것에 대한 정확한 공학적 답이다.

펌웨어가 필요로 하는 것어디서 오는가
서명 HTTPS OTA와 날씨 피드용 TLSESP32-S3의 mbedTLS
HA 디스커버리 MQTT 클라이언트, syslog, SNTPlwIP 위 ESP-IDF 컴포넌트
mDNS 응답자 (<hostname>.local)lwIP 위 espressif/mdns
캡티브 DNS 서버, 36개 라우트의 HTTP 서버lwIP 위 esp_http_server
물리적으로 없는 이더넷 MAC과 PHYW5500

하드웨어 TCP 소켓은 mbedTLS 세션도, MQTT 클라이언트 라이브러리도, 36개 라우트 HTTP 서버도 실어 나를 수 없다. 그것들은 이미 ESP32-S3가 가진 스택 상주 기능이다. SoC에 없던 건 아래 두 계층이었다. MACRAW는 정확히 그것만 공급하는 모드이고, 그래서 W5500 하나가 위쪽 코드 한 줄도 안 바꾸고 기존의 완성된 소프트웨어 스택 밑으로 들어갈 수 있었다. net.hpp가 결론을 직접 적어둔다 — 이건 2차 소스가 아니라 2차 전송 경로다. MQTT·syslog·SNTP·OTA·HTTP가 유선 위에서 그대로 돌고, 그 위의 브리지는 어느 쪽이 자기를 나르는지 알지도 신경 쓰지도 않는다.

메이커 입장에서 가져갈 교훈은 이거다. W5500에는 서로 다른 두 개의 커리어가 있다. 스택이 없는 소형 MCU에서는 소켓을 공짜로 주는 TOE다. lwIP와 mbedTLS를 이미 소유한 애플리케이션 SoC에서는 MACRAW로 도는 깔끔한 SPI 부착형 MAC+PHY다. 같은 실리콘, 데이터시트의 반대쪽 절반.

🔷 레지스터 하나가 보드 변종을 런타임 기능으로 바꾼다

이 프로젝트에서 W5500이 해주는 가장 우아한 일이고, 비용은 SPI 트랜잭션 한 번이다.

VERSIONR은 W5500 Common Register 블록의 오프셋 0x0039에 있고, 지금까지 만들어진 모든 W5500에서 고정값 0x04를 읽는다. 펌웨어는 부팅 시 정확히 그 한 바이트를 읽는다. 주소 페이즈 3바이트 + 데이터 1바이트, 총 4바이트 전이중 전송 한 번.

  • 0x04가 읽히면 → 그 패드에 컨트롤러가 물려 있다. netif를 올리고 lease를 받고, 와이파이 라디오는 아예 시작하지 않는다.
  • 다른 값이 읽히면 → SPI 버스를 다시 해제한다. 보드는 이 코드가 없던 시절의 와이파이 장치와 바이트 단위로 동일해진다. netif도, 드라이버도, 태스크도, 이벤트 핸들러도 없다.

MISO가 플로팅이면 0x00 또는 0xFF가 읽히므로 현실적인 오탐이 없다. 결정론적인 이 레지스터 하나 덕분에 CI는 모든 보드에 이미지 하나를 계속 배포할 수 있다. 유선 변종과 무선 변종이 같은 바이너리이고, 차이는 컴파일 타임에 선언되는 대신 부팅 시 마이크로초 단위로 발견된다. 고정 레지스터에서 읽는 대신 동작으로 정체를 추측해야 하는 부품이었다면 이 설계는 성립하지 못했다.

🔷 폴링 동작 — 베이스가 인터럽트 선을 안 빼주기 때문에

ATOMIC PoE Base는 SCLK·CS·MISO·MOSI와 전원만 라우팅한다. 배선할 INT 핀이 없다. 그래서 드라이버는 int_gpio_num = -1, poll_period_ms = 10으로 돈다. 이건 우회책이 아니라 정식 지원 모드이고(Espressif가 정확히 이 구성의 CI 설정을 배포한다), 프로젝트 자체 Kconfig 도움말이 숫자의 의미를 정확히 적어뒀다. 이 값은 처리량이 아니라 RX 지연을 제한한다 — 폴링 한 번마다 W5500 버퍼에 이미 쌓인 걸 전부 비운다. 온칩 16 KB RX 버퍼가 있기에 10 ms 폴링이 패킷 손실 예산이 아니라 여유 있는 설계점이 된다.

🔷 꼼꼼한 통합이 챙긴 디테일들

  • MAC 주소: W5500은 자기 MAC이 없어서 펌웨어가 공급한다. ESP32-S3의 eFuse 기반 이더넷 주소인 ESP_MAC_ETH. 재부팅해도 안정적이고 와이파이 STA MAC과 구분되므로 두 인터페이스가 같은 LAN에서 충돌할 수 없다.
  • 라우트 우선순위: ESP-IDF 기본값이 WIFI_STA_DEF 100, ETH_DEF 50이라 유선이 그냥 두면 진다. net.cpp가 이더넷 netif를 128로 올린다. 코드 주석은 이게 off-link 트래픽만 지배한다는 점까지 정직하게 적어뒀다. lwIP의 on-link 라우팅은 그와 무관하게 netif_list 순서를 따르기 때문이다.
  • 히트펌프를 지키는 프로브 가드: 식별 프로브는 클럭과 칩셀렉트를 구동한다. 문서화된 AtomS3 Lite 대안 배선은 X10A를 GPIO5/6에 놓는데, 같은 패드다. 그래서 net_eth_probe_allowed()는 네 패드 중 하나라도 설정된 X10A / 센서 / 인디케이터 / 버튼 핀과 겹치면 프로브 자체를 거부한다. 이 가드가 없으면 부팅 프로브가 히트펌프의 살아있는 서비스 버스에 SPI 엣지를 때려넣는다.
  • 완전한 실패 되감기: 프로브가 성공하면 SPI 버스가 설치된 상태로 남는다. 이후 어떤 할당/기동 실패든 전부를 엄격한 역순으로 되돌린다 — 드라이버 stop, 핸들러 해제, netif glue 삭제, 드라이버 uninstall, MAC 삭제, PHY 삭제, netif destroy, 그다음에야 spi_bus_free(). 반쯤 되감긴 이더넷 시도는 유선 경로를 죽인 바로 그 힙 압박을 와이파이 폴백에 그대로 물려주기 때문이다.
  • 케이블 분리 복구를 정책으로 명시: 유선으로 부팅한 보드는 라디오가 안 돌고 있고, 이벤트 핸들러에서 안전하게 라디오를 키울 수도 없다. 그래서 케이블이 뽑히면 약 30초 뒤 의도적으로 재부팅해 같은 부트 분기를 처음부터 다시 태운다. 24시간 트렌드는 살아남는다. 와이파이가 아예 설정돼 있지 않으면 재부팅조차 하지 않고, 그냥 선을 기다렸다가 돌아오면 다시 잡는다.

🔷 검증된 증거 ✅

  • VERSIONR = 0x04 감지가 net.cpp에 구현되어 실제 부트 분기로 사용됨
  • main/idf_component.ymlespressif/w5500: "^2.0.0" 고정, dependencies.lock으로 잠금
  • ✅ 핀·클럭·폴링 주기·DHCP 대기창 전부 Kconfig(CONFIG_DAIKIN_ETH_*)로 노출
  • ✅ 전송 정책을 IDF 비의존 logic/net_link.hpp로 분리해 호스트 테스트 — 라우트 소유권, 부트 분기, 포털 억제, 프로브 허용, 케이블 분리 복구
  • test/test_transport_contract.mjs가 소스 텍스트 자체를 검사해 호출부가 그 규칙들을 여전히 묻고 있는지 단언. wifi_start_sta()를 다시 무조건 호출하는 코드가 들어오지 못하게 하는 항목 포함
  • ✅ 사용자 가시 동작 문서화: Connections 타일에 협상 속도를 보여주는 Ethernet 행이 생기고, 케이블 없음케이블 연결됨, 주소 없음을 구분해서 표시
  • ✅ 전송 경로를 끄면(CONFIG_DAIKIN_ETH_ENABLED=n) 플래시 약 40 KB를 회수하고 이더넷 코드가 아예 링크되지 않음

05 — 핵심 구성 요소

🌐 WIZnet W5500 — MACRAW 모드 (Sn_MR = 0x04), 소켓 0, 16 KB RX 버퍼 전체

선택적 유선 전송 경로. SCLK 5 · CS 6 · MISO 7 · MOSI 8에서 SPI mode 0 · 20 MHz, INT 선 없이 10 ms 폴링으로 도는 10/100 오토네고 MAC+PHY. 부팅 시 VERSIONR(0x0039)을 읽어 고정값 0x04와 비교해 감지한다. TCP/IP는 호스트 SoC의 lwIP가 처리하고, W5500은 ESP32-S3가 실리콘으로 갖지 못한 두 계층을 공급한다.

🔲 ESP32-S3 (M5Stack AtomS3 Lite / Seeed XIAO ESP32-S3)

플래시 8 MB 이상 필수 — 4 MB 아래에 약 2.03 MB OTA 슬롯 두 개, 상위 4 MiB 전체는 히스토리 저널 전용. PSRAM은 쓰지 않으며 힙 규율 자체가 내부 RAM만 가정한다. 브라우저 인스톨러를 위한 네이티브 USB-Serial/JTAG.

🔌 M5Stack ATOMIC PoE Base

W5500 캐리어. 케이블 한 가닥에 전원과 LAN — 지하실에서는 이게 전부다. GPIO 5/6/7/8/38, 즉 AtomS3 Lite 사이드 헤더 전체를 점유하는데, 펌웨어는 이걸 사용자가 발견하게 두지 않고 명시된 제약으로 바꾼다. 컨트롤러가 감지되는 순간 그 네 패드는 모든 핀 선택 UI에서 사라지고, 그 핀을 지정한 설정 POST는 거부된다.

📦 espressif/w5500 ^2.0.0 (managed component) · ESP-IDF ≥ 6.0

ESP-IDF 6.0에서 SPI 이더넷 칩 드라이버들이 코어 esp_eth 밖으로 빠졌고, 지금 esp_eth는 MAC/PHY 프레임워크와 내부 EMAC만 싣는다. W5500 MAC/PHY 쌍은 컴포넌트 레지스트리에서 가져오며, ^2.0.0 라인이 6.x esp_eth API에 맞춰 빌드된 라인이라 이게 프로젝트의 실제 IDF 하한이다.

🔗 X10A UART 탭 · 다이킨 HomeHub (Modbus TCP) · M5Stack ENV III

세 개의 데이터 소스, 전부 읽기 전용. 같은 물리량을 X10A와 HomeHub가 둘 다 줄 때는 X10A가 주도하고, Modbus는 라벨이 붙은 2차 값으로 계속 보이면서 X10A가 죽었을 때 폴백을 맡는다.


06 — 응용 시나리오

01. PoE 기반 기계실·지하실 텔레메트리

출발점이자 가장 이식성 높은 케이스. 전파가 안 닿는 곳에 사는 모든 감시 대상 — 보일러, 히트펌프, 펌프, 컴프레서, 탱크 수위, 서브미터. PoE 한 가닥이 콘센트 + AP + 중계기를 대체한다.

02. 모든 ESP32-S3 제품의 유선 이더넷

S3에 내장 EMAC이 없기 때문에, 이 저장소는 케이블이 필요한 모든 ESP32-S3 설계에 그대로 재사용 가능한 레퍼런스다. 핀 계획, SPI 설정, 폴링 모드 근거, MAC 할당, 라우트 우선순위, DHCP 데드라인, 완전한 teardown 경로까지 다 있다. net.cpplogic/net_link.hpp만 들어내면 작업의 대부분이 끝난다.

03. 유선/무선 SKU를 하나의 펌웨어 이미지로

VERSIONR 프로브는 디테일이 아니라 패턴이다. 와이파이 SKU와 유선/PoE SKU를 함께 출하하는 제품이라면 이 방식으로 바이너리 하나, OTA 매니페스트 하나, 인스톨러 하나를 유지하면서 하드웨어가 스스로를 알리게 할 수 있다. 소규모 제조사에게 릴리스 엔지니어링 표면적이 의미 있게 줄어드는 이야기다.

04. 무선이 불가능하거나 금지된 환경의 읽기 전용 산업 게이트웨이

아키텍처 전체 — 어디에도 쓰기 경로가 없고 CI가 그걸 강제하며, 신뢰 표면이 유선 LAN에는 전체 API를 열고 개방형 셋업 라디오에는 닫으며, syslog·SNTP·서명 OTA가 전부 전송 계층에 무관한 구조 — 가 무선이 비현실적이거나 금지된 환경의 모니터링 브리지 템플릿으로 좋다.


결론

지하실의 히트펌프에는 케이블이 필요하고, 바이너리 하나만 배포하는 프로젝트는 보드 변종을 감당할 수 없다. 0x0039의 W5500 레지스터 하나가 둘 다 해결한다.

  • ✅ X10A 서비스 포트 완전 디코드(I/S 변종), 실제 버스 캡처로 검증
  • ✅ Home Assistant 디스커버리 MQTT, 내장 라이브 웹 UI, 서명 듀얼 슬롯 OTA, 브라우저 인스톨러
  • ✅ CRC와 마지막 프로그래밍 커밋 워드를 갖춘 4 MiB 웨어 레벨링 원형 히스토리 저널
  • MACRAW 모드의 선택적 W5500 유선 전송 경로 — 실리콘에 없는 MAC+PHY를 ESP32-S3에 공급
  • VERSIONR = 0x04 런타임 감지 — 바이너리 하나가 유선 보드와 무선 보드를 함께 담당
  • ✅ M5Stack ATOMIC PoE Base로 PoE 대응: 케이블 한 가닥이 전원과 LAN을 기계실까지 운반
  • ✅ 이더넷이 기본 라우트를 차지(netif 우선순위 128)하고 와이파이 스테이션과 캡티브 포털을 모두 억제, 유선 부팅 시 힙 약 50 KB 절약
  • ✅ 설정된 X10A 서비스 버스에 SPI를 클럭하지 않도록 거부하는 프로브 가드
  • ✅ 유선 브링업 실패가 와이파이 폴백을 오염시키지 않게 하는 완전한 역순 되감기
  • ✅ 전송 정책을 호스트 테스트 가능한 순수 로직으로 분리하고 소스 텍스트 CI 계약 테스트로 고정

Q&A

Q. W5500의 최대 장점이 하드와이어드 TCP/IP 스택인데, MACRAW로 쓰면 그걸 버리는 거 아닌가? ESP32-S3가 이미 완전한 네트워크 스택을 소유하고 있기 때문이다. lwIP, mbedTLS, MQTT 클라이언트, mDNS 응답자, 36개 라우트 HTTP 서버까지. 반대로 갖지 못한 게 이더넷 MAC과 PHY다. S3는 초기 ESP32의 EMAC 페리페럴을 통째로 뺐다. MACRAW는 빠져 있던 아래 두 계층만 정확히 공급하고 위쪽은 건드리지 않으며, 그래서 MQTT 브리지 코드 한 줄 안 바꾸고 유선이 기존 펌웨어 밑으로 미끄러져 들어갈 수 있었다. 스택이 없는 소형 MCU였다면 같은 칩을 반대로, TOE 모드로 쓰는 게 맞다.

Q. MACRAW 모드는 TOE 대비 실제로 어떤 비용을 치르나? 모든 프레임이 SPI를 건너오고 프로토콜 스택을 호스트 CPU가 돌리므로, TOE가 흡수했을 SPI 대역폭과 CPU 사이클을 쓴다. 9600 보드율 시리얼 버스를 폴링해 MQTT 값 수십 개를 퍼블리시하는 텔레메트리 브리지에서 그 비용은 보이지 않는 수준이고, 온칩 16 KB RX 버퍼가 폴링 사이의 버스트를 흡수한다. INT 선 하나 없이 10 ms 폴링이 성립하는 이유가 정확히 그것이다.

Q. 왜 W5500의 인터럽트 출력을 안 쓰고 폴링하나? M5Stack ATOMIC PoE Base가 SCLK·CS·MISO·MOSI와 전원만 라우팅해서 배선할 INT 핀 자체가 없다. 폴링 동작은 우회책이 아니라 ESP-IDF가 정식 지원하는 구성이고, 폴링 주기는 처리량이 아니라 지연을 제한한다. 폴링 한 번마다 W5500이 이미 버퍼링해둔 것을 전부 비우기 때문이다.

Q. 같은 바이너리가 어떻게 이더넷 있는 보드와 없는 보드를 동시에 담당하나? 0x0039VERSIONR은 모든 W5500에서 고정 0x04를 읽고, 플로팅 MISO는 0x00 또는 0xFF를 읽는다. 따라서 부팅 시 4바이트 SPI 전송 한 번이 "여기 컨트롤러가 물려 있다"와 "이 패드는 비어 있다"를 현실적 오탐 없이 구분한다. 찾으면 유선을 올리고 라디오를 건너뛴다. 못 찾으면 SPI 버스를 해제하고 와이파이 전용 빌드와 똑같이 동작한다.

Q. W5500이 쓰는 핀은 어디이고 그 대가는 무엇인가? SCLK 5 · CS 6 · MISO 7 · MOSI 8이며 전부 Kconfig로 변경 가능하다. AtomS3 Lite에서는 PoE 베이스가 사이드 헤더 전체를 덮으므로 X10A가 Grove 포트를 써야 하고, 결과적으로 이더넷 설치에는 선택적 ENV III 센서에 줄 핀이 남지 않는다. 펌웨어는 이 충돌을 사용자가 겪게 두지 않고 미리 명시한다. Seeed XIAO ESP32-S3에서는 같은 네 패드가 여유 있게 나와 있어 손배선 W5500 모듈로도 X10A와 ENV III 양쪽 핀이 남는다.

Q. WIZnet 부품으로 이 설계를 '가능하게' 하는 걸 넘어 '더 좋게' 만들 수 있나? 두 방향이 있다. 같은 SPI 자리에 W6100을 넣으면 IPv6가 필요한 네트워크를 위해 듀얼스택이 추가된다. 새로 설계한다면 W55RP20이 컨트롤러와 RP2040을 한 패키지로 합쳐준다. ESP32-S3의 와이파이가 더 이상 핵심이 아닌 전용 PoE 텔레메트리 노드라면 특히 매력적인 선택지다.


유사 콘텐츠 — ESP32-S3 + WIZnet: Update Your App Over Wi-Fi or Ethernet

두 프로젝트 모두 ESP32-S3에 WIZnet 컨트롤러를 얹고 OTA를 진지하게 다루지만, 업데이트 로직을 앱 경계의 정반대편에 놓는다. 여기서 비교할 가치가 있는 건 그 축 하나다.

OTA 관점이 프로젝트WIZnet OTA 부트로더
업데이트 로직의 위치앱 이미지 내부덮이지 않는 factory 파티션
앱 자체의 OTA 코드모든 빌드에 영구 필수없음 — 예제 앱은 네트워킹 0줄, 80줄
OTA를 빠뜨린 이미지USB 없이는 복구 불가정상 설치·동작
진입 조건앱이 서명된 HTTPS 매니페스트를 폴링BOOT 버튼, 빈 otadata, 또는 앱의 요청
다운타임없음 — 다운로드 중에도 앱이 계속 동작리부트 → 로더 → 다운로드 → 리부트
이미지 진위부트 슬롯 지정 전 RSA-3072 서명 검증 + 2점 다운그레이드 게이트전송 구간 HTTPS, 인증서는 factory에 고정
실패 복구무장된 롤백 + 연결성 증명 헬스게이트BOOT 버튼으로 네트워크에서 새 이미지 수신
인터페이스부팅당 하나 — 유선이 이기면 라디오는 아예 미기동이더넷과 와이파이 동시 기동, 역할 분리

트레이드는 양방향으로 깔끔하게 읽힌다. OTA를 앱 안에 두었기 때문에 이 펌웨어는 자기 업데이트와 협조할 수 있다 — 힙을 리스하고, MQTT 퍼블리셔와 X10A 폴러를 비켜세우고, 진행 중인 날씨 요청을 유한 시간 안에 잠재운다. OTA를 factory 파티션으로 옮기면 그 협조가 불가능해지는 동시에 불필요해진다. 앱이 이미 정지해 있기 때문이다. 그리고 그 이동은 이 프로젝트가 방어할 수 없는 단 하나의 실패를 제거한다 — 부팅되고, 연결성을 증명하고, 스스로를 봉인한 뒤, 다시는 업데이트되지 못하는 건강한 이미지.

Documents
Comments Write