Skip to content

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.

Grace_Koo

Published September 03, 2026

Original author: MorseyOriginal source (new tab)

Why Does an Escape Room Run Its W5500 Boards on MicroPython?

Components

Hardware components

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.md makes it non-commercial, so not open source. โœ… Verification: Checked against the repository at main, 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:

PartResponsibility
Node-REDPuzzle and game state, issues commands
MQTT brokerWired transport, tracks boards through retained messages and LWT
Morseboard firmwareProp 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.

PropSignal ASignal B
CandleCandle LED outputIR lit-detected input
Demon sealRFID wrong-card inputRFID correct-card input
Demon knockerSolenoid outputNeoPixel data output
Hang the DollsRFID wrong-card inputRFID 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

FunctionGPIO
DFPlayer UART TX / RX0 / 1
Switched 5 V prop power15
W5500 MISO / CS / SCK / MOSI16 / 17 / 18 / 19
W5500 RESET20
W5500 INT, also boot REPL button21
Optional onboard relay22
  • 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.md states that "Wi-Fi is intentionally out of scope."

MQTT topics

Everything sits under morseflow/<site>/<room>/<board_id>.

TopicDirectionRetained
statusboard to brokeryes
stateboard to brokeryes
eventboard to brokerno
cmd/power, cmd/relay, cmd/port/n, cmd/audioNode-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.uf2
 

It 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 MicroPythonWIZnet port
WIZnet boardsW5500, W5100S (both RP2040)Pico and Pico2 across the five families above
Network classnetwork.WIZNET5Knetwork.WIZNET6K
Build variantsoneLWIP 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 MCU
  • TOE-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.

BranchBuild it matchesSPI clockRequests DHCP
WIZNET6K()WIZnet releasefrom the board definitionyes
WIZNET5K(spi, cs, reset)upstream release2 MHz, opened in codeno
  • SPI clock. Upstream's W5500_EVB_PICO board sets MICROPY_HW_WIZNET_SPI_BAUDRATE to 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 calls dhcp_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 lwIP
setSn_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, set MQTT_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 not 0.0.0.0. That is what is_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.md discloses 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


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

Comments

Similar projects you might like

Comments