ESP32 Wake-on-LAN over MQTT with ESP-IDF

Wake-on-LAN needs to send a broadcast packet to a local subnet — you can't do that from outside NAT. The typical fix is a Raspberry Pi relaying the trigger. My fix: an ESP32 on the LAN, drawing ~80mA vs ~5W for the Pi.

Architecture

[You, remote]
      ↓ MQTT publish to wol/wake
[MQTT Broker]
      ↓ MQTT subscribe
[ESP32 on LAN]
      ↓ UDP broadcast 255.255.255.255:9
[Target NIC] → Machine powers on

Why ESP-IDF over Arduino

ESP-IDF gives direct access to FreeRTOS, lwIP, and the MQTT client with proper task scheduling and no hidden layers.

MQTT Setup

c
static esp_mqtt_client_config_t mqtt_cfg = {
    .broker.address.uri = CONFIG_BROKER_URL,
    .credentials.username = CONFIG_MQTT_USER,
    .credentials.authentication.password = CONFIG_MQTT_PASS,
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);

Magic Packet Construction

A WoL magic packet is 102 bytes: 6× 0xFF then 16 repetitions of the 6-byte MAC.

c
void send_magic_packet(const uint8_t *mac) {
    uint8_t packet[102];
    memset(packet, 0xFF, 6);
    for (int i = 0; i < 16; i++)
        memcpy(&packet[6 + i * 6], mac, 6);

    struct sockaddr_in dest = {
        .sin_family      = AF_INET,
        .sin_port        = htons(9),
        .sin_addr.s_addr = htonl(INADDR_BROADCAST),
    };
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    int bc = 1;
    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
    sendto(sock, packet, sizeof(packet), 0, (struct sockaddr *)&dest, sizeof(dest));
    close(sock);
}

Event Handler

c
if (event_id == MQTT_EVENT_DATA) {
    if (strncmp(event->topic, "wol/wake", event->topic_len) == 0) {
        uint8_t mac[6];
        char payload[18] = {0};
        memcpy(payload, event->data, MIN(event->data_len, 17));
        sscanf(payload, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
               &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
        send_magic_packet(mac);
    }
}

Triggering

bash
mosquitto_pub -h broker -t wol/wake -m "AA:BB:CC:DD:EE:FF" -u user -P pass

Machine wakes within 2-3 seconds. Reliability features: WiFi + MQTT auto-reconnect, QoS 1 for the subscription, hardware watchdog for unattended operation.

Source: ESP32-MQTT-WOL on GitHub.