Why Does an Escape Room Run Its W5500 Boards on MicroPython?
Wired MQTT escape-room control on W5500-EVB-Pico2, using the WIZnet MicroPython build whose release ships both LWIP and TOE images.
0
Project description
MorseFlow โ Running a W5500-EVB-Pico2 on the WIZnet MicroPython Build
#MicroPython #W5500 #W5500EVBPico2 #RP2350 #MACRAW #lwIP #MQTT #WiredOnly
๐ Context: Morsey/MorseFlow โ a wired, MQTT-based escape-room prop control framework. Last pushed 2026-09-26.
LICENSE.mdmakes it non-commercial, so not open source. โ Verification: Checked against the repository atmain, upstream MicroPython (ports/rp2/boards/,extmod/network_wiznet5k.c) and the WIZnet port wiki. Not run on hardware here.
01 โ What the repository builds
A control system for a whole escape room, split across three roles:
| Part | Responsibility |
|---|---|
| Node-RED | Puzzle and game state, issues commands |
| MQTT broker | Wired transport, tracks boards through retained messages and LWT |
| Morseboard firmware | Prop power and signal lines, status publishing, endless reconnect |
Board configs are committed for mb-001 through mb-010; four of them are filled in.
Here is what one room looks like. The real prop wiring for CMCM (The Curse of Mount Clifton Manor) is recorded in docs/cmcm/props.md.
| Prop | Signal A | Signal B |
|---|---|---|
| Candle | Candle LED output | IR lit-detected input |
| Demon seal | RFID wrong-card input | RFID correct-card input |
| Demon knocker | Solenoid output | NeoPixel data output |
| Hang the Dolls | RFID wrong-card input | RFID correct-card input |
The point is that a port gives you exactly two signal lines, A and B, and those two mean something different on every prop. On one port A is an output driving an LED; on another it is an input reporting a wrong card. Which is why each board carries its own config file.
At the far end sits a PIB (Prop Interface Board), turning the RJ45 power and signals into prop-specific behaviour.
- Sometimes there is none. The five demon knockers on mb-002 have no PIB microcontroller โ the Morseboard drives the solenoids and NeoPixel data lines directly.
- When there is one, it is an RP2040-Zero-class board. The repository carries firmware for an RFID reader (PN5180), a ToF sensor (VL53L0X), an OLED character display and an e-paper display.
The design rule is written down too. docs/cmcm/overview.md says to build against the real room first, document boards, ports and topics as they are actually wired, and generalize only when real reuse appears.
The pin contract of one board
| Function | GPIO |
|---|---|
| DFPlayer UART TX / RX | 0 / 1 |
| Switched 5 V prop power | 15 |
| W5500 MISO / CS / SCK / MOSI | 16 / 17 / 18 / 19 |
| W5500 RESET | 20 |
| W5500 INT, also boot REPL button | 21 |
| Optional onboard relay | 22 |
- Eight RJ45 prop ports. Every cable brings GND, switched 5 V, always-live 12 V, Signal A and Signal B.
- GPIO16โ21 are reserved for Ethernet and off-limits to props.
- Hold GPIO21 low during boot and the application is skipped, leaving the board at the USB REPL โ the field recovery path.
- No radio at all.
docs/architecture.mdstates that "Wi-Fi is intentionally out of scope."
MQTT topics
Everything sits under morseflow/<site>/<room>/<board_id>.
| Topic | Direction | Retained |
|---|---|---|
| status | board to broker | yes |
| state | board to broker | yes |
| event | board to broker | no |
| cmd/power, cmd/relay, cmd/port/n, cmd/audio | Node-RED to board | โ |
A board publishes an offline Last Will and Testament, and on reconnect republishes online status and state and re-subscribes to its command topics.
02 โ Why MicroPython
The repository never states a reason. But the design and the field log point one way.
Deployment is a file copy. The field log says it plainly โ copy board_configs/mb_010.py to board_config.py. No compile, no flash. config.py's _load_board_config() then overrides every uppercase name.
The wiring keeps changing in the field. From docs/cmcm/board-status-log.md:
- mb-002 had physical ports 4 and 5 fail, so the knockers moved to 1, 2, 3, 6 and 7.
- An earlier note had said ports 6 and 7 were the broken ones; it was corrected later.
- A comment in mb_001.py: the RFID PIB is currently wired with Signal A as wrong-card and Signal B as correct-card.
That is the kind of thing a config has to absorb rather than code โ and here it is one file, no rebuild.
REPL access is a requirement. docs/firmware.md states the firmware must stay reachable over the USB serial REPL, and gives the reason it cannot be moved to UART0: UART0 belongs to the DFPlayer. Holding GPIO21 low at boot skips the application and lands at the REPL, and a log buffer exists so boot messages can be retrieved later with debug.dump(). This is designed around someone standing at the board.
The performance bar is low. Prop control is a few events per second and the timing is a ticks_ms() state machine. Nothing here fills an Ethernet link, so the interpreter's cost never becomes the problem.
On top of that, docs/ai-assistance.md discloses that OpenAI Codex helped โ and Python is the path of least resistance when scaffolding that way.
03 โ The firmware in the repository is a WIZnet release asset
firmware/downloads/ holds a 1.1 MB file:
LWIP-W5500_EVB_PICO2-v1.29.0-preview.uf2It can be traced. An asset of the same name sits in the v1.29.0-WIZnet-timeout release of WIZnet-EVB-Pico-micropython, the MicroPython port WIZnet maintains. That port covers both Pico and Pico2 variants โ W5100S, W5500, W6100, W6300, and W55RP20.
| Upstream MicroPython | WIZnet port | |
|---|---|---|
| WIZnet boards | W5500, W5100S (both RP2040) | Pico and Pico2 across the five families above |
| Network class | network.WIZNET5K | network.WIZNET6K |
| Build variants | one | LWIP and TOE |
Upstream's ports/rp2/boards/ has 38 board definitions and RP2350 is already supported through RPI_PICO2, but the only WIZnet boards are the two RP2040 ones, with no Pico2 variant.
Closing that gap is in progress. The wiki links upstream PR micropython/micropython#18035, still open. It adds the Pico2 variants along with W55RP20 and the W6300 family, and migrates the two existing boards from wiznet5k to wiznet6k.
The wiki marks the fix-wiznet-timeout branch as required for MQTT. MorseFlow being an MQTT application, and the file it picked up coming from a -WIZnet-timeout release, line up here.
The same release ships two images per board
LWIP-W5500_EVB_PICO2-v1.29.0-preview.uf2โ the chip in MACRAW with lwIP running on the MCUTOE-W5500_EVB_PICO2-v1.29.0-preview.uf2โ the chip's own hardware TCP/IP stack
MorseFlow ships the LWIP one. The repository does not say why.
04 โ So the code forks between two APIs
ethernet.py asks at runtime which one it has:
def _create_interface(self): if hasattr(network, "WIZNET6K"): return network.WIZNET6K()
if hasattr(network, "WIZNET5K"): spi = SPI(0, 2_000_000, mosi=Pin(pins.W5500_MOSI), miso=Pin(pins.W5500_MISO), sck=Pin(pins.W5500_SCK)) return network.WIZNET5K(spi, Pin(pins.W5500_CS), Pin(pins.W5500_RESET))
raise RuntimeError("MicroPython firmware does not include WIZnet Ethernet support")The only WIZnet network module in upstream extmod/ is network_wiznet5k.c, so network.WIZNET6K does not exist upstream. The hasattr check is really asking which build it is running on.
| Branch | Build it matches | SPI clock | Requests DHCP |
|---|---|---|---|
| WIZNET6K() | WIZnet release | from the board definition | yes |
| WIZNET5K(spi, cs, reset) | upstream release | 2 MHz, opened in code | no |
- SPI clock. Upstream's
W5500_EVB_PICOboard setsMICROPY_HW_WIZNET_SPI_BAUDRATEto 20 MHz. The fallback opens SPI itself and comes up at a tenth of that. - DHCP branch. The condition is
if config.NETWORK_DHCP and hasattr(network, "WIZNET6K"), so it fires on one side only. The upstream lwIP driver already callsdhcp_start()when the interface is created, so an address still arrives โ but the control path the application intended exists on one side only.
05 โ How MicroPython drives the W5500
Opening the upstream driver shows the floor this project stands on. From the netif init in extmod/network_wiznet5k.c:
int ret = WIZCHIP_EXPORT(socket)(0, Sn_MR_MACRAW, 0, 0);...// Enable MAC filtering so we only get frames destined for us, to reduce load on lwIPsetSn_MR(0, getSn_MR(0) | Sn_MR_MFEN);- Socket 0 is opened in MACRAW with lwIP on top. The chip's hardware stack is not used.
- MAC filtering is on for the reason the comment gives: take only frames addressed to us and spare lwIP the rest.
- Builds without IPv6 drop IPv6 in the chip through
Sn_MR_MIP6B. MTU is 1500. - The driver's default SPI speed is 2 MHz, overridden to 20 MHz by a board definition. Bypassing the board definition falls back to the library default.
- The same file carries a chip-stack path (
WIZNET5K_PROVIDED_STACK), marked with a TODO that not all modnetwork and socket features are provided there.
06 โ What running it left behind
docs/firmware.md records what they hit on hardware.
wiznet5k_send_ethernet: fatal error -5โ the W5500 driver reporting a send failure. Check the wired link, the broker address and its availability. For bench testing without MQTT, setMQTT_ENABLED = False.- MQTT startup is gated on an address. The interface can report a link-like connected state before DHCP completes, so the firmware waits until
ifconfig()[0]is not0.0.0.0. That is whatis_ready()checks, and the application loop never calls into the MQTT service until it holds. - Reconnects retry indefinitely on a 5-second interval, and "link up but no address yet" is logged as its own state.
07 โ Where it stands
- The Current Status section calls it an initial scaffold. The skeleton is up โ non-blocking state machines, safe IO defaults, timed prop pulses, MQTT reconnect and status publishing โ and one room is being filled in.
docs/ai-assistance.mddiscloses that OpenAI Codex helped, while direction, hardware requirements, testing on real boards and final acceptance stay with the author.- The uf2's origin is not stated in the repository, though the filename matches a WIZnet release asset and can be followed. It is still a preview build.
- The licence is non-commercial, so using the code commercially needs the author's written permission.
08 โ Similar Projects on WIZnet Makers
- How to Build Commercial MicroPython Ethernet Prototypes with WIZnet W5500 on W5500-EVB-Pico covers the same ground from the other end: building commercial prototypes on Pico 1 with upstream firmware. This article is about what happens crossing to Pico 2, where upstream has no board.
- Phone Prop Controller โ ESP32-S3 + W5500 Escape Room Telephone with MQTT/ProSLIC is the closest in purpose โ the same escape-room props over the same MQTT. The difference is scope: that one finishes a single telephone prop on an ESP32-S3, while MorseFlow lays out a framework for a whole room across ten boards and eighty RJ45 ports.
- Pico MicroPython MQTT Client: Subscription & Message Handling explains the same combination โ Pico, MicroPython, MQTT โ from the fundamentals. Read next to MorseFlow's
mqtt_service.pyit shows why reconnect, re-subscribe and LWT matter once a room depends on them.
Q&A
Q. Why is a firmware uf2 committed to the repository? Because upstream MicroPython has no W5500-EVB-Pico2 board definition yet. The rp2 port has 38 boards including RPI_PICO2, but the only WIZnet ones are W5500_EVB_PICO and W5100S_EVB_PICO, both RP2040. The port WIZnet maintains supports all nine boards and publishes release uf2 images, and upstreaming is tracked in PR #18035.
Q. What separates WIZNET6K from WIZNET5K here? Upstream extmod/ contains only network_wiznet5k.c, so WIZNET6K is not there. The firmware checks with hasattr at runtime: if it exists, call WIZNET6K(); otherwise build WIZNET5K() by passing SPI, CS and RESET explicitly.
Q. Which mode does MicroPython run the W5500 in? Socket 0 in MACRAW with lwIP above it. MAC filtering (Sn_MR_MFEN) is enabled so only frames addressed to the board arrive, and on builds without IPv6 the chip drops IPv6 through Sn_MR_MIP6B. The driver also contains a chip-stack path, marked as not feature-complete.
Q. Why no Wi-Fi? docs/architecture.md puts it deliberately out of scope. Each board fans out to eight RJ45 prop ports carrying power and signals together, so the cabling is going in regardless.
Q. Can the code be reused? The licence is source-available for non-commercial use only. Commercial use or copying needs the author's prior written permission.
Original Link: https://github.com/Morsey/MorseFlow
Hardware contract: docs/hardware.md ยท MQTT convention: docs/mqtt.md ยท WIZnet MicroPython port: WIZnet-EVB-Pico-micropython wiki ยท Upstream PR: micropython#18035 ยท Upstream driver: extmod/network_wiznet5k.c
