$ cd ..

Using a Pico W for wake-on-LAN... Remotely

đź“… 2024-08-26

⌛ 66 days ago


But why?

I love playing video games. Lately my friends and I have started a playthrough of Baldur’s Gate 3 (it’s been a blast, such a fun game!).

Unfortunately I have to travel fairly often. Now I could just run BG3 on my laptop, BG3 does have a really nice macOS build. But I’m a software engineer with a PhD in overengineering things (I run a k3s cluster just for Plex).

The plan is to use WoL to wake up my windows machine, and then use Sunshine + Moonlight to stream the game to my laptop.

The how

I thought the problem was simple enough, I could just use Tailscale to access my windows machine. Unfortunately WoL operates at OSI-2 layer and Tailscale on OSI-3 layer. Once a device is in a deep sleep mode, the NIC may not be fully functional in the context of a Tailscale connection, especially if the VPN connection is terminated.

I could just use a Raspberry Pi but I didn’t feel like spending ~$50 on such a small scale project. So after 2 minutes of research and ₹539 later, I ordered a Pi Pico W.

Le Problems

Image of a Raspberry Pi Pico W
Tiny.

The Pico does not work.

TIL there are two types of USB micro cables. One for charging and one for data transfer. The one I had was for charging i.e. the Pico wasn’t being recognised by my machines.

Once I got that sorted out, I faced another challenge:

The Pico cannot run Tailscale.

I realized this after the Pico was at my door. I had 2 options:

  1. Give up
  2. Try to remotely “notify” the Pico to send the ✨magic packets✨

Would something like… MQTT work?

The Plan

Mosquitto

Incredibly easy to set up, on my VPS I installed mosquitto:

sudo apt install mosquitto

And a simple mosquitto.conf:

listener 4242 0.0.0.0
allow_anonymous false
password_file /etc/mosquitto/passwd

Finally, generate a password file:

mosquitto_passwd -c /etc/mosquitto/passwd <username>

And then play around with mosquitto_pub and mosquitto_sub to make sure everything is working.

Micropython

I used Thonny to install umqtt.simple and used the following script:

import network
import time
from umqtt.simple import MQTTClient
import machine
import socket

# Configuration
WIFI_SSID = "WiFi"
WIFI_PASSWORD = "password"
MQTT_BROKER = "mosquitto.vps.com"
MQTT_PORT = 4242
MQTT_USER = "mosquitto"
MQTT_PASSWORD = "mosquitto"
MQTT_TOPIC = b"wol/command"

TARGET_MAC = "AA:BB:CC:DD:EE:FF"
BROADCAST_IP = "255.255.255.255"
WOL_PORT = 9

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(WIFI_SSID, WIFI_PASSWORD)
    while not wlan.isconnected():
        time.sleep(1)
    print('WiFi connected')

def send_wol_packet(mac_address):
    mac_bytes = bytes.fromhex(mac_address.replace(':', ''))
    magic_packet = b'\xff' * 6 + mac_bytes * 16
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    sock.sendto(magic_packet, (BROADCAST_IP, WOL_PORT))
    sock.close()
    print(f"Sent WoL packet to {mac_address}")

def on_message(topic, msg):
    if msg == b"wake":
        print("Wake-on-LAN command received")

        # Callback to send WoL packet
        send_wol_packet(TARGET_MAC)

def main():
    connect_wifi()
    client = MQTTClient("pico", MQTT_BROKER, MQTT_PORT, MQTT_USER, MQTT_PASSWORD)
    client.set_callback(on_message)
    client.connect()
    client.subscribe(MQTT_TOPIC)
    print(f"Connected to MQTT broker, subscribed to {MQTT_TOPIC}")
    
    while True:
        client.check_msg()
        time.sleep(1)

if __name__ == "__main__":
    main()

And voila! Here’s a demo video of the Pico W successfully waking my Windows machine remotely:

My “testbench”

Why didn’t you…

Could I have? Sure. Would it have been more efficient? Absolutely. But where’s the fun in that?

I believe side projects are the perfect playground for overengineering. I’m a big fan of doing things the “boring” way in production so this is a welcome escape.

Building a Rube Goldberg machine of a solution puts a smile on my face every time.