---
title: "Why Does an Escape Room Run Its W5500 Boards on MicroPython?"
url: "https://maker.wiznet.io/Grace_Koo/projects/morseflow/"
markdown_url: "https://maker.wiznet.io/Grace_Koo/projects/morseflow/md"
type: "UCC: User Created Content"
author: "Morsey"
author_url: "https://github.com/Morsey/MorseFlow"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "Morsey"
original_url: "https://github.com/Morsey/MorseFlow"
published: "2026-09-03"
language: "en"
hardware: ["WIZnet W5500-EVB-Pico2"]
likes: 0
views: 92
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# 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.

Original author: Morsey (source: https://github.com/Morsey/MorseFlow)

## Components

- **WIZnet W5500-EVB-Pico2** x 1 ([docs](https://wiznet.io/products/powered-by-raspberry-pi/w5500-evb-pico2))

## Article

## MorseFlow — Running a W5500-EVB-Pico2 on the WIZnet MicroPython Build

`#MicroPython` `#W5500` `#W5500EVBPico2` `#RP2350` `#MACRAW` `#lwIP` `#MQTT` `#WiredOnly`

> 📚 **Context**: [Morsey/MorseFlow](https://github.com/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](https://github.com/WIZnet-ioNIC/WIZnet-EVB-Pico-micropython/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.md` states 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.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](https://github.com/WIZnet-ioNIC/WIZnet-EVB-Pico-micropython/wiki), 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](https://github.com/micropython/micropython/pull/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.

| 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_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 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, 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

- [**How to Build Commercial MicroPython Ethernet Prototypes with WIZnet W5500 on W5500-EVB-Pico**](https://maker.wiznet.io/chen/projects/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**](https://maker.wiznet.io/irina/projects/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**](https://maker.wiznet.io/ruilixin6/projects/pico-micropython-mqtt-client-subscription-messagehandling-explained/) explains the same combination — Pico, MicroPython, MQTT — from the fundamentals. Read next to MorseFlow's `mqtt_service.py` it 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](https://github.com/micropython/micropython/pull/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](https://github.com/WIZnet-ioNIC/WIZnet-EVB-Pico-micropython/wiki) · **Upstream PR**: [micropython#18035](https://github.com/micropython/micropython/pull/18035) · **Upstream driver**: [extmod/network_wiznet5k.c](https://github.com/micropython/micropython/blob/master/extmod/network_wiznet5k.c)

---

Source: https://maker.wiznet.io/Grace_Koo/projects/morseflow/
