An ESP32 W5500 MQTT node replaces Wi-Fi with a network cable, which is often the difference between "mostly works" and "just works" inside a metal control cabinet or on a factory floor. In this tutorial you will connect an ESP32 to wired Ethernet with a W5500 SPI module, or with a LAN8720 PHY on boards like the WT32-ETH01, handle link-up and link-down events, and publish machine data to broker.connectmq.com. The MQTT code barely changes; the interesting work is in the network layer.
Why Wired Ethernet for ESP32 MQTT
Wi-Fi is convenient but fragile in industrial environments. Steel enclosures shield the signal, motors and drives generate noise, IT departments restrict wireless clients, and access points get replaced or reconfigured without anyone telling the automation team. Ethernet removes most of these variables:
| Factor | Wi-Fi | Wired Ethernet |
|---|---|---|
| Inside a metal cabinet | Weak or no signal | Unaffected |
| Electrical noise from drives | Can disrupt the link | Generally robust with shielded cable |
| Credentials on the device | SSID and password needed | None for the link layer |
| Latency consistency | Varies with load and interference | Steady |
| Power | Separate supply | Optional PoE with a suitable module |
| Mobility | Yes | No |
The trade-off is cabling and a slightly more complex hardware setup. For a fixed node that reads a machine, a meter or a Modbus RS-485 device and forwards it to MQTT, wired Ethernet is usually the right call.
W5500 or LAN8720?
There are two common ways to give an ESP32 an Ethernet port.
| W5500 | LAN8720 | |
|---|---|---|
| Interface | SPI, works with any ESP32 variant | RMII, needs the internal EMAC of the original ESP32 |
| Pins used | 4 SPI pins plus interrupt and reset | About 10 fixed pins |
| Typical hardware | Small W5500 module, many S3 Ethernet boards | WT32-ETH01, Olimex ESP32-POE, LilyGO T-ETH |
| Throughput | Plenty for MQTT | Higher, also more than MQTT needs |
| Support in Arduino-ESP32 3.x | Built into ETH.h |
Built into ETH.h |
Both are supported natively by the ETH library in Arduino-ESP32 3.x, and both feed into the same lwIP network stack. That is the key point: once the link has an IP address, NetworkClient, PubSubClient, DNS and TLS all work exactly as they do over Wi-Fi. If you are on core 2.x, upgrade; W5500 support in ETH.h arrived with 3.x, and the older workaround libraries are no longer needed. The Arduino-ESP32 documentation and the ETH examples bundled with the core list the supported PHYs.
Wiring a W5500 Module
| W5500 pin | ESP32 pin | Note |
|---|---|---|
| MOSI | GPIO 23 | SPI data out |
| MISO | GPIO 19 | SPI data in |
| SCLK | GPIO 18 | SPI clock |
| SCS / CS | GPIO 5 | Chip select |
| INT | GPIO 4 | Interrupt, recommended |
| RST | GPIO 14 | Reset |
| 3.3V | 3V3 | Some modules have a 5 V input with a regulator |
| GND | GND | Common ground |
Keep SPI wires short, under about 10 cm on a breadboard. The W5500 can draw noticeable current during transmission, so power it from a solid 3.3 V rail rather than a weak USB-serial board regulator. Most link problems on a breadboard turn out to be power or wiring, not software.
The ESP32 W5500 MQTT Sketch
This sketch uses Arduino-ESP32 3.x and PubSubClient. It tracks the Ethernet state through events, connects MQTT only when the link has an IP address, and publishes telemetry every five seconds including link speed.
#include <ETH.h>
#include <SPI.h>
#include <NetworkClient.h>
#include <PubSubClient.h>
// W5500 pins
#define ETH_PHY_TYPE ETH_PHY_W5500
#define ETH_PHY_ADDR 1
#define ETH_PHY_CS 5
#define ETH_PHY_IRQ 4
#define ETH_PHY_RST 14
#define ETH_SPI_SCK 18
#define ETH_SPI_MISO 19
#define ETH_SPI_MOSI 23
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "eth-node-6b2d90";
const char* BASE = "connectmq/demo/eth-node-6b2d90";
NetworkClient net;
PubSubClient mqtt(net);
volatile bool ethReady = false;
unsigned long lastAttempt = 0, lastPublish = 0;
void onNetworkEvent(arduino_event_id_t event) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
ETH.setHostname("eth-node-6b2d90");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
Serial.println("Ethernet link up");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
Serial.printf("IP %s, %u Mbps, %s duplex\n", ETH.localIP().toString().c_str(),
ETH.linkSpeed(), ETH.fullDuplex() ? "full" : "half");
ethReady = true;
break;
case ARDUINO_EVENT_ETH_LOST_IP:
case ARDUINO_EVENT_ETH_DISCONNECTED:
Serial.println("Ethernet link down");
ethReady = false;
break;
case ARDUINO_EVENT_ETH_STOP:
ethReady = false;
break;
default:
break;
}
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("Command %s: %.*s\n", topic, (int)length, (const char*)payload);
}
bool connectMqtt() {
String status = String(BASE) + "/status";
if (!mqtt.connect(CLIENT_ID, nullptr, nullptr, status.c_str(), 1, true, "offline")) {
Serial.printf("MQTT failed, state=%d\n", mqtt.state());
return false;
}
mqtt.publish(status.c_str(), "online", true);
mqtt.subscribe((String(BASE) + "/cmd/#").c_str(), 1);
Serial.println("MQTT connected over Ethernet");
return true;
}
void setup() {
Serial.begin(115200);
Network.onEvent(onNetworkEvent);
SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI);
if (!ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI)) {
Serial.println("W5500 not detected, check wiring");
}
// Optional static IP for networks without DHCP:
// ETH.config(IPAddress(192,168,10,50), IPAddress(192,168,10,1),
// IPAddress(255,255,255,0), IPAddress(192,168,10,1));
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
mqtt.setSocketTimeout(5);
}
void loop() {
if (!ethReady) {
if (mqtt.connected()) mqtt.disconnect();
return;
}
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
return;
}
mqtt.loop();
if (millis() - lastPublish > 5000) {
lastPublish = millis();
char json[128];
snprintf(json, sizeof(json), "{\"uptime\":%lu,\"link\":%u,\"heap\":%u}",
millis() / 1000, ETH.linkSpeed(), ESP.getFreeHeap());
mqtt.publish((String(BASE) + "/telemetry").c_str(), json);
}
}
Using a LAN8720 board instead
On a WT32-ETH01 or similar LAN8720 board, remove the SPI lines and the W5500 defines and start Ethernet with the RMII parameters. For the WT32-ETH01 the common values are:
// type, PHY address, MDC, MDIO, power pin, clock mode
ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN);
Other boards differ in PHY address, power pin and clock mode, so check the board's schematic or vendor example. Everything after ETH.begin(), including the events and the MQTT code, stays identical.
Test the Node
Plug in the cable, open the serial monitor and look for the IP ... Mbps line followed by MQTT connected over Ethernet. Then subscribe from any computer:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-6b2d90 -v -t "connectmq/demo/eth-node-6b2d90/#"
Now pull the cable for twenty seconds and plug it back in. The serial log should show link down, no MQTT connection attempts while the cable is out, then link up, a new IP event and a reconnect. On the subscriber you will see status offline from the Last Will once the keep-alive expires, followed by online after recovery. This "cable pull test" is worth doing on every new hardware design; it catches power and PHY problems that a bench test with a stable link never reveals.
The public MQTT test broker is fine for this exercise, but its data is world-readable. Machine data belongs on private credentials.
Shaping Machine Data for MQTT
The telemetry in the sketch is deliberately minimal. On a real machine, decide early what goes into each message and how often. A few rules that hold up well in practice:
- Separate fast and slow data. Counters, temperatures and states that change every second go on a
telemetrytopic at a fixed interval. Configuration, firmware version and serial numbers go on aninfotopic, published retained once after each connect. - Publish on change for states. A machine state such as
RUNNING,IDLEorFAULTis more useful as a retained message published only when it changes than as a value repeated every five seconds. - Include units and a timestamp.
{"spindleTempC":41.2,"ts":1760600000}is self-describing; a bare41.2is not. - Keep payloads small. Wired Ethernet has plenty of bandwidth, but the broker, your dashboards and any database downstream all pay for verbose messages.
If you publish into a ConnectMQ broker, these conventions also make the live topic explorer far easier to read during commissioning, because each machine shows up as a tidy branch with a handful of meaningful topics.
Industrial Deployment Checklist
- Outbound firewall rules. Plant networks often block everything by default. Ask for outbound TCP 8883 to your broker host; you do not need any inbound rules because the ESP32 initiates the connection.
- Use TLS on 8883.
NetworkClientSecureworks over Ethernet exactly like over Wi-Fi; follow ESP32 MQTT SSL with WiFiClientSecure and swap the class name. - Static IP or DHCP reservation. Operations teams want to find the device. A DHCP reservation is usually easier to manage than a hard-coded address.
- NTP for timestamps. Industrial networks may block public NTP; use the plant's time server.
- Watchdog. Combine the event-driven design with the backoff in ESP32 MQTT reconnect logic and a hardware watchdog.
- Topic structure. Use an ISA-95 style hierarchy such as
site/area/line/cell/device/telemetryfrom day one; it scales from one node to a plant. - Power and grounding. Use DIN-rail supplies, shielded cable where drives are present, and consider PoE modules to remove a separate power run.
Fallback: Ethernet with Cellular Backup
For remote sites, some designs combine Ethernet with a cellular modem as backup. The MQTT layer stays the same; the firmware switches the Client it hands to PubSubClient. The ESP32 with SIM7600 4G LTE MQTT guide shows the cellular side.
From One Node to a Plant
Once a few nodes are publishing, the broker becomes shared infrastructure for the plant. A hosted MQTT broker removes the need to run and patch a server on the OT network. ConnectMQ detects ISA-95 and Unified Namespace topic structures automatically, so when your topics follow that structure the portal's devices view groups your Ethernet nodes by site, area and line, and threshold alert rules can flag a value that leaves its normal range. Using an MQTT broker in the cloud also lets engineering and remote support see the same live data without VPN access into the plant. For the full ESP32 learning path, see the ESP32 IoT tutorial.
Get Started with ConnectMQ
Create a free Serverless broker at app.connectmq.com, no credit card required. Give each Ethernet node its own credentials and watch your machine data appear in the live topic explorer within minutes.
Frequently Asked Questions
Does PubSubClient work over Ethernet on the ESP32?
Yes. PubSubClient accepts any Arduino Client, and on Arduino-ESP32 3.x NetworkClient works over both Wi-Fi and Ethernet through the same network stack.
Can I use a W5500 with an ESP32-S3 or ESP32-C3?
Yes. The W5500 connects over SPI, so it works on any ESP32 variant with free SPI pins. The LAN8720 needs the internal EMAC, which only the original ESP32 has among these.
Do I need the Arduino Ethernet library for a W5500?
Not on Arduino-ESP32 3.x. The built-in ETH library supports the W5500 directly and integrates with lwIP, so DNS, TLS and NetworkClient behave as they do on Wi-Fi.
Can the ESP32 use Wi-Fi and Ethernet at the same time?
Yes, both interfaces can be active. Make sure your firmware knows which one carries the MQTT connection and handles events from both.



